Asteroids Tutorial

From Victory Engine

Jump to: navigation, search

In this tutorial, we will use the Victory Engine to create a small game similar to the classic game Asteroids. You will need a basic knowledge of Java to follow this tutorial, and it will not cover such things as object-oriented programming or adding the game engine to your build/classpath.

You will need to download the resources for this tutorial here.

Contents

Game Description

The player controls a spaceship from a top-down view. Asteroids are flying around on the screen. The player must evade them, or destroy them by shooting them with his cannon. If an asteroid is hit, it will split into three smaller ones. If the player gets hit, he loses a life. More and more asteroids will appear over time. The goal is to survive as long as possible.

Image:Asteroidstut_screen.jpg

Creating a new empty game

To begin, create a package named asteroids. In this package, we'll create our main game class, which we will simply call AsteroidsGame.

package asteroids;

import org.openwar.victory.Game;

/**
 * The main class of the Asteroids game.
 * @author Bart van Heukelom
 */
public class AsteroidsGame extends Game {

}

Notice how this class extends the class Game. This is the core class of the Victory engine.

Now, let's add a main method and make this game runnable. First add:

import org.openwar.victory.graphics.j2d.JDisplay;

Then add the main method to AsteroidsGame:

    /**
     * The entry point for our game.
     * @param args Command line arguments.
     */
    public static void main(final String[] args) {
        
        final AsteroidsGame game = new AsteroidsGame();
        
        final JDisplay display = new JDisplay();
        game.setDisplay(display);
        game.setInputManager(new AWTInputManager(display.getCanvas()));
        game.start();
        
    }

What happens here? First, we construct a new instance of our game. In fact, we construct the instance, because there is only supposed to be one. Next we set the display system we want to use. We choose JDisplay, which uses Java2D, Java's built-in graphics library. For this tutorial, we don't give it any parameters which mean the game will run in a window, sized 800 x 600. Then we create our input manager, which we can use to get input from the player. Since we'll be using the default AWT based implementation, we need to give it a component to get events from, which is our display's canvas. Lastly, we start the game.

If you try running the game now, you will see that a blank white window will be opened. Note that the game cannot be closed be closing the window. If you ran the game from the command line, hit Ctrl+C. If you ran it from your IDE, there should be a stop or terminate button.

Now, let's do some game initialisation. Again, add some imports first:

import org.openwar.victory.graphics.Colour;
import org.openwar.victory.graphics.DisplayListener;

Then, add this method to AsteroidsGame:

    /**
     * Initialise the game.
     */
    @Override
    protected void init() {
        
        // set the title of our window
        getDisplay().setWindowTitle("Asteroids");
        
        // add a display listener. this listener will receive events
        // from the display. in this case we tell the game to stop
        // when the display window is closed
        getDisplay().addListener(new DisplayListener() {
            @Override
            public void windowClosed() {
                stop();
            }
        });
        
        // set the background colour of our display to a spacy black
        getDisplay().setBackgroundColour(Colour.BLACK);
        
    }

The method will be called at game initialisation, which is shortly after start. If you run the game now, you will see that the window is black, has a nice title and the game can be stopped by closing the window.

Loading Sprites

Now, let's add some graphics to our game. First, create a new directory called res (next to the asteroids package). In this directory, create a new directory sprites. Copy all images from the resource package, download above, into this directory.

Next we, must make these sprites available to the game. For that purpose, create a new file called sprites.res in res:

# sprite list for asteroids
#
# name        file           frames  originx   originy
# ----------------------------------------------------------------
  ship        ship.png	     1	     c	       c
  ship_fire   ship_fire.png  1       c         c
  bullet      bullet.png     1	     c	       c
  asteroid    asteroid.png   1	     c	       c
  background  background.png 1       c         c

This file defines the sprites for the game. An explanation of the collumns:

  • name: the identifier by which this sprite is accessible in the game.
  • file: the name of the image file containing the sprite.
  • frames: the amount of frames in the sprite.
  • originx: the horizontal component of the "origin" of the sprite. This is the point that will be positioned at point 0,0 when drawing the sprite. The value c here means that it's centered.
  • originy: the vertical component of the origin.

Now, we must tell the game read this so-called "batch file". Again, import the class ResourceManager first:

import org.openwar.victory.extra.ResourceManager;

Then, add the following lines to the bottom of init:

        // load sprites and other resources
        try {
            ResourceManager.get().loadResources();
        } catch (GameException e) {
            e.printStackTrace();
            exit(e.getCode());
        }

This will load up the ResourceManager, an extra utility class for managing sprites and other resources, and tell it to load the resource from the batch files. This may result in an exception being thrown when a resource cannot be found, hence the try/catch block.

You can try running the game to see whether it works. If all is well, the game should start just as before, though there will not be any visible changes.

Building space

Now that we have a nice background sprite, let's tell the game to use it. In the init method, find this bit of code:

        // set the background colour of our display to a spacy black
        getDisplay().setBackgroundColour(Colour.BLACK);

and move it all the way to the bottom of the method. Then, add another line, so it will become:

        // set the background colour of our display to a spacy black
        getDisplay().setBackgroundColour(Colour.BLACK);
        getDisplay().setBackgroundImage(ResourceManager.get().getSprite("background").getImage());

This line will call the resource manager again and get the background sprite we've just loaded. It will then get it's image (sprites are more than images, they also contain origin and animation information, which regular images do not) and assign that to the game background.

Fire up the game and see how nice it looks. Right...

Creating our ship

It's time to put in our spaceship. Create a new class Ship:

package asteroids;

import org.openwar.victory.extra.PhysicalObject;
import org.openwar.victory.extra.ResourceManager;
import org.openwar.victory.geom.Point;

/**
 * The ship of the player.
 * @author Bart van Heukelom
 */
public class Ship extends PhysicalObject {

    /**
     * Create the ship.
     * @param pos The position to create it at.
     */
    public Ship(final Point pos) {
        super(pos);
        setFacing(-Math.PI / 2);
        setSprite(ResourceManager.get().getSprite("ship"));
        setDrawingFilter(true);
    }
    
}

The class Ship extends the class PhysicalObject. This is another extra class which takes care of many often-used aspects in physical objects such as position, speed, force and graphics. In our constructor, we first call the super constructor with the position we want to spawn at (the position is defined as a Point, which contains x and y coordinates and methods to work with them). Then we set our facing direction (the direction in which the sprite will be rotated) to up. Directions in the victory engine are measured in radians (Pi radians = 180 degrees), counterclockwise from the east, so up is -1/2Pi, or -(Pi/2). Next, we give the ship the correct sprite. Lastly, we make it use filtering when drawing it's sprite, which will greatly improve the sprite quality when the ship is rotated.

Now we need to tell the game to create this ship in it's world. Define a private field to keep track of it in AsteroidsGame:

private Ship player;

Then at the bottom of init, spawn the ship:

        // create the player and add it to the game
        player = new Ship(getDisplay().getCenter());
        addEntity(player);

What happens here? First, we create an instance of the ship, and tell it to be spawned at the center of the screen. Then we add our new entity to the game. An entity is simply an actor in the game, and participates in the game and rendering loops. PhysicalObject extends Entity.

If you launch the game now, you will see a spaceship floating in the middle of the screen.

Controlling the ship

Let's make our ship controllable by the player. First, import some classes:

import org.openwar.victory.geom.Point;
import org.openwar.victory.math.Vector2;
import org.openwar.victory.input.InputManager;

Then override PhysicalObject's beginstep method. This is the first of the three step phases, which are executed every step in the game loop.

    /**
     * Let the ship be controlled by the player.
     * @param step The current step.
     */
    @Override
    public void beginstep(final int step) {
        
        // control ship trough player input
        final InputManager in = Game.get().getInputManager();
        
        if (in.getKeyDown(InputManager.VK_UP)) {
            setForce("thrust", Vector2.fromLengthDirection(0.2, getRotation()));
            setSprite(ResourceManager.get().getSprite("ship_fire"));
        } else {
            unsetForce("thrust");
            setSprite(ResourceManager.get().getSprite("ship"));
        }
        
        // rotate
        boolean rotated = false;
        if (in.getKeyDown(InputManager.VK_LEFT)) {
            rotated = true;
            setRotationSpeed(getRotationSpeed() - Math.toRadians(1));
        }
        if (in.getKeyDown(InputManager.VK_RIGHT)) {
            rotated = true;
            setRotationSpeed(getRotationSpeed() + Math.toRadians(1));
        }
        if (rotated) {
            if (Math.abs(getRotationSpeed()) > Math.toRadians(20)) {
                setRotationSpeed(Math.signum(getRotationSpeed()) * Math.toRadians(20));
            }
        } else {
            setRotationSpeed(getRotationSpeed() * 0.9);
        }
        
        // execute motion
        move();
        
        // wrap
        getPosition().shift(Game.get().getDisplay().getBounds(), 20);
        
    }

Here's a breakdown of what this does:

First, we get the input manager from the game and store it in a variable called in, just for convenience.

        final InputManager in = Game.get().getInputManager();

The next step is controlling the thrust (forward motion) of the ship. We check with the input manager whether the arrow-up (VK_UP) key is down. If that is the case, we apply a force to the ship. This force is represented a by a Vector2, which is a 2D vector (a vector is a single unit combining a direction and length). We set the force to 0.2, which means that, with the default mass of 1.0 which the ship has, the speed will be increased by 0.2 pixels/step every step. This force should be applied in the direction the ship is facing, hence getFacing(). We give the force the name "thrust", by which we can refer to it later.

We also set the sprite of the ship to the one with some fire coming from it's exhaust.

        if (in.getKeyDown(InputManager.VK_UP)) {
            setForce("thrust", Vector2.fromLengthDirection(0.2, getFacing()));
            setSprite(ResourceManager.get().getSprite("ship_fire"));
        }

If the up key is not pressed, we simply remove the thrust force (by referring to it's name), and set the sprite to the one without fire.

        else {
            unsetForce("thrust");
            setSprite(ResourceManager.get().getSprite("ship"));
        }

In the next section, we rotate the ship depending on the state of the arrow-left and arrow-right keys. While not realistic, for gameplay purposes we limit the maximum rotation speed, and we make it stop rotating if no key is pressed at all.

        // rotate
        boolean rotated = false;
        if (in.getKeyDown(InputManager.VK_LEFT)) {
            rotated = true;
            setRotationSpeed(getRotationSpeed() - Math.toRadians(1));
        }
        if (in.getKeyDown(InputManager.VK_RIGHT)) {
            rotated = true;
            setRotationSpeed(getRotationSpeed() + Math.toRadians(1));
        }
        if (rotated) {
            if (Math.abs(getRotationSpeed()) > Math.toRadians(20)) {
                setRotationSpeed(Math.signum(getRotationSpeed()) * Math.toRadians(20));
            }
        } else {
            setRotationSpeed(getRotationSpeed() * 0.9);
        }

Now that we've set all forces, we can let PhysicalObject do it's physics. For that purpose, we call move(), a method in PhysicalObject.

In the last section, we make the ship wrap. If it gets outside the boundaries of the screen, it is moved to the other side.

        getPosition().shift(Game.get().getDisplay().getBounds(), 20);

Notice how we get the position, but then use shift on it and the ship's actual position will change! This is because the Point returned by getPosition() is the original reference to the position in PhysicalObject, so modifying it will affect the object. Keep this in mind! It can be convenient, but can also cause bugs when not handled carefully. If you want to execute modifications on a position without affecting the object, clone it:

        // clone, then modify
        setPosition(someObject.getPosition().clone().add(10, 10));
        // or use the "past tense" version of the modifier, which makes a clone itself
        setPosition(someObject.getPosition().added(10, 10));

Now launch the game again and have fun controlling the ship. See you in an hour or two...

Creating asteroids

Ok, so we have a flying spaceship, but that's not much of a game. Now we're going to add big asteroids, which will act as obstacles and targets for the player.

Create a new physical object class, Asteroid:

package asteroids;

import org.openwar.victory.Game;
import org.openwar.victory.extra.PhysicalObject;
import org.openwar.victory.extra.ResourceManager;
import org.openwar.victory.geom.Circle;
import org.openwar.victory.geom.Point;
import org.openwar.victory.math.Vector2;

/**
 * Asteroid obstacle for the player.
 * @author Bart van Heukelom
 */
public class Asteroid extends PhysicalObject {

    private double scale;
    
    /**
     * Create a new asteroid.
     * @param pos It's starting position.
     * @param speed It's speed.
     * @param theScale The scale. Applies to graphics and damage.
     */
    public Asteroid(final Point pos, final Vector2 speed, final double theScale) {
        super(pos, speed);
        setSprite(ResourceManager.get().getSprite("asteroid"));
        scale = theScale;
        setSpriteScale(new Vector2(scale).multiply(0.8 + 0.4 * Math.random()));
        setRotationSpeed((Math.random() * 0.2 - 0.1) / scale);
    }
    
}

Again, a step-by-step explanation. In our constructor, we first call the super constructor which will create the physical object at a specified position and give it an initial speed:

super(pos, speed);

Then, we set the sprite:

setSprite(ResourceManager.get().getSprite("asteroid"));

Next we save the scale of the asteroid. We will use this scale to determine what size asteroids it is, since asteroids will be splitting into smaller bits, as described in the game design above. We will use 1.0 for large ones, 0.5 for the medium ones and 0.25 for the tiny ones. We also set the sprite scale accordingly, with some extra randomness.

scale = theScale;
setSpriteScale(new Vector2(scale).multiply(0.8 + 0.4 * Math.random()));

Finally, we set a somewhat random rotational speed, which will make the asteroid rotate automatically.

setRotationSpeed((Math.random() * 0.2 - 0.1) / scale);

Making asteroids appear

Now that we have asteroids, we can make them appear in the game. For this purpose, we can override the method stepInit in Game. This method is called at the beginning of every game step. Implement it in AsteroidsGame:

    /**
     * Create asteroids every step.
     * @param currentStep The current step number.
     */
    @Override
    protected void stepInit(final int currentStep) {
        
        if (currentStep % 150 == 0) {

            final Point pos;
            final int side = (int) (Math.random() * 4);

            switch(side) {
            
            // left
            case 0:
                pos = new Point(
                        -40,
                        Math.random() * getDisplay().getHeight()
                );
                break;
                
            // right
            case 1:
                pos = new Point(
                        getDisplay().getWidth() + 40,
                        Math.random() * getDisplay().getHeight()
                );
                break;
                
            // top
            case 2:
                pos = new Point(
                        getDisplay().getWidth() * Math.random(),
                        -40
                );
                break;
                
            // bottom
            case 3:
                pos = new Point(
                        getDisplay().getWidth() * Math.random(),
                        getDisplay().getHeight() + 40
                );
                break;
            
            default:
                    pos = new Point();

            }

            final Vector2 speed = Vector2.fromLengthDirection(
                    Math.random() + 2,
                    (Math.random() - 0.5) * Math.PI * 0.5
                    + pos.angleTo(getDisplay().getCenter())
            );

            final Asteroid a = new Asteroid(pos, speed, 1);
            addEntity(a);
            
        }
        
    }

First, we check the current step number, and only create new asteroids every 150th step (every 5 seconds).

if (currentStep % 150 == 0) {

Then we determine at which edge of the screen to spawn the new asteroid:

final int side = (int) (Math.random() * 4);

Using the result of that random generation, we determine the exact spawning position of the asteroid in a switch statement:

            switch(side) {
            
            // left
            case 0:
                pos = new Point(
                        -40,
                        Math.random() * getDisplay().getHeight()
                );
                break;
                
            // right
            case 1:
                pos = new Point(
                        getDisplay().getWidth() + 40,
                        Math.random() * getDisplay().getHeight()
                );
                break;
                
            // top
            case 2:
                pos = new Point(
                        getDisplay().getWidth() * Math.random(),
                        -40
                );
                break;
                
            // bottom
            case 3:
                pos = new Point(
                        getDisplay().getWidth() * Math.random(),
                        getDisplay().getHeight() + 40
                );
                break;
            
            default:
                    pos = new Point();

            }

Then we set the speed and direction. The asteroid will be more or less aimed at the center of the screen.

            final Vector2 speed = Vector2.fromLengthDirection(
                    Math.random() + 2,
                    (Math.random() - 0.5) * Math.PI * 0.5
                    + pos.angleTo(getDisplay().getCenter())
            );

Finally, we create the asteroid and add it to the game.

final Asteroid a = new Asteroid(pos, speed, 1);
addEntity(a);

Collission detection

Now let's do some collission detection between the ship and asteroids. First, we need to give the ship some kind of volume to collide with. For our purposes, a simple circle shape will work just fine. First, import Circle in Ship:

import org.openwar.victory.geom.Circle;

Then insert this line into the constructor:

setBounds(new Circle(20));

This will give the ship a circular boundary with a radius of 20 pixels.

Similarly, we need to give the asteroids some bounds as well. Import Circle again:

import org.openwar.victory.geom.Circle;

Then insert this into the constructor of Asteroid:

setBounds(new Circle(40 * scale));

Now we need to do the actual collission checking between asteroids and ship. We can either do this in the ship, or in every asteroid. In this situation we'll choose to do it in the asteroid, because there we can simply access the only ship, while in the ship we first need to compose a list of asteroids.

First, we need to make the ship accessible to asteroids, by adding a method to AsteroidsGame:

    /**
     * @return The player ship.
     */
    public Ship getPlayer() {
        return player;
    }

Also, let's "override" (note that you cannot really override static methods) the get method from Game, casting it's result to AsteroidsGame so we can directly use AsteroidsGame.get() in other objects. Add to AsteroidsGame:

    /**
     * @return The asteroids game.
     */
    public static AsteroidsGame get() {
        return (AsteroidsGame) Game.get();
    }

Now that we have access to the ship, override the step method in Asteroid.

    /** {@inheritDoc} */
    @Override
    public void step(final int frame) {
        if (collidesWith(AsteroidsGame.get().getPlayer())) {
            destroy();
        }
    }

If the asteroid collides with the ship, it will just be destroyed for now. Note that destroy isn't declared yet, so let's do that now:

    /**
     * Destroy this asteroid when it is hit by a bullet or player.
     */
    public void destroy() {
        if (scale > 0.25) {
            for (int i = 0; i < 3; i++) {
                final Asteroid a = new Asteroid(
                        getPosition(),
                        getSpeed().clone().rotate(i * Math.PI / 1.5),
                        scale / 2
                );
                Game.get().addEntity(a);
            }
        }
        remove();
    }

In this method, we create 3 new, smaller asteroids at the position of the destroyed asteroid, unless we're already tiny. Then, we mark the asteroid for removal.

You can now try the game again. Hit the asteroids with the ship, and they will split into smaller pieces.

Shooting with the ship

There isn't much to do for the player, if he can't shoot the asteroids and get points. To give the player something to shoot, let's create the class Bullet:

package asteroids;

import org.openwar.victory.extra.PhysicalObject;
import org.openwar.victory.extra.ResourceManager;
import org.openwar.victory.geom.Circle;
import org.openwar.victory.geom.Point;
import org.openwar.victory.math.Vector2;

/**
 * A simple bullet fired from a ship.
 * @author Bart van Heukelom
 */
public class Bullet extends PhysicalObject {

    private PhysicalObject shooter;

    /**
     * Create a new bullet.
     * @param pos The position to creat it at.
     * @param speed The speed it should move at.
     * @param theShooter The object which shot it.
     * @param rotation The rotation of the bullet.
     */
    public Bullet(final Point pos, final Vector2 speed, final PhysicalObject theShooter, final double rotation) {
        super(pos, speed);
        shooter = theShooter;
        setSprite(ResourceManager.get().getSprite("bullet"));
        setBounds(new Circle(2));
        setRotation(rotation);
    }
    
}

This class pretty much explains itself. You can see that we store the object which shot the bullet. That is so that when we hit something, we can check whether it's not ourselves. "But we already know it was shot by the player ship", you may say. That's true, but this way we keep the bullet class flexible so it can easily be used by other shooting objects in the future.

Now, on to making the bullet shootable. Since we already have a beginstep in Ship, let's just insert this bit of code at the end of that method:

        // shoot
        if (in.getKeyState(InputManager.VK_SPACE) == InputManager.KeyState.PRESSED) {
            final Bullet b = new Bullet(
                    getPosition(),
                    getSpeed().added(Vector2.fromLengthDirection(20, getRotation())),
                    this,
                    getRotation()
            );
            Game.get().addEntity(b);
        }

What does this do? First, it checks whether the space key is pressed (pressed, in this context, means that it is down, but was not in the previous step). If that is true, a new bullet is created at the position of the ship. This bullet is given a speed, moving with speed 10 in the direction the ship is facing. This speed is added to a clone of the ship's speed (if you don't understand why we need to clone that, read above), because that is just how physics works, and it would look strange otherwise. Then the bullet is added to the game.

Next step, checking collission between asteroids and bullets. In Asteroid, import Entity first:

import org.openwar.victory.Entity;

Then insert the collission detection at the end of step:

        for (Entity e : Game.get().getEntities()) {
            if (e.getClass() == Bullet.class) {
                if (collidesWith((Bullet) e)) {
                    destroy();
                    e.remove();
                }
            }
        }

How does this work? First, we get a list of all entities in the game. Note that this is the actual internal list maintained by the game, and you may not modify it. For each entity, we check whether it is a bullet. If so, we check whether it collides with the asteroid. If so, the asteroid is destroyed and the bullet removed. It's as simple as that.

Some performance enhancements

You may have noticed (depending on the power of your computer) that if you play the game for a while, it will get gradually slower. This is because objects that move out of the screen (such as undestroyed asteroids and missed bullets), even though they are invisible, are still there drinking CPU time. Time to fix that.

In Asteroid, add this to the beginning of step:

        if (!Game.get().getDisplay().getBounds().expanded(50, 50)
                .contains(getPosition())) {
            remove();
        }

Then, add this to Bullet:

    /**
     * Middle step. Check for out of bounds.
     * @param frame The current step.
     */
    @Override
    public void step(final int frame) {
        if (!Game.get().getDisplay().getBounds().expanded(4, 4)
                .contains(getPosition())) {
            remove();
        }
    }

Scoring points

The next step: recording points. We're going to give the player points for shooting asteroids, and need to keep track of them somewhere. There are several places we can do that, but let's store them inside Ship for now. Add this:

    private int score;

    /**
     * @return The player score.
     */
    public int getScore() {
        return score;
    }
    
    /**
     * Add a number of points to the score.
     * @param add The number of points to add.
     */
    public void addScore(final int add) {
        score += add;
        Game.get().getDisplay().setWindowTitle("Score: " + score); // this is until we have a HUD, remove later
    }

And there, we have a place to record points. Now on to scoring them. Let's say we give 1 point for big asteroids, 2 for medium ones and 4 for the very little ones, so the harder they are to hit, the more points they are worth.

In Asteroid.step, there is this code:

                if (collidesWith((Bullet) e)) {
                    destroy();
                    e.remove();
                }

Change that to:

                if (collidesWith((Bullet) e)) {
                    AsteroidsGame.get().getPlayer().addScore((int) (1 / scale));
                    destroy();
                    e.remove();
                }

Now you can play and get some points. Can you beat 1000?

Damage

Yes, you can! See, we have the ability to score points, but we cannot die yet, which mean the player can go on forever and score as many points as he wants to. Time to fix this!

First, we need to give our ship some health. 30 seems a fine number of hitpoints. Add to Ship:

    private int health = 30;
    
    /**
     * @return Player health.
     */
    public int getHealth() {
        return health;
    }

    /**
     * Deal the ship some damage.
     * @param damage The damage to deal.
     */
    public void damage(final int damage) {
        health -= damage;
    }

Now that the ship has hitpoints, we need to let the asteroids decrease them. In Asteroid.step, find:

        if (collidesWith(AsteroidsGame.get().getPlayer())) {
            destroy();
        }

Replace:

        if (collidesWith(AsteroidsGame.get().getPlayer())) {
            AsteroidsGame.get().getPlayer().damage((int) (4 * scale));
            destroy();
        }

So that means 4 damage for large asteroids, 2 for the medium ones and 1 for the small ones.

You can play now, but you'll notice that hitting a big asteroid will almost kill you, because you collide with and get damaged by the smaller asteroids that come out of it as well. We can fix this by adding an invincible mode to the player. In Ship, add:

    private boolean invincible = false;
    private int restoreStep = 0;

    /**
     * Set the invincibility of the player.
     * @param isInvincible The new invincibility state.
     */
    public void setInvincible(final boolean isInvincible) {
        invincible = isInvincible;
        if (invincible) {
            setAlpha(0.5);
            restoreStep = Game.get().getStep() + 90;
        } else {
            setAlpha(1.0);
        }
    }
    
    /**
     * @return Whether the player is invincible.
     */
    public boolean isInvincible() {
        return invincible;
    }

This introduces two new fields: the invincibility state, and the step number at which the ship should be restored to normal. The method setInvincible is used to change the state. It takes care of changing the transparency of the ship (which we use as visual indication of invincibility) and setting the restore step to the current step + 90, or 3 seconds from now.

This restoration does not happen automagically now, so insert this at the beginning of step:

        if (step == restoreStep) {
            setInvincible(false);
        }

Now we must make it so that the ship becomes invincible if it's damaged. Therefore, edit damage:

    /**
     * Deal the ship some damage.
     * @param damage The damage to deal.
     */
    public void damage(final int damage) {
        health -= damage;
        setInvincible(true);
    }

And finally, we need asteroids not to break or damage the player if he is invincible, so find this code in Asteroid:

        if (collidesWith(AsteroidsGame.get().getPlayer())) {
            AsteroidsGame.get().getPlayer().damage((int) (4 * scale));
            destroy();
        }

And replace it with:

        if (!AsteroidsGame.get().getPlayer().isInvincible()
                && collidesWith(AsteroidsGame.get().getPlayer())) {
            AsteroidsGame.get().getPlayer().damage((int) (4 * scale));
            destroy();
        }

As a final touch, for fairness, we disable the player from shooting when he is invincible. In Ship.beginstep, find:

if (in.getKeyState(InputManager.VK_SPACE) == InputManager.KeyState.PRESSED) {

Replace with:

if (!invincible && in.getKeyState(InputManager.VK_SPACE) == InputManager.KeyState.PRESSED) {

HUD

At this point we really need to start displaying some information to the player, so let's create the HUD (Heads Up Display) next. Create a class HUD:

package asteroids;

import org.openwar.victory.graphics.Colour;
import org.openwar.victory.graphics.DrawableAdapter;
import org.openwar.victory.graphics.GC;

/**
 * Heads Up Display for asteroids.
 * @author Bart van Heukelom
 */
public class HUD extends DrawableAdapter {

    /**
     * Create HUD.
     */
    public HUD() {
        setDepth(-100);
    }

    /**
     * Draw the HUD.
     * @param context The graphics context.
     */
    @Override
    public void draw(final GC context) {

        final Ship player = AsteroidsGame.get().getPlayer();
        
        // construct info text
        final String text = String.format(
                "Score: %d\n"
                + "Health: %d HP",
                
                player.getScore(),
                player.getHealth()
        );

        // set the drawing colour to a predefined white
        context.setColour(Colour.WHITE);
        
        // draw the info text
        context.drawString(text, 10, 20);

    }

}

This class extends DrawableAdapter. This is a convenience class for implementing the interface Drawable. Drawable objects can be inserted into the rendering loop to draw something. They have a depth, which determines in which order objects are drawn (the higher the depth, the earlier they are drawn, hence appear underneath other objects). In the constructor of HUD, we set it's depth to -100 so it appears above anything else.

The draw method is passed a graphics context as a parameter. A graphics context (GC in short) is the object you use to do direct drawing operations. The GC in this case can be used to draw on the screen, but you can also, for instance, use them to edit images as runtime (depending on the display implementation you use, but it is supported in the default JDisplay). We draw a very simple HUD by putting all info in a string and drawing this string on the screen.

Now, we must create this HUD and insert it into the render loop, so add to the end of AsteroidsGame.init:

        // create info HUD
        getDisplay().addDrawable(new HUD());

Death

More coming soon...