Home arrow Java arrow Page 8 - Swing Animation
JAVA

Swing Animation


Have you ever been interested in creating a game using the Swing-based animation library? This article covers some important information to help you understand the backbone of this library. It is excerpted from the book Advanced Java Game Programming, written by David Wallace Croft (Apress, 2004; ISBN 1590591232).

Author Info:
By: Apress Publishing
Rating: 5 stars5 stars5 stars5 stars5 stars / 19
March 29, 2005
TABLE OF CONTENTS:
  1. · Swing Animation
  2. · RepaintCollector
  3. · SimpleRepaintCollector
  4. · CoalescingRepaintCollector
  5. · LoopGovernor
  6. · WindowedLoopGovernor
  7. · AnimatedComponent
  8. · Static method check()

print this article
SEARCH DEVARTICLES

Swing Animation - Static method check()
(Page 8 of 8 )

The static method check() throws a NullArgumentException if the argument is null. For this reason, the ComponentAnimator, RepaintCollector, and LoopGovernor instance variables can never be assigned null values. Knowing this removes the need to check for a null reference during animation operations that are called repeatedly. Classes such as NullRepaintCollector are useful as temporary do-nothing placeholders, or null objects, when no other instance would be appropriate or available.

 public void init ( )
 ///////////////////////////////////////////////////////
 {
 }

This init() method is empty. Subclass implementations may override this method without bothering to call the superclass implementation, but probably should do so anyway in case a future version is non-empty.

Lifecycle methods in general and the particular technique of animation thread management used in the following are described in detail in the previous chapter. The reader is advised to review that material before proceeding.

 public synchronized void start ( )
 /////////////////////////////////////////////////////
 {
   stopRequested = false;
   if ( animationThread == null )
   {
    animationThread = new Thread (
    new Runnable ( )
    {
    public void run ( )
    {
      loop ( );
    }
  },
    ANIMATION_THREAD_NAME );
   animationThread.setPriority ( Thread.MIN_PRIORITY );
   animationThread.setDaemon ( true );
   animationThread.start ( );
}
else
{
  notify ( );
 }
}

When the start() method is called for the first time, it creates the animation-Thread and starts it. When the start() method is called again to resume animation after it has been paused, it resets the stopRequested flag to false and notifies the animation loop that it should resume.

The thread priority is set to the minimum value to prevent the animation from blocking other Swing events. Given that the animation is running continuously in a fast loop, setting it to a low priority is necessary to prevent the user from experiencing poor responsiveness due to delayed processing of mouse and keyboard inputs.

The setDaemon() method call indicates that the animationThread is to be used as a daemon thread. Daemon threads differ from normal threads in that they terminate automatically when all normal threads have expired. This makes them ideal for running fire-and-forget background processes. By specifying the use of a daemon thread, AnimatedComponent ensures that a containing program can complete without requiring explicit control of the animationThread.

 public synchronized void stop ( )
 ///////////////////////////////////////////////////////
 {
   stopRequested = true;
   animationThread.interrupt ( );
 }

The stop() method sets the flag stopRequested. As described below, this causes the animation loop to stop. It also interrupts the animationThread in order to get it to stop what it is doing and check the state of the stopRequested flag.

 public synchronized void destroy ( )
 //////////////////////////////////////////////////////
 {
   animationThread = null;
   stopRequested = false;
   notify ( );
 }

The destroy() method dereferences the animationThread, which will cause the animation loop to terminate at the beginning of the next iteration. In order for the loop to reach the beginning of the next iteration when it is suspended, it must be restarted by resetting the stopRequested flag to false and generating a notification.

 public void paintComponent ( Graphics graphics )
 /////////////////////////////////////////////////////
 {
  componentAnimator.paint ( this, ( Graphics2D )
  graphics );
 }

