Simple Effects
As you can imagine, it's pretty easy to add more sprites at this point. Just create more Sprite objects, and be sure to update and draw each one-for example:
for (int i=0; i<sprites.length; i++) { sprites[i].update(elapsedTime); g.drawImage(sprites[i].getImage(), Math.round(sprite[i].getX()), Math.round(sprite[i].getY()), null); }
One thing to note is that sprites update their own animations, so sprites can't share the same Animation object; the Animation object would get updated too many times. If you're using a lot of animations that are the same, you might want to add a clone() method to the animation to make them easier to duplicate.
Image Transforms
One cool effect is rotating or scaling an image. This is called an image transform. Image transforms enable you to translate, flip, scale, shear, and rotate images, even on the fly. An AffineTransform object describes a transform. The AffineTransform class stores its transform data in a 2D 3x3 matrix, but it's not necessary to understand how matrices work with graphics. Instead, the class provides several easy methods such as rotate(), scale(), and translate() that do the calculations for you. The Graphics2D class is where the transform actually takes place. There's a special drawImage() method in Graphics2D that takes an AffineTransform as a parameter. Here's an example of drawing an image scaled twice as large:
AffineTransform transform = new AffineTransform(); transform.scale(2,2); transform.translate(100,100); g.drawImage(image, transform, null);
Note that transform operations are based on an origin, which is the upper-left corner of the image. That means you might have to translate the image to get the results you expect. Now, let's add a couple more sprites and some image transforms to SpriteTest1. The character normally always faces right, but let's make the character face left when it's moving left. Instead of loading separate images for facing left, let's use transforms to create a mirror of the image on the fly. To do this, scale the width of the image by -1, and then translate the result so it is positioned correctly.
transform.scale(-1, 1); transform.translate(-sprite.getWidth(), 0);
Just for fun, SpriteTest2, in Listing 2.11, also has a fade effect. When the program first starts, the graphics fade from black like you are opening horizontal blinds. The same effect happens in reverse as the program exits. This effect is created by using the fillRect() method of the Graphics object to create solid black "blinds" that have a size based on the amount of time the demo has run. See Screenshot for a screen capture of SpriteTest2.
Listing 2.11 SpriteTest2.java
import java.awt.*; import java.awt.geom.AffineTransform; import javax.swing.ImageIcon; public class SpriteTest2 { public static void main(String args[]) { SpriteTest2 test = new SpriteTest2(); test.run(); } private static final DisplayMode POSSIBLE_MODES[] = { new DisplayMode(800, 600, 32, 0), new DisplayMode(800, 600, 24, 0), new DisplayMode(800, 600, 16, 0), new DisplayMode(640, 480, 32, 0), new DisplayMode(640, 480, 24, 0), new DisplayMode(640, 480, 16, 0) }; private static final long DEMO_TIME = 10000; private static final long FADE_TIME = 1000; private static final int NUM_SPRITES = 3; private ScreenManager screen; private Image bgImage; private Sprite sprites[]; public void loadImages() { // load images bgImage = loadImage("images/background.jpg"); Image player1 = loadImage("images/player1.png"); Image player2 = loadImage("images/player2.png"); Image player3 = loadImage("images/player3.png"); // create and init sprites sprites = new Sprite[NUM_SPRITES]; for (int i = 0; i < NUM_SPRITES; i++) { Animation anim = new Animation(); anim.addFrame(player1, 250); anim.addFrame(player2, 150); anim.addFrame(player1, 150); anim.addFrame(player2, 150); anim.addFrame(player3, 200); anim.addFrame(player2, 150); sprites[i] = new Sprite(anim); // select random starting location sprites[i].setX((float)Math.random() * (screen.getWidth() - sprites[i].getWidth())); sprites[i].setY((float)Math.random() * (screen.getHeight() - sprites[i].getHeight())); // select random velocity sprites[i].setVelocityX((float)Math.random() - 0.5f); sprites[i].setVelocityY((float)Math.random() - 0.5f); } } private Image loadImage(String fileName) { return new ImageIcon(fileName).getImage(); } public void run() { screen = new ScreenManager(); try { DisplayMode displayMode = screen.findFirstCompatibleMode(POSSIBLE_MODES); screen.setFullScreen(displayMode); loadImages(); animationLoop(); } finally { screen.restoreScreen(); } } public void animationLoop() { long startTime = System.currentTimeMillis(); long currTime = startTime; while (currTime - startTime < DEMO_TIME) { long elapsedTime = System.currentTimeMillis() - currTime; currTime += elapsedTime; // update the sprites update(elapsedTime); // draw and update screen Graphics2D g = screen.getGraphics(); draw(g); drawFade(g, currTime - startTime); g.dispose(); screen.update(); // take a nap try { Thread.sleep(20); } catch (InterruptedException ex) { } } } public void drawFade(Graphics2D g, long currTime) { long time = 0; if (currTime <= FADE_TIME) { time = FADE_TIME - currTime; } else if (currTime > DEMO_TIME - FADE_TIME) { time = FADE_TIME - DEMO_TIME + currTime; } else { return; } byte numBars = 8; int barHeight = screen.getHeight() / numBars; int blackHeight = (int)(time * barHeight / FADE_TIME); g.setColor(Color.black); for (int i = 0; i < numBars; i++) { int y = i * barHeight + (barHeight - blackHeight) / 2; g.fillRect(0, y, screen.getWidth(), blackHeight); } } public void update(long elapsedTime) { for (int i = 0; i < NUM_SPRITES; i++) { Sprite s = sprites[i]; // check sprite bounds if (s.getX() < 0.) { s.setVelocityX(Math.abs(s.getVelocityX())); } else if (s.getX() + s.getWidth() >= screen.getWidth()) { s.setVelocityX(-Math.abs(s.getVelocityX())); } if (s.getY() < 0) { s.setVelocityY(Math.abs(s.getVelocityY())); } else if (s.getY() + s.getHeight() >= screen.getHeight()) { s.setVelocityY(-Math.abs(s.getVelocityY())); } // update sprite s.update(elapsedTime); } } public void draw(Graphics2D g) { // draw background g.drawImage(bgImage, 0, 0, null); AffineTransform transform = new AffineTransform(); for (int i = 0; i < NUM_SPRITES; i++) { Sprite sprite = sprites[i]; // translate the sprite transform.setToTranslation(sprite.getX(), sprite.getY()); // if the sprite is moving left, flip the image if (sprite.getVelocityX() < 0) { transform.scale(-1, 1); transform.translate(-sprite.getWidth(), 0); } // draw it g.drawImage(sprite.getImage(), transform, null); } } }
Screenshot SpriteTest2 animates several sprites at once and uses transforms to create mirror images.
In SpriteTest2, the sprites' images are flipped on the fly. There's just one problem with this: Transforming images does not take advantage of hardware acceleration. Even if only a simple transform such as translation is done, the image isn't accelerated. So, normally, it might be a good idea to use transforms sparingly. You should note a couple things about these sprite demos. First, although we kept all sprites on the screen, it's okay to draw sprites partially off-screen or completely off-screen-Java2D takes care of clipping for you. Second, we're tying our animation to the system clock, which isn't very granularity on Windows platforms. We create a workaround for this problem later in , "Optimization Techniques."