Some reader favorites:
EJB fundamentals and session beans
Create a scrollable virtual desktop in Swing
Wizard API updated!
Tim Boudreau has released a new version of the Swing Wizard library (version 0.997) that fixes the WizardException bug reported in JavaWorld's recent Open Source Java Project profile. The article's examples have been reworked to test out the new, improved WizardException. Thanks, Tim, for this helpful fix!
Open Source Java Projects: The Wizard API
In version 1.4 of the Java 2 SDK, Sun Microsystems introduced support for full-screen exclusive mode, an operating system feature that lets programs obtain exclusive access to and render their output to the entire screen, which results in high-performance graphics. (Microsoft Windows implements full-screen exclusive mode via its DirectX technology, for example.) This Java Fun and Games installment reveals this support in the context of an "under the sea" application and its animation engine.
This article first introduces you to the "under the sea" (UTS) application, where you learn about the application's image and audio resources. The article next explores the application's architecture in terms of the application class, the animation and audio classes, and the resource-loader class. You next tour the animation engine's engine class, animation and audio interfaces, and exception classes. In closing, this article focuses on how to deploy the application.
| Note |
|---|
| Unlike most of the previous Java Fun and Games installments, which I wrote from the perspective of J2SE 1.4, this installment requires Java SE 5.0. For thorough coverage of Java's support for full-screen exclusive mode, consult the Java Tutorial. |
I've created a UTS application that works with the animation engine to animate underwater sea life over the entire screen. This application's source code, audio/image resource, and miscellaneous files can be downloaded from Resources.
This figure presents a single animation frame. This frame reveals a suitable background, an animated angel fish, two instances of an animated tiger barb, two instances of an animated zebra fish, two instances of an animated plant, and three instances of an animated bubble sequence.
The figure shows various graphics images that I've combined into a somewhat realistic underwater scene: I created the background image with the Terragen scenery generator and enhanced the resulting image to achieve a bluish haze in the distance; the angel fish, tiger barb, and zebra fish images were made available to me courtesy of Dave Sutton at Sevenoaks Art; and the bubbles/plants images are my own creations.
Each type of fish requires 16 images (8 for moving left to right, and 8 for moving right to left), the bubble sequence requires
8 images, and the plant animation requires 3 images. The background image and one other image (used for hiding the mouse cursor)
round out the image resources. These image resources are organized into gif files that are stored in a hierarchical resource
directory structure, rooted in the images directory:
images
angelfish
lfish1.gif
lfish2.gif
lfish3.gif
lfish4.gif
lfish5.gif
lfish6.gif
lfish7.gif
lfish8.gif
rfish1.gif
rfish2.gif
rfish3.gif
rfish4.gif
rfish5.gif
rfish6.gif
rfish7.gif
rfish8.gif
bubbles
bubbles1.gif
bubbles2.gif
bubbles3.gif
bubbles4.gif
bubbles5.gif
bubbles6.gif
bubbles7.gif
bubbles8.gif
misc
background.gif
white.gif
plant
plant1.gif
plant2.gif
plant3.gif
tigerbarb
lfish1.gif
lfish2.gif
lfish3.gif
lfish4.gif
lfish5.gif
lfish6.gif
lfish7.gif
lfish8.gif
rfish1.gif
rfish2.gif
rfish3.gif
rfish4.gif
rfish5.gif
rfish6.gif
rfish7.gif
rfish8.gif
zebrafish
lfish1.gif
lfish2.gif
lfish3.gif
lfish4.gif
lfish5.gif
lfish6.gif
lfish7.gif
lfish8.gif
rfish1.gif
rfish2.gif
rfish3.gif
rfish4.gif
rfish5.gif
rfish6.gif
rfish7.gif
rfish8.gif
Along with presenting animations, the UTS application is capable of playing an audio clip (in the Sun AU format) to add realism. This audio clip can be toggled off and on by pressing the "A" key.
The audio clip produces an underwater bubbling sound effect. As with both the bubbles and plants images, this audio clip is
my own creation. It is stored in the hierarchical resource directory structure, rooted in the audios directory:
audios bubbles.au
The architecture of the UTS application divides into an application class, several animation and audio classes, and a class for loading audio and image resources. This section's exploration of these classes illustrates how to create an application that works with the animation engine.
The UTS application class specifies the following:
public static void main(String [] args) to create various animation and audio objects, and the animation engine; register the animation and audio objects with the
engine; run the animations and play audio; shut down the engine; and terminate the application
The code below illustrates the UTS class:
public class UTS
{
// Screen width (in pixels)
final static int WIDTH = 800;
// Screen height (in pixels)
final static int HEIGHT = 600;
// Color mode -- 32 indicates true-color (32-bit) mode
final static int BITDEPTH = 32;
// Animation frame rate, measured in frames per second
final static int FPS = 20;
public static void main (String [] args) throws Exception
{
// Create the animations.
AngelFish af = new AngelFish (WIDTH, HEIGHT-AngelFish.height-
rnd (Fish.VERTICAL_RANGE), WIDTH,
HEIGHT, 3, AngelFish.LEFT, 0);
Background bg = new Background ();
Bubbles b1 = new Bubbles (168, 251-Bubbles.height, 3);
Bubbles b2 = new Bubbles (376, 205-Bubbles.height, 3);
Bubbles b3 = new Bubbles (679, 309-Bubbles.height, 3);
Plant p1 = new Plant (20, HEIGHT-Plant.height, 3);
Plant p2 = new Plant (WIDTH-Plant.width-20, HEIGHT-Plant.height, 3);
TigerBarb tb1 = new TigerBarb (-TigerBarb.width,
HEIGHT-TigerBarb.height-
rnd (Fish.VERTICAL_RANGE), WIDTH,
HEIGHT, 1, TigerBarb.RIGHT, 2);
TigerBarb tb2 = new TigerBarb (-TigerBarb.width,
HEIGHT-TigerBarb.height-
rnd (Fish.VERTICAL_RANGE), WIDTH,
HEIGHT, 2, TigerBarb.RIGHT, 1);
ZebraFish zf1 = new ZebraFish (-ZebraFish.width,
HEIGHT-ZebraFish.height-
rnd (Fish.VERTICAL_RANGE), WIDTH,
HEIGHT, 2, ZebraFish.RIGHT, 1);
ZebraFish zf2 = new ZebraFish (-ZebraFish.width,
HEIGHT-ZebraFish.height-
rnd (Fish.VERTICAL_RANGE), WIDTH,
HEIGHT, 3, ZebraFish.RIGHT, 0);
// Create the audio.
BubblesAudio ba = new BubblesAudio ();
// Create an animation engine for a WIDTHxHEIGHTxBITDEPTH screen, where
// the overall animation rate does not exceed FPS. Enter fullscreen
// exclusive mode and switch the display mode to WIDTHxHEIGHTxBITDEPTH.
AnimationEngine engine;
engine = new AnimationEngine (WIDTH, HEIGHT, BITDEPTH, FPS);
// Register the animations with the animation engine. The order in
// which animations are registered determines their Z-order -- which
// animations cover other animations. Earlier registered animations are
// covered by latter registered animations.
engine.register (bg);
engine.register (b1);
engine.register (b2);
engine.register (b3);
engine.register (zf1);
engine.register (af);
engine.register (tb1);
engine.register (tb2);
engine.register (zf2);
engine.register (p1);
engine.register (p2);
// Register the audio with the animation engine.
engine.register (ba);
// Run the animations and play the audio.
engine.runAnimations ();
// Shutdown the animation engine, reverting the display mode to the
// display mode that was in effect before creating the animation engine.
engine.shutdown ();
// Terminate the application: System.exit is necessary.
System.exit (0);
}
public static int rnd (int limit)
{
// Return a randomly-selected integer ranging from 0 through limit-1.
return (int) (Math.random ()*limit);
}
}
Although the UTS class is easy to understand, you might be curious about my decision to create the animation and audio objects before creating
the animation engine. Creating the animation and audio objects causes resources to load; cumulative resource loading takes
time, and engine creation results in the screen switching to full-screen exclusive mode. If I chose to create the animation
and audio objects after the engine, you would observe a blank screen for a few seconds (on slower computers) before the animation
starts.
The UTS application includes several classes that describe various animations and render animation frames: AngelFish, Bubbles, Plant, TigerBarb, and ZebraFish—classes AngelFish, TigerBarb, and ZebraFish share a common Fish superclass. A Background class is also present. Although this class is implemented similarly to the other animation classes, the background is not
animated.
Each animation class loads its image resources in a static initializer. This implies that the image resources load exactly
once (when the class is loaded instead of each time an object is created), which reduces the application's memory usage. In
contrast, I chose to have the Background class load its image resource in its constructor—I don't need to create multiple Background objects. The following Bubbles class reveals its static initializer loading its image resources:
class Bubbles implements Animation
{
private static Image imBubbles [];
static int height;
static
{
imBubbles = ResourceLoader.loadImages ("images/bubbles", "bubbles", 8);
height = imBubbles [0].getHeight (null);
}
private int x;
private int y;
private int numFramesToSkip;
private int nextFrame;
private int frameCounter;
Bubbles (int x, int y, int numFramesToSkip)
{
this.x = x;
this.y = y;
this.numFramesToSkip = numFramesToSkip;
}
public void render (Graphics g)
{
g.drawImage (imBubbles [nextFrame], x, y, null);
if (numFramesToSkip == 0 || ++frameCounter == numFramesToSkip)
{
if (++nextFrame == imBubbles.length)
nextFrame = 0;
frameCounter = 0;
}
}
}
The Bubbles class and the earlier UTS class demonstrate a second advantage to using a static initializer. Because an image's width and height are available after
the class has loaded (all images must share the same width and height in a sequence of images), their dimensions can be obtained
to help define the animation's initial position. For example, UTS's main() method retrieves the value of Bubbles.height when calculating the upper row for each image in each bubbles animation.
The Bubbles constructor saves both the initial position and a "number of frames to skip" value. This value determines how quickly the
animation moves to the next frame. Larger values slow down this movement, which lets you assign different speeds to animations.
Assigning a speed to an animation is not the same thing as specifying how quickly an animation moves across the screen. For
example, the Fish class adjusts a fish's movement by adding or subtracting a velocity to/from its current horizontal position—x += vx; and x -= vx;. The animation speed lets you determine how quickly the fish moves its fins and tail; the "movement across the screen" speed
lets you determine how quickly the fish reaches the left or right side of the screen.
Application UTS also specifies a BubblesAudio class for retrieving an audio clip and a delay factor. After creating a BubblesAudio object and registering it with the animation engine, no further work is required because the engine invokes the methods at
the appropriate times:
class BubblesAudio implements Audio
{
private AudioClip ac;
BubblesAudio ()
{
ac = ResourceLoader.loadAudioClip ("audios/bubbles.au");
}
public AudioClip getAudioClip ()
{
return ac;
}
public int getDelay ()
{
return UTS.FPS+UTS.rnd (100);
}
}
The ResourceLoader utility class conveniently loads the application's audio and image resources, hiding the loading details to support future
implementation changes. As you've previously seen, its methods are invoked by the various animation and audio classes. Methods
include:
public static AudioClip loadAudioClip(String name): Loads the audio clip identified by name and returns this clip as a java.applet.AudioClip. If the audio clip cannot be loaded, null returns.
public static Image loadImage(String name): Loads the image identified by name and returns this image as a java.awt.Image. If the image cannot be loaded, null returns.
public static Image [] loadImages(String dir, String prefix, int count): Repeatedly calls the previous method to load count images from directory dir into an array. Names share a common prefix and are sequentially numbered, starting with 1.
The animation engine consists of an engine class, animation and audio interfaces, and exception classes. This section's tour of these classes and interfaces teaches you about full-screen exclusive mode and gives you the necessary knowledge for customizing the animation engine.
The AnimationEngine class is the heart of the animation engine. This class provides a constructor, animation, and audio registration methods,
a method to run the animations and play the audio, and a method that shuts down the animation engine:
public AnimationEngine(int width, int height, int bitDepth, int fps): Constructs the animation engine, entering full-screen exclusive mode with the appropriate display settings.
public void register(Animation animation): Registers an animation object with the animation engine. Two of this object's methods are called by runAnimations().
public void register(Audio audio): Registers an audio object with the animation engine. One of this object's methods is called by runAnimations().
public void runAnimations(): Enters a loop that runs registered animations and plays registered audio. The loop continues until the Esc key is pressed
or a mouse button is clicked.
public void shutdown(): Shuts down the animation engine by terminating full-screen exclusive mode, restoring the display mode to the mode that was
previously in effect.
The AnimationEngine constructor initializes the animation engine by performing a variety of tasks. Tasks related to full-screen exclusive mode
include making sure that full-screen exclusive mode is supported, creating a display mode, switching to full-screen exclusive
mode, establishing the display mode, and creating a buffer strategy that determines the form of buffering used to obtain high-performance
graphics output. These and other tasks are illustrated in the following AnimationEngine excerpt:
public AnimationEngine (int width, int height, int bitDepth, int fps)
throws NoFullScreenException, UnsupportedDisplayModeException
{
GraphicsEnvironment ge;
ge = GraphicsEnvironment.getLocalGraphicsEnvironment ();
monitor = ge.getDefaultScreenDevice ();
if (!monitor.isFullScreenSupported ())
throw new NoFullScreenException ();
DisplayMode myMode;
myMode = new DisplayMode (width, height, bitDepth,
DisplayMode.REFRESH_RATE_UNKNOWN);
DisplayMode [] availModes = monitor.getDisplayModes ();
int i = 0;
for (i = 0; i < availModes.length; i++)
if (availModes [i].getWidth () == myMode.getWidth () &&
availModes [i].getHeight () == myMode.getHeight () &&
availModes [i].getBitDepth () == myMode.getBitDepth ())
break;
if (i == availModes.length)
throw new UnsupportedDisplayModeException ();
this.width = width;
this.height = height;
this.fps = fps;
animations = new ArrayList<Animation> ();
Frame frame = new Frame ();
frame.setUndecorated (true);
frame.setResizable (false);
frame.setIgnoreRepaint (true);
frame.addKeyListener (new KeyAdapter ()
{
public void keyPressed (KeyEvent e)
{
if (e.getKeyCode () == KeyEvent.VK_ESCAPE)
done = true;
}
public void keyTyped (KeyEvent e)
{
if (e.getKeyChar () == 'a')
playAudio = !playAudio;
}
});
frame.addMouseListener (new MouseAdapter ()
{
public void mousePressed (MouseEvent e)
{
done = true;
}
});
monitor.setFullScreenWindow (frame);
monitor.setDisplayMode (myMode);
frame.createBufferStrategy (2);
strategy = frame.getBufferStrategy ();
Toolkit toolkit = Toolkit.getDefaultToolkit ();
Image imWhite = ResourceLoader.loadImage ("images/misc/white.gif");
Cursor hidden = toolkit.createCustomCursor (imWhite, new Point (0, 0),
"");
frame.setCursor (hidden);
}
After obtaining the graphics environment and identifying the environment's default screen device, the constructor invokes
java.awt.GraphicsDevice's public boolean isFullScreenSupported() method to determine if full-screen exclusive mode is supported for this device. If full-screen exclusive mode is supported,
this method returns true. Otherwise, this method returns false, which leads to a thrown exception.
Moving forward, the values passed in AnimationEngine's width, height, and bitDepth parameters are passed to java.awt.DisplayMode's public DisplayMode(int width, int height, int bitDepth, int refreshRate) constructor (along with a constant that indicates unknown display refresh rate), and a display mode is created. If this mode
matches a display mode supported by the default screen device, the engine is created; otherwise, an exception is thrown.
Java's support for full-screen exclusive mode requires a window to be created that will occupy the entire screen. This window
is either a java.awt.Window instance or a subclass of Window—like java.awt.Frame. After creating the Frame instance, the constructor configures this instance to make sure that the frame is not decorated, the frame cannot be resized,
and any paint messages sent to the frame by the underlying operating system are ignored.
The GraphicsDevice class provides a public void setFullScreenWindow(Window w) method for switching to and from full-screen exclusive mode. This method takes a java.awt.Window (or subclass) argument that identifies the window to use as a full-screen window. After creating and configuring the Frame instance, the engine's constructor passes this instance to setFullScreenWindow(), which immediately enters full-screen exclusive mode.
The GraphicsDevice class also provides a public void setDisplayMode(DisplayMode dm) method that switches a screen device's display mode to the specified display mode. The engine's constructor invokes this
method to switch the default screen device's display mode to the previously established mode. This display mode exists for
as long as full-screen exclusive mode is in effect. As you will see, shutting down the animation engine restores the previous
display mode.
Following the establishment of the display mode, the AnimationEngine constructor establishes a buffer strategy—a buffering technique for improving the performance of graphics output—for the previously created frame. The public void createBufferStrategy(int numBuffers) method is called to establish the best-possible double-buffering strategy. This strategy is subsequently returned by invoking
public BufferStrategy getBufferStrategy(), which I discuss later.
| Subject | Replies |
Last post
|
|
By zxevil181 |
0 |
04/05/08 06:06 PM
by Anonymous |
Free Download - 5 Minute Product Review. When slow equals Off: Manage the complexity of Web applications - Symphoniq
![]()
Free Download - 5 Minute Product Review. Realize the benefits of real user monitoring in less than an hour. - Symphoniq