This method simply delegates paint operations to the ComponentAnimator. The paintComponent() method overrides the superclass JComponent method. With Abstract Window Toolkit (AWT)-based animation, subclasses of Component override method paint() to provide the custom code to paint the component surface. With Swing JComponent subclasses, however, the paint() method is also responsible for painting the component borders and children, if any. It does so by calling methods paintComponent(), paintBorder(), and paintChildren(), in that order. For this reason, JComponent subclass AnimatedComponent overrides the paintComponent() method instead of paint().

As it is often unnecessary, the AnimatedComponent paintComponent() method does not clear the component rectangle by filling it with the background color prior to delegating to the ComponentAnimator paint() method. For example, a ComponentAnimator that paints an opaque background image across the entire area of the component would simply overwrite any previous frame painting, making pointless any first step of clearing the surface. The JComponent superclass implementation of paintComponent() clears the surface in this manner as a convenience for subclass implementations. Under the assumption that this preliminary step will frequently be unnecessary, however, the AnimatedComponent subclass implementation does not call super.paintComponent(). AnimatedComponent instead relies upon the ComponentAnimator instance to completely repaint the component area to cover up any outdated pixels if necessary.

Note that the paintComponent() method may be called when the animation loop has been stopped. In this case, the screen needs to be refreshed without updating the sprite positions. This might occur, for example, when the window is resized or when it is covered and then uncovered by another window while the game is paused.

 public void repaint ( )
 //////////////////////////////////////////////////////
 {
   repaintCollector.repaint ( );
 }
 public void repaint ( long tm )
 ////////////////////////////////////////////////////
 {
   repaintCollector.repaint ( );
 }

These overridden JComponent repaint methods delegate requests to repaint the entire component to the RepaintCollector.

 public void repaint (
   int x,
   int y,
   int width,
   int height )
 ///////////////////////////////////////////////////////
 {
   repaintCollector.repaint ( x, y, width, height );
 }
 public void repaint (
   long tm,
   int x,
   int y,
   int width,
   int height )
 //////////////////////////////////////////////////////
 {
   repaintCollector.repaint ( x, y, width, height );
 }
 public void repaint ( Rectangle r )
 ////////////////////////////////////////////////////
 {
  repaintCollector.repaint ( r.x, r.y, r.width, r.height );
 }

These three repaint methods are similar to the first two except that they request a repaint of a smaller area of the AnimatedComponent instead of the entire surface. This can result in a significant performance improvement.

 protected void loop ( )
 ///////////////////////////////////////////////////
{
  while ( animationThread != null )
  {
   try
   {
    EventQueue.invokeAndWait ( animationRunner );
    loopGovernor.govern ( );
    if ( stopRequested )
    {
     synchronized ( this )
     {
      while ( stopRequested )
      {
        wait ( );
     }
    }
   }
  }
  catch ( InterruptedException ex )
  {
  }
  catch ( InvocationTargetException ex )
  {
     ex.getCause ( ).printStackTrace ( );
   }
  } 
 }

The loop() method is the heart of AnimatedComponent; it is what keeps the animation beating. It periodically queues the animationRunner for execution within the event dispatch thread and then stalls until it is finished. The LoopGovernor govern() method is then used to delay the loop just long enough to achieve the desired animation frame rate.

As the stop() method also calls Thread.interrupt() on the animationThread, the govern() method can throw an InterruptedException if it checks the status of Thread.interrupted(). This check normally occurs automatically if Thread.sleep() is called within the implementation of method govern().

The loop exits when the animationThread is dereferenced by the destroy() method. Note that it does not exit immediately when destroy() is called, but only upon the check at the beginning of the next loop iteration. This can cause complications if a subclass implementation needs to delay the deallocation of resources used in animation until after the loop has completed. Since the destroy() method will most likely complete before the loop() method, these resources must be destroyed at the end of the loop() method instead. This can be accomplished by overriding the loop() method in a subclass implementation.

 protected void animate ( )
 //////////////////////////////////////////////////////
 {
   componentAnimator.update ( this );
   int count = repaintCollector.getCount ( );
   Rectangle [ ] repaintRegions
    = repaintCollector.getRepaintRegions ( );
   for ( int i = 0; i < count; i++ )
   {
    paintImmediately ( repaintRegions [ i ] );
   }
   repaintCollector.reset ( );
 }

