Swing Animation - AnimatedComponent
(Page 7 of 8 )
Class AnimatedComponent from package com.croftsoft.core.animation is the Swing component that brings it all together by providing the surface on which the painting occurs, the animation loop that calls the other classes, and the lifecycle methods that allow Swing components to be integrated into a framework.
package com.croftsoft.core.animation;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.lang.reflect.InvocationTargetException;
import javax.swing.JComponent;
import com.croftsoft.core.animation.factory.DefaultAnimationFactory; import com.croftsoft.core.lang.NullArgumentException;
import com.croftsoft.core.lang.lifecycle.Lifecycle;
import com.croftsoft.core.util.loop.LoopGovernor;
public class AnimatedComponent
extends JComponent
implements Lifecycle
///////////////////////////////////////////////////
//////////////////////////////////////////////////
{
AnimatedComponent is a Swing JComponent subclass implementation. It implements interface Lifecycle so that its animation can be stopped and restarted as necessary.
public static final String ANIMATION_THREAD_NAME =
"Animation";
//
protected final Runnable animationRunner;
//
protected ComponentAnimator componentAnimator;
protected RepaintCollector repaintCollector;
protected LoopGovernor loopGovernor;
protected Thread animationThread;
protected boolean stopRequested;
Runnable instance animationRunner is used to call the update() and paint() methods within a separate thread. ComponentAnimator is used to animate the AnimatedComponent. RepaintCollector is used to aggregate repaint requests. LoopGovernor regulates the animation speed. The animationThread variable refers to the current Thread instance that drives the animation loop. The boolean flag stopRequested is used to indicate that animation should stop.
The references to the instances of the interfaces ComponentAnimator, Repaint-Collector, and LoopGovernor are not final, as they may be replaced during runtime if desired. This is used in the demonstration Sprite program to compare the performance of different implementations.
public AnimatedComponent (
ComponentAnimator componentAnimator,
RepaintCollector repaintCollector,
LoopGovernor loopGovernor )
/////////////////////////////////////////////////////
{
setComponentAnimator ( componentAnimator );
setRepaintCollector ( repaintCollector );
setLoopGovernor ( loopGovernor );
setOpaque ( true );
animationRunner =
new Runnable ( )
{
public void run ( )
{
animate ( );
}
};
}
Methods setComponentAnimator(), setRepaintCollector(), and setLoop-Governor() simply save references to the constructor arguments and are described below.
JComponent method setOpaque() is used to indicate whether a subclass implementation has any transparent areas. For reasons of efficiency, the Animated-Component subclass marks itself as opaque, i.e., it has no transparent regions, so that any components behind and completely obscured by this object will not be drawn unnecessarily. If AnimatedComponent were to be displayed as an odd shape such as a circle, it would be necessary to set opaque to false so that components behind the transparent regions would be repainted as necessary. For this implementation, however, a simple rectangular display is assumed and the default JComponent property value of false is overridden.
The constructor method initializes the final variable animationRunner to an anonymous inner class implementation of Runnable that simply redirects to the protected method animate(). The animationRunner is created only once during the constructor method, as the animation loop will use it over and over again instead of creating a new instance with each iteration.
public AnimatedComponent (
ComponentAnimator componentAnimator,
AnimationFactory animationFactory,
double frequency )
/////////////////////////////////////////////////////
{
this (
componentAnimator,
animationFactory.createRepaintCollector ( ),
animationFactory.createLoopGovernor ( frequency ) );
}
public AnimatedComponent (
ComponentAnimator componentAnimator,
AnimationFactory animationFactory )
///////////////////////////////////////////////////
{
this (
componentAnimator,
animationFactory.createRepaintCollector ( ),
animationFactory.createLoopGovernor ( ) );
}
These convenience constructors use an instance of the interface AnimationFactory from package com.croftsoft.core.animation to create the RepaintCollector and LoopGovernor objects required by the main constructor. The choice of RepaintCollector and LoopGovernor implementations will significantly impact the animation performance. Providing an AnimationFactory implementation as an argument allows you to choose a preferred strategy.
The constructor argument frequency is used to determine the animation speed. Film provides the eye with the illusion of smooth motion by displaying sampled snapshots of the world at the rate of 24 fps. By repainting itself at periodic intervals of 1/24th of a second per frame, AnimatedComponent can achieve a similar result.
The second convenience constructor does not take a frequency argument. In this case, a LoopGovernor will be created that runs the animation at the default frame rate as determined by the AnimationFactory.
public AnimatedComponent (
ComponentAnimator componentAnimator,
double frequency )
////////////////////////////////////////////////////////
{
this (
componentAnimator,
DefaultAnimationFactory.INSTANCE,
frequency );
}
public AnimatedComponent ( ComponentAnimator
componentAnimator )
////////////////////////////////////////////////////////// {
this (
componentAnimator,
DefaultAnimationFactory.INSTANCE );
}
DefaultAnimationFactory from package com.croftsoft.core.animation.factory is a recommended AnimationFactory implementation. The concrete implementations produced by this factory may change over time as new research suggests better algorithms.
public synchronized ComponentAnimator setComponentAnimator (
ComponentAnimator componentAnimator )
//////////////////////////////////////////////////////
{
NullArgumentException.check ( componentAnimator );
ComponentAnimator oldComponentAnimator =
this.componentAnimator;
this.componentAnimator = componentAnimator;
return oldComponentAnimator;
}
public synchronized RepaintCollector setRepaintCollector (
RepaintCollector repaintCollector )
//////////////////////////////////////////////////////
{
NullArgumentException.check ( repaintCollector );
RepaintCollector oldRepaintCollector =
this.repaintCollector;
this.repaintCollector = repaintCollector;
return oldRepaintCollector;
}
public synchronized LoopGovernor setLoopGovernor (
LoopGovernor loopGovernor )
//////////////////////////////////////////////////////
{
NullArgumentException.check ( loopGovernor );
LoopGovernor oldLoopGovernor = this.loopGovernor;
this.loopGovernor = loopGovernor;
return oldLoopGovernor;
}
These mutator methods permit the ComponentAnimator, RepaintCollector, and LoopGovernor instances to be replaced as necessary during animation. The methods return the replaced instances for future use. These methods are synchronized to prevent them from being called simultaneously by multiple threads. It is assumed that the methods are called infrequently, much less than once per animation loop on average, so the performance hit from synchronizing these methods should be negligible.
Multiple ComponentAnimator, RepaintCollector, and LoopGovernor instances can be pre-constructed in memory and then instantly swapped in and out of the AnimatedComponent as required. For example, alternating the ComponentAnimator can cause the scene to suddenly switch to a different view, such as an animated pause screen, and just as suddenly, back again.
This article is excerpted from Advanced Java Game Programming by David Wallace Croft (Apress, 2004; ISBN 1590591232). Check it out at your favorite bookstore today. Buy this book now. |
Next: Static method check() >>
More Java Articles
More By Apress Publishing