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).
It has been my experience that the most significant performance optimization one can make in Java game programming is to minimize the number of pixels painted in each new frame of animation. Sometimes you can simply repaint the entire screen every time and still achieve your desired frame rate. Example-Animator, for example, requests a repaint of the entire component each time its update() method is called. On slower computers or platforms without graphics acceleration, however, your animation may be so slow that the game is unplayable. The following describes a mechanism for ensuring that your game will run with acceptable performance on a wide range of platforms.
The Swing GUI library code has been optimized for efficiency, which means that there are very few synchronized methods that would prevent methods from being called simultaneously by different threads. In other words, most Swing methods are not thread-safe and should only be executed serially, i.e., one at a time. If you do manage to call the methods simultaneously, you will sometimes see funny effects such as components not properly redrawing themselves on the screen in response to multiple simultaneous external events.
Most of the time, however, this is not a problem, as Swing methods such as repaint() do not actually repaint the screen immediately but instead simply queue the requests for serial execution later by another special thread. This special thread is called the event dispatch thread and it continuously monitors the AWT EventQueue for operations to perform serially.
The repaint() method is smart enough that if multiple repaint requests pile up in the AWT EventQueue faster than the event dispatch thread can process them, they will be coalesced, i.e., merged, into a single request for efficiency. If the repaint areas for the multiple requests are not exactly identical, the new merged request will cover a rectangular area that encloses the union of both of the original requested areas.
This attempt at efficiency causes a perverse effect when it comes to high-speed animation in Swing. Often it would take less time to process the requests separately than if they were merged into one. For example, suppose that you have a single sprite roaming around in the top-left corner of your scene with a stationary background. Your repaint request can be handled quickly just by repainting the background over a very small square covering the old position and another at the sprite’s new position. If the sprite is 32 pixels in width and 32 pixels in height (32×32), this is just 2 × 32 × 32 = 2048 pixels.
You can often get away with drawing even fewer pixels because the new sprite area will overlap the old sprite area. For example, if the maximum sprite velocity and the frame rate is such that you know that the sprite can only move one pixel per frame at the most, you could just repaint over a single 33×33 pixel area. This is just 1089 pixels, about half the 2048 if we drew both separately. In this case, collapsing two repaint requests, one for the old sprite position and another for the new one, into a single request has helped us. Before Just-In-Time (JIT) compilers, gigahertz computers, and accelerated graphics came on the scene, this is a trick that I used in the early days of Java to achieve fast animation.
But suppose now that you have two sprites wandering around the scene on a stationary background, one in the upper right corner and the other in the lower left. If the requests to repaint these sprites are processed separately, only 2 × 33 × 33 = 2178 pixels need to be painted. But if they are merged into one request, it is a different story. Suppose the two sprites are separated by 300 pixels vertically and horizontally. Any merged repaint request for two separate sprites would have to use a rectangle that enclosed this 300×300 inner area, which is 90,000 extra pixels to draw. As the sprites approached each other, the animation would speed up. As they got farther apart, it would slow down.
As shown in Figure 3-1, you can observe this phenomenon using the Sprite program in the CroftSoft Collection. When the Sprite program starts, the target frame rate is set to the default maximum value as measured in frames per second (fps). Since most machines today cannot repaint the component that fast, the actual frame rate achieved will be less. Under Options, select “Only sprite regions.” The frame rate should suddenly increase dramatically since only the sprite regions, not the entire component, are being repainted in each frame. You should be able to distinguish these repaint regions clearly, as the background brick pattern will be moving within their bounds but not outside of them. After observing the behavior for a bit, select the option “Use Swing RepaintManager.” The sprite repaint regions will then coalesce into a single repaint region that expands as the sprites move away from each other and contracts as they approach. The frame rate will be inversely proportional to the size of this region.
Figure 3-1.Coalescing repaint requests
Another problem with the default Swing repaint behavior for animation is that some repaints may occur before you want them to. Suppose that you have two sprites sailing along side by side from left to right. In this case you want to update the position of the first sprite, then the second, and then repaint both simultaneously so that one does not appear to be leading the other momentarily. Whether the default Swing repaint behavior repaints each sprite separately or at the same time depends upon the timing of the repaint requests and whether they get coalesced or not.
Class AnimatedComponent, described in detail at the end of this chapter, allows you to override the default behavior of the repaint operations by delegating all repaint requests to an instance of interface RepaintCollector from package com.croftsoft.core.animation. RepaintCollector is responsible for collecting the repaint requests generated during the update phase. This allows you to provide your own strategy for how you want to merge multiple repaint requests and control when they are processed.
public int getCount ( ); public Rectangle [ ] getRepaintRegions ( );
Interface RepaintCollector method getCount() returns the number of repaint requests made during the current animation-loop iteration. The requested repaint areas are returned by method getRepaintRegions(). The length of the Rectangle array returned may be longer than the request count; in this case only the first count positions will contain valid data.
public void repaint ( ); public void repaint ( int x, int y, int width, int height );
AnimatedComponent delegates repaint requests to the RepaintCollector using these two methods. The first method requests a repaint of the entire component and the second generates a request to repaint a limited rectangular area on the component. Painting the entire component is useful, for example, when the entire background of the scene is moving.
public void reset ( );
The reset() method is called at the end of each animation loop iteration after the component has been repainted. It resets the request count to zero in preparation for the next loop.
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.