The private animate() method starts by calling the ComponentAnimator update() method, which updates the positions of the sprites on the screen and requests that the AnimatedComponent repaint itself where necessary. After all of the sprite positions have been updated and all of the repaint requests have been collected, paintImmediately() is called for each repaint region. The paint-Immediately() method calls the paintComponent() method, which is overridden to call the ComponentAnimator paint() method. Directly and indirectly then, the job of animate() is to call the ComponentAnimator update() and paint() methods in that order. When it is finished, it resets the repaintCollector for the next animation loop.

When the animation loop is stopped, the animate() method is not called and any repaint requests redirected by the overridden repaint() methods to the repaintCollector continue to accumulate without being processed. Fortunately, the system windowing events that require a repaint of the component will bypass the repaint() method and call paintComponent() directly. This permits the screen to be repainted while the game is paused without sending a repaint request to the RepaintCollector.

Since the animate() method is only called within the event dispatch thread, the update, paint, and reset operations are properly serialized. One might worry that a repaint() method call generated by the application from a place other than the animation loop might cause a problem in two ways. First, it might try to repaint the screen while the sprite positions are being updated. Second, the repaint request might be lost if it is collected just before the RepaintCollector reset() method is called. Neither one of these is a problem, however, since the repaint requests are processed serially in the event dispatch thread.

Summary

In this chapter you learned about the four basic animation classes: Component-Animator, RepaintCollector, LoopGovernor, and AnimatedComponent. An implementation of the interface ComponentAnimator represents the bulk of your application-specific code, as it provides the game logic for updating the sprite positions. AnimatedComponent and the general-purpose implementations of the interfaces RepaintCollector and LoopGovernor form the reusable core of the Swing-based animation engine. Understanding how these classes operate and interoperate is crucial to fine-tuning animation performance.

Further Reading

Robinson, Matthew and Pavel Vorobiev, “Swing Mechanics.” Chapter 2 in Swing. Greenwich, CT: Manning Publications Company, 2000.

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.


DISCLAIMER: The content provided in this article is not warranted or guaranteed by Developer Shed, Inc. The content provided is intended for entertainment and/or educational purposes in order to introduce to the reader key ideas, concepts, and/or product reviews. As such it is incumbent upon the reader to employ real-world tactics for security and implementation of best practices. We are not liable for any negative consequences that may result from implementing any information covered in our articles or tutorials. If this is a hardware review, it is not recommended to open and/or modify your hardware.

blog comments powered by Disqus
JAVA ARTICLES

- Java Too Insecure, Says Microsoft Researcher
- Google Beats Oracle in Java Ruling
- Deploying Multiple Java Applets as One
- Deploying Java Applets
- Understanding Deployment Frameworks
- Database Programming in Java Using JDBC
- Extension Interfaces and SAX
- Entities, Handlers and SAX
- Advanced SAX
- Conversions and Java Print Streams
- Formatters and Java Print Streams
- Java Print Streams
- Wildcards, Arrays, and Generics in Java
- Wildcards and Generic Methods in Java
- Finishing the Project: Java Web Development ...

Watch our Tech Videos 
Dev Articles Forums 
 RSS  Articles
 RSS  Forums
 RSS  All Feeds
Write For Us 
Weekly Newsletter
 
Developer Updates  
Free Website Content 
Contact Us 
Site Map 
Privacy Policy 
Support 

Developer Shed Affiliates

 




© 2003-2017 by Developer Shed. All rights reserved. DS Cluster - Follow our Sitemap
Popular Web Development Topics
All Web Development Tutorials