Under the sea

Explore an "under the sea" application and its animation engine

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.

Introducing UTS

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

Application architecture

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.

Application class

The UTS application class specifies the following:

  • Constants describing the desired display mode and the desired animation frame rate (the number of animation frames to render each second)
  • Method 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
  • A method for returning random integers

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.

Animation and audio classes

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); } }

Resource-loader class

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.
1 2 3 Page
Join the discussion
Be the first to comment on this article. Our Commenting Policies
See more