Download Ssjava.java source file
// S.S. Java 1 - an animation demo adapted from Sun's Java Tutorial.
// This version uses a swing timer to send action events at regular
// intervals, which are used to move and repaint the animation.
// The space ship S.S. Java is moved across a star field, and the
// user can click the mouse to start/stop the animation.
//
// The animation uses a "frame number" to advance the image
// a certain number of pixels per repaint update (5). This number
// is used modulo the width of the applet (which is the width of
// the background image).
//
// Notice how when used as an appet a MediaTracker is used to
// preload the image, and how the Applet start/stop methods are
// used in conjunction with the boolean "frozen" to control the
// animation. Also a main has been included so this program can
// be run as a stand-alone as well. Notice no media tracker is
// needed in this case, but certain window event handler methods
// are used to control the animation, just like the applet start
// and stop.
//
// Notice too the new style of showing multiple closing braces
// on a single line. I feel this is just as readable, see if you do.
//
// Written 3/2001 by Wayne Pollock, Tampa Florida USA.
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class Ssjava1 extends JApplet implements ActionListener
{
int frameNumber = -1;
static final int pelsPerUpdate = 2; // a "pel" is a "pixel"
boolean frozen = false;
Timer timer;
AnimationPane animationPane;
// Should probably use PARAM tags (and cmd line args) to set these:
static String fgFile = "rocketship.gif";
static String bgFile = "starfield.gif";
//Invoked only when run as an applet, don't invoke from main:
public void init()
{
// Get and load the images:
Image bgImage = getImage( getCodeBase(), bgFile );
Image fgImage = getImage( getCodeBase(), fgFile );
MediaTracker mt = new MediaTracker( this );
mt.addImage( bgImage, 0 );
mt.addImage( fgImage, 1 );
try { mt.waitForAll(); }
catch ( InterruptedException ignored ) {}
buildUI( getContentPane(), bgImage, fgImage );
}
void buildUI ( Container container, Image bgImage, Image fgImage )
{
int fps = 25; // Frames per Second, should be >28 to appear smooth.
// How many milliseconds between frames?
// 1000 / (frames per second) = milliseconds delay per frame.
int delay = ( fps > 0 ) ? ( 1000 / fps ) : 40;
// Set up a swing timer that calls this object's action handler:
timer = new Timer( delay, this );
timer.setInitialDelay( 0 ); // Start immediately.
timer.setCoalesce( true ); // ok to skip updates if falling behind.
animationPane = new AnimationPane( bgImage, fgImage );
container.add( animationPane, BorderLayout.CENTER );
animationPane.addMouseListener( new MouseAdapter()
{ public void mousePressed( MouseEvent e )
{ if ( frozen )
{ frozen = false;
startAnimation();
} else
{ frozen = true;
stopAnimation();
} } }
);
}
// Invoked by a browser only:
public void start ()
{ startAnimation();
}
// Invoked by a browser only:
public void stop ()
{ stopAnimation();
}
// Can be invoked from any thread, for instance if a user clicks then
// immediately clicks "Back" then this might be called simultaneously
// from main and the AWT Event Dispatch Thread.
// Since "frozen" and "timer" are shared resources make method synchronized:
public synchronized void startAnimation ()
{ if ( ! frozen )
{ // Start animating only if not frozen and not already running:
if ( ! timer.isRunning() )
{ timer.start();
} } }
// Can be invoked from any thread like startAnimation:
public synchronized void stopAnimation ()
{ // Stop the animating thread:
if ( timer.isRunning() )
{ timer.stop();
} }
public void actionPerformed ( ActionEvent e )
{ // Advance animation frame counter and repaint:
frameNumber++;
animationPane.repaint();
}
// An inner class, this is all that actually gets repainted:
class AnimationPane extends JPanel
{
Image background, foreground;
public AnimationPane ( Image background, Image foreground )
{ this.background = background;
this.foreground = foreground;
}
// Draw the current frame of animation:
public void paintComponent ( Graphics g )
{
super.paintComponent( g ); //paint any space not covered
//by the background image
int compWidth = getWidth();
int compHeight = getHeight();
// Safty check: Only draw if we have a valid image:
int imageWidth = background.getWidth( this );
int imageHeight = background.getHeight( this );
if ( (imageWidth > 0) && (imageHeight > 0) )
{ g.drawImage( background, (compWidth - imageWidth)/2,
(compHeight - imageHeight)/2, this );
}
imageWidth = foreground.getWidth( this );
imageHeight = foreground.getHeight( this );
if ( (imageWidth > 0) && (imageHeight > 0) )
{ int pos = ( (frameNumber * pelsPerUpdate) %
(imageWidth + compWidth) ) - imageWidth;
g.drawImage( foreground, pos, (compHeight - imageHeight)/2, this );
} }
} // End of AnimationPane class.
// Invoked only when run as an application:
public static void main ( String [] args )
{
Toolkit tk = Toolkit.getDefaultToolkit();
Image bgImage = tk.getImage( bgFile );
Image fgImage = tk.getImage( fgFile );
JFrame f = new JFrame( "S. S. Java 1" );
// Anonymous inner classes can only access final variables:
final Ssjava1 controller = new Ssjava1();
controller.buildUI( f.getContentPane(), bgImage, fgImage );
f.addWindowListener( new WindowAdapter()
{ public void windowIconified ( WindowEvent e )
{ controller.stopAnimation();
}
public void windowDeiconified ( WindowEvent e )
{ controller.startAnimation();
}
public void windowClosing ( WindowEvent e )
{ System.exit(0);
}
}
);
f.setSize( new Dimension( 500, 100 ) );
f.setVisible( true );
controller.startAnimation();
}
}