Creating a BufferStrategy couldn't be simpler. We simply ask the Canvas to do it for us. The only thing that needs to be specified is how many buffers to use to manage the screen, in this case we're going to use just 2.

Finally for each Entity we have we'd like to have an image displayed, using an old term, a Sprite. However, we might use the same Sprite for multiple entities, for instance the aliens. It seems logically therefore to keep the sprite as a separate object from the entity. In addition we don't want to waste graphics memory so we'd like to only load each sprite once. To manage this it'd be nice to add a class to manage loading of the Sprites and storing them for future use. So we add a pair of classes to our design, Sprite and SpriteStore .

In that window we want to see some things moving around. The player's ship, the aliens and the shots that the players fire. Since all of these things have common properties (i.e. they display a graphic and move around the screen) we can infer a common class to represent each one with potentially subclasses to define the specific behaviors of these different types. Since "thing" is such a terrible name for a class, for our design I'm going to call them Entities. From this we get 4 classes. Entity with 3 subclasses, ShipEntity , AlienEntity and ShotEntity

For our space invaders game we're going to have a main window. The window needs to use accelerated graphics. It also needs to respond to the player's key presses to move our player's ship around. For now we can call this class Game since it represents our main game.

Disclaimer: This tutorial is provided as is. I don't guarantee that the provided source is perfect or that that it provides best practices.

The final game can be see here . The complete source for the tutorial can be found here . Its intended that you read through this tutorial with the source code at your side. The tutorial isn't going to cover every line of code but should give you enough to fully understand how it works.

At this stage we're just interested in getting the screen swap and timing done, so we add a function called gameLoop that does this:

while (gameRunning) { // work out how long its been since the last update, this // will be used to calculate how far the entities should // move this loop long delta = System.currentTimeMillis() - lastLoopTime; lastLoopTime = System.currentTimeMillis(); // Get hold of a graphics context for the accelerated // surface and blank it out Graphics2D g = (Graphics2D) strategy.getDrawGraphics(); g.setColor(Color.black); g.fillRect(0,0,800,600); // finally, we've completed drawing so clear up the graphics // and flip the buffer over g.dispose(); strategy.show(); // finally pause for a bit. Note: this should run us at about // 100 fps but on windows this might vary each loop due to // a bad implementation of timer try { Thread.sleep(10); } catch (Exception e) {} }

Sprites and Resource Management

The Sprite class

So heres the sprite class. Essentially it just takes and holds an image which can be drawn at a specified location onto a graphics context.

public class Sprite { /** The image to be drawn for this sprite */ private Image image; /** * Create a new sprite based on an image * * @param image The image that is this sprite */ public Sprite(Image image) { this.image = image; } /** * Get the width of the drawn sprite * * @return The width in pixels of this sprite */ public int getWidth() { return image.getWidth(null); } /** * Get the height of the drawn sprite * * @return The height in pixels of this sprite */ public int getHeight() { return image.getHeight(null); } /** * Draw the sprite onto the graphics context provided * * @param g The graphics context on which to draw the sprite * @param x The x location at which to draw the sprite * @param y The y location at which to draw the sprite */ public void draw(Graphics g,int x,int y) { g.drawImage(image,x,y,null); } }

Sprite Loading and Management

Implementing the singleton

Implementing this in our sprite store looks like this:

/** The single instance of this class */ private static SpriteStore single = new SpriteStore(); /** * Get the single instance of this class * * @return The single instance of this class */ public static SpriteStore get() { return single; }

Loading the Sprites

The first step is locate the sprite:

URL url = this.getClass().getClassLoader().getResource(ref);

The next step is to actually load in the image. In Java this is a simple matter of using the utility class ImageIO. To load our image we use this code:

sourceImage = ImageIO.read(url);

Finally, we need to allocate some accelerated graphics memory to store our image in. This will allow the image to be drawn without the CPU getting involved, we just want the graphics card to do the work for us.

Allocating the graphics memory is achieved like so:

// create an accelerated image of the right size to store our sprite in GraphicsConfiguration gc = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().getDefaultConfiguration(); Image image = gc.createCompatibleImage(sourceImage.getWidth(),sourceImage.getHeight(),Transparency.BITMASK);

// draw our source image into the accelerated image image.getGraphics().drawImage(sourceImage,0,0,null);

Caching the Sprites

// create a sprite, add it the cache then return it Sprite sprite = new Sprite(image); sprites.put(ref,sprite);

// if we've already got the sprite in the cache // then just return the existing version if (sprites.get(ref) != null) { return (Sprite) sprites.get(ref); }

Movement

// cycle round asking each entity to move itself for (int i=0;i<entities.size();i++) { Entity entity = (Entity) entities.get(i); entity.move(delta); } // cycle round drawing all the entities we have in the game for (int i=0;i<entities.size();i++) { Entity entity = (Entity) entities.get(i); entity.draw(g); }

The Entity class

Once these properties are defined we can cover the two methods that we require of Entity. The move() method looks like this:

public void move(long delta) { // update the location of the entity based on move speeds x += (delta * dx) / 1000; y += (delta * dy) / 1000; }

Next we need to be able to draw an Entity onto our accelerated graphics context. draw() is implemented like this:

public void draw(Graphics g) { sprite.draw(g,(int) x,(int) y); }

Now we've defined our basic entity we should create a few subclasses as place-holders for some more functionality we'll add later on. We simply need to create the 3 subclasses of Entity; ShipEntity, ShotEntity and AlienEntity. For now we won't bother adding anything extra but it normally pays to be aware and add these things up front.

The final step is to create our entities and add them to the game world. If we add a utility method to central Game class called initEntities(). This will initialize a set of entities at the game start. The current implementation looks like this:

private void initEntities() { // create the player ship and place it roughly in the center of the screen ship = new ShipEntity(this,"sprites/ship.gif",370,550); entities.add(ship); // create a block of aliens (5 rows, by 12 aliens, spaced evenly) alienCount = 0; for (int row=0;row<5;row++) { for (int x=0;x<12;x++) { Entity alien = new AlienEntity(this,"sprites/alien.gif",100+(x*50),(50)+row*30); entities.add(alien); alienCount++; } } }

The second step is to create all the aliens. We loop through creating a block of aliens. Again each alien is just the creation of the AlienEntity positioned at the right location. In addition we count how many aliens we've created so we can track whether the player has won the game.

Assuming the code to support moving and displaying the entities has been added to main game loop, running the game should now show the player's ship and a bunch of aliens.

Now each type of Entity moves in its own way and with its own constraints. Lets look at each one.

Ship Entity

public void move(long delta) { // if we're moving left and have reached the left hand side // of the screen, don't move if ((dx < 0) && (x < 10)) { return; } // if we're moving right and have reached the right hand side // of the screen, don't move if ((dx > 0) && (x > 750)) { return; } super.move(delta); }

Shot Entity

To start the shot moving with initialize the vertical movement to a negative number based on the speed we'd like the shot to move. The movement method itself looks like this:

public void move(long delta) { // proceed with normal move super.move(delta); // if we shot off the screen, remove ourselfs if (y < -100) { game.removeEntity(this); } }

Alien Entity

Hopefully, we now know what we want to do, so how? First we initialize the movement of the aliens to start them moving to the left based on the predefined speed. Next we put the detection of an alien hitting the side in the movement routine like this:

public void move(long delta) { // if we have reached the left hand side of the screen and // are moving left then request a logic update if ((dx < 0) && (x < 10)) { game.updateLogic(); } // and vice vesa, if we have reached the right hand side of // the screen and are moving right, request a logic update if ((dx > 0) && (x > 750)) { game.updateLogic(); } // proceed with normal move super.move(delta); }

Keyboard Input

private class KeyInputHandler extends KeyAdapter { public void keyPressed(KeyEvent e) { if (e.getKeyCode() == KeyEvent.VK_LEFT) { leftPressed = true; } if (e.getKeyCode() == KeyEvent.VK_RIGHT) { rightPressed = true; } if (e.getKeyCode() == KeyEvent.VK_SPACE) { firePressed = true; } } public void keyReleased(KeyEvent e) { if (e.getKeyCode() == KeyEvent.VK_LEFT) { leftPressed = false; } if (e.getKeyCode() == KeyEvent.VK_RIGHT) { rightPressed = false; } if (e.getKeyCode() == KeyEvent.VK_SPACE) { firePressed = false; } } public void keyTyped(KeyEvent e) { // if we hit escape, then quit the game if (e.getKeyChar() == 27) { System.exit(0); } } }

To make this class work we need to add it to our canvas as a "KeyListener". This is done by adding the following line to the constructor of our Game class:

addKeyListener(new KeyInputHandler());

With this source code in place the boolean flags will be set to appropriate values as the keys are pressed. The next step is to respond to these boolean flags being set in the game loop. If we add this in the game loop we can add keyboard control to the ship:

// resolve the movement of the ship. First assume the ship // isn't moving. If either cursor key is pressed then // update the movement appropriately ship.setHorizontalMovement(0); if ((leftPressed) && (!rightPressed)) { ship.setHorizontalMovement(-moveSpeed); } else if ((rightPressed) && (!leftPressed)) { ship.setHorizontalMovement(moveSpeed); }

If the player holds the fire key we'd like the ship to fire a shot up towards the aliens. However, we don't want to let the player just keep firing. It'd be good to limit how often they can fire. We'll put this functionality in a utility method called (somewhat logically) "tryToFire()"

// if we're pressing fire, attempt to fire if (firePressed) { tryToFire(); }

public void tryToFire() { // check that we have waiting long enough to fire if (System.currentTimeMillis() - lastFire < firingInterval) { return; } // if we waited long enough, create the shot entity, and record the time. lastFire = System.currentTimeMillis(); ShotEntity shot = new ShotEntity(this,"sprites/shot.gif",ship.getX()+10,ship.getY()-30); entities.add(shot); }

Collision Detection

First, we'll need to implement a check to resolve whether two entities have in fact collided. We'll do this in the Entity class like this:

public boolean collidesWith(Entity other) { me.setBounds((int) x,(int) y,sprite.getWidth(),sprite.getHeight()); him.setBounds((int) other.x,(int) other.y,other.sprite.getWidth(),other.sprite.getHeight()); return me.intersects(him); }

First we configure the two rectangles to represent the two entities. Next we use the inbuilt functionality of java.awt.Rectangle to check if the two entities intersect with each other. This isn't the smartest way to do this by any means, but for our purposes it will be good enough.

The next thing we'll add is a way to notify entities that they have collided with another. To do this we'll add a method like this to the Entity class:

public abstract void collidedWith(Entity other);

ShipEntity

public void collidedWith(Entity other) { // if its an alien, notify the game that the player // is dead if (other instanceof AlienEntity) { game.notifyDeath(); } }

ShotEntity

public void collidedWith(Entity other) { // if we've hit an alien, kill it! if (other instanceof AlienEntity) { // remove the affected entities game.removeEntity(this); game.removeEntity(other); // notify the game that the alien has been killed game.notifyAlienKilled(); } }

Game Loop Additions

// brute force collisions, compare every entity against // every other entity. If any of them collide notify // both entities that the collision has occurred for (int p=0;p<entities.size();p++) { for (int s=p+1;s<entities.size();s++) { Entity me = (Entity) entities.get(p); Entity him = (Entity) entities.get(s); if (me.collidesWith(him)) { me.collidedWith(him); him.collidedWith(me); } } }

A smarter way to do this might be to check for the collisions only every so often. It might also be nice to have a flag on an entity whether it should detect collisions.

Game Logic

notifyDeath()

notifyWin()

notifyAlienKilled()

public void notifyAlienKilled() { // reduce the alien count, if there are none left, the player has won! alienCount--; if (alienCount == 0) { notifyWin(); } // if there are still some aliens left then they all need to get faster, so // speed up all the existing aliens for (int i=0;i<entities.size();i++) { Entity entity = (Entity) entities.get(i); if (entity instanceof AlienEntity) { // speed up by 2% entity.setHorizontalMovement(entity.getHorizontalMovement() * 1.02); } } }

The last step we need is to support entity based game logic. We implied the requirement when building the AlienEntity. When a single alien detects the edge of the screen we want all the aliens to change direction. To support this we're going to add two sections of code.

Entity Logic

public void doLogic() { // swap over horizontal movement and move down the // screen a bit dx = -dx; y += 10; // if we've reached the bottom of the screen then the player // dies if (y > 570) { game.notifyDeath(); } }

Entity Logic Infrastructure

public void updateLogic() { logicRequiredThisLoop = true; }

// if a game event has indicated that game logic should // be resolved, cycle round every entity requesting that // their personal logic should be considered. if (logicRequiredThisLoop) { for (int i=0;i<entities.size();i++) { Entity entity = (Entity) entities.get(i); entity.doLogic(); } logicRequiredThisLoop = false; }

Finishing Off

If you have any comments or corrects feel free to mail me here

Exercises for the Reader

Frame Rate Counter

Animated Aliens

Alien Return Fire

Pause Button

Further Reading