Design In-game Entities – The Strategy Pattern

In this part I will try to explain what I understand on good game design elements. I will use droids in the examples and I will script a basic fight simulator to see how they behave.

The problem:

I command a single robot and I want to obliterate my enemies. To face the same type of enemy all over again is boring. I need new challenges and this means new types of enemies. For example in the first level I want only to practice my target. So I need a pretty dumb enemy that does not do much but takes the shots. After I mastered that skill (shooting a helpless droid), I need a bit of a challenge and I want the enemy droid to fight back, but because I am still a beginner I don’t want to die quickly so I need weak droids. After I am over with them I want a tougher challenge. I need better and stronger droids. Not just stronger, but different in behaviour as well as it can get boring killing the same type of enemy over and over again.

The obvious solution:

Create 3 classes for the 3 types of enemy droids. To keep it simple, each droid has 2 abilities: move and attack. It makes sense to create a Droid interface with these two methods and have each droid implement them.

They can move and shoot. Well, not all of them but we can provide an empty implementation for the ones that do nothing.

The droid types:

Looking at the 3 types we can implement the following simple class diagram:

The Obvious Solution

The Obvious Solution

The interface has 3 simple methods which the droids need to implement:


public interface Droid {

 

    // display some info of the droid

    public void display();

 

    // move the droid

    public void move(int x, int y);

 

    // attack position

    public void shoot(int x, int y);

}

The 3 classes are as follow:

DecoyDroid.java


public class DecoyDroid implements Droid {

 

    @Override

    public void display() {

        System.out.println("I am a DECOY droid");

    }

 

    @Override

    public void move(int x, int y) {

        System.out.println("Can't move.");

    }

 

    @Override

    public void shoot(int x, int y) {

        System.out.println("Have no weapon.");

    }

}

ScoutDroid.java


public class ScoutDroid implements Droid {

 

    private float damage = 0.5f;

 

    @Override

    public void display() {

        System.out.println("I am a scout droid");

    }

 

    @Override

    public void move(int x, int y) {

        System.out.println("Moving QUICKLY to: " + x + "," + y + ".");

    }

 

    @Override

    public void shoot(int x, int y) {

        System.out.println("Light Laser Canon targeting: " + x + "," + y

                + ". Damage: " + damage);

    }

}

AssaultDroid.java


public class AssaultDroid implements Droid {

 

    private float   damage = 2.5f;

    private boolean loaded = true;

 

    @Override

    public void display() {

        System.out.println("I am an ASSAULT droid");

    }

 

    @Override

    public void move(int x, int y) {

        System.out.println("Moving SLOWLY to: " + x + "," + y + ".");

    }

 

    @Override

    public void shoot(int x, int y) {

        if (loaded) {

            System.out.println("Heavy laser targeting: " + x + "," + y

                    + ". Damage: " + damage);

            loaded = false;

        } else {

            System.out.println("Reloading...");

            loaded = true;

        }

    }

}

Both ScoutDroid and AssaultDroid have the argument damage. This holds the value of the damage inflicted by them.

To give the AssaultDroid a heavy weapon with a slow reload time we added the loaded variable. This way it takes the assault droid two turns to fire its weapon once.

I have created a simple simulator for the droids to take turns to move and shoot.

Run the simulator for this design:

BadDroidSimulator.java


public class BadDroidSimulator {

 

    public static void main(String[] args) {

        // for generating random numbers

        Random rand = new Random();

 

        Droid scout = new ScoutDroid();

        Droid assailant = new AssaultDroid();

        Droid decoy = new DecoyDroid();

 

        scout.display();

        assailant.display();

        decoy.display();

 

        // shoot-out - each droid fires once per turn

        for (int i = 1; i <= 5; i++) {

            System.out.println("\n<=== BEGIN TURN " + i + " ===>");

            scout.shoot(rand.nextInt(10), rand.nextInt(10));    // we assume this is an enemy position

            scout.move(rand.nextInt(10), rand.nextInt(10));

            System.out.println();

            assailant.shoot(rand.nextInt(10), rand.nextInt(10));

            assailant.move(rand.nextInt(10), rand.nextInt(10));

            System.out.println();

            decoy.shoot(rand.nextInt(10), rand.nextInt(10));

            decoy.move(rand.nextInt(10), rand.nextInt(10));

            System.out.println("<=== END TURN " + i + " ===>");

        }

    }

}

The result (console output) will look like this:


I am a scout droid

I am an ASSAULT droid

I am a DECOY droid

<=== BEGIN TURN 1 ===>

Light Laser Canon targeting: 9,0. Damage: 0.5

Moving QUICKLY to: 4,6.

 

Heavy laser targeting: 6,2. Damage: 2.5

Moving SLOWLY to: 9,1.

 

Have no weapon.

Can’t move.

<=== END TURN 1 ===>

 

<=== BEGIN TURN 2 ===>

Light Laser Canon targeting: 3,4. Damage: 0.5

Moving QUICKLY to: 6,5.

 

Reloading…

Moving SLOWLY to: 1,6.

 

Have no weapon.

Can’t move.

<=== END TURN 2 ===>

 

<=== BEGIN TURN 3 ===>

Light Laser Canon targeting: 6,7. Damage: 0.5

Moving QUICKLY to: 9,7.

 

Heavy laser targeting: 7,1. Damage: 2.5

Moving SLOWLY to: 2,0.

 

Have no weapon.

Can’t move.

<=== END TURN 3 ===>

 

<=== BEGIN TURN 4 ===>

Light Laser Canon targeting: 3,7. Damage: 0.5

Moving QUICKLY to: 1,4.

 

Reloading…

Moving SLOWLY to: 5,9.

 

Have no weapon.

Can’t move.

<=== END TURN 4 ===>

 

<=== BEGIN TURN 5 ===>

Light Laser Canon targeting: 0,8. Damage: 0.5

Moving QUICKLY to: 3,9.

 

Heavy laser targeting: 1,2. Damage: 2.5

Moving SLOWLY to: 3,2.

 

Have no weapon.

Can’t move.

<=== END TURN 5 ===>

Challenges to extend the design

The droids take turns to move and shoot. This is all good, but:

I am sure you have plenty ideas on how to enhance the gameplay and extend the world but the most obvious solution (described above) seems ill-suited for this. It requires new droid classes to be created and each droid type will implement its methods separately. Many of these methods are identical. The current design doesn’t allow you to change the droid’s internals at runtime without significant effort.

Here is one proposed solution: Composition and the Strategy Pattern.

Designing a Droid (properly)

A very simple droid consists of a weapon put on a chassis. The first design consisted of a “is a” type relationship. A ScoutDroid is a generic Droid with some peculiarities.

Composition is based on “has a” relationships. A Droid has a Chassis. A Droid has a Weapon. What type of components a droid has, determines its type.

Let’s decompose the Scout Droid for example.

The Scout Droid is a Droid which has a Light Laser Canon, has a set of Wheels to move. We want to make the scout move quickly with a light weapon.

The Assault Droid on the other hand is a Droid too but it *has a Heavy Laser Canon and it runs on (has) Tracks. This makes it extremely powerful but a bit slow.

Think from a factory perspective. How does a car production line work? You get the chassis with a specific place for the engine, wheels, drive-shaft, gear-box and so on.

All these components are produced separately. The teams that produce them have no idea of the other parts. They must fulfil one criteria: the gearbox must fit in perfectly in its place and connected up with the engine.. Different makes have different ways of doing this. The connector in this instance is the interface.

The engine has a similar story. If it hooks up nicely with the wheels and gearbox then it can be fitted. Its internal design, capacity, power, fuel consumption can be completely different. The engine is one of the car’s components.

So is our droid. But to keep it simple we have only 2 components. We need one generic droid that has all the wirings built so its components will be triggered by the droid through those interfaces. For example a droid needs to only pull the trigger of the weapon and doesn’t care what type of weapon it is as long as it has a trigger. The droid needs to understand the pullTrigger method and to do this it needs to be implemented, in order for us to give weapons to the droid to use.

The same thing with the changing of location. It needs to trigger the movement. The wheels or track or anti-gravity propulsion will take the droid there. The droid only needs to set the coordinates.

To fulfil this we need a class with implemented methods instead of an interface.

We create the abstract Droid class. We make it abstract because we actually implement the methods that trigger the weapon, and action the moving mechanism but we don’t have concrete weapons and movement mechanisms attached to the droid. The assembly of a concrete droid will be delegated to the type constructor along with the description method.


public abstract class Droid {

 

    protected Weapon    weapon;     // the weapon which will be used in fights

    protected Chassis   chassis;    // the chassis on which the droid is placed

 

    public void moveToPosition(int x, int y) {

        System.out.print(id + " > " );

        chassis.moveTo(x, y);

    }

 

    /**

     *  Engages the position on the screen whether it is occupied by

     *  an enemy or not. Each strategy should decide how to do it.

     */

    public void attackPosition(int x, int y) {

        System.out.print(id + " > ");

        weapon.useWeapon(new Vector2f(x, y));

    }

 

    /**

     * Displays some info on the droid

     */

    public abstract void display();

}

If you examine the class you will see that the Droid has 3 methods from which 2 are implemented. It also has two components: Weapon and Chassis.

The components are interfaces so the droid does not know what it is doing when triggering the actions on them. It is all delegated to the implementation.

The interfaces are as follows:

Weapon.java


public interface Weapon {

    /**

     * The trigger to use the weapon.

     * @param target - where on the map is the target

     */

    public void useWeapon(Vector2f target);

 

    /**

     * Returns the description of the weapon

     */

    public String getDescription();

}

Chassis.java


public interface Chassis {

    /**

     * Delegates the movement to the supporting chassis and

     * tries to move the unit to x,y

     */

    public void moveTo(int x, int y);

 

    /**

     * Returns the description of the chassis

     */

    public String getDescription();

}

We will enhance our base Droid class. We will add setter and getter methods for both Weapon and Chassis. This will allow us to change the droid’s behaviour at runtime. This is what the strategy pattern is all about. A Droid has behaviours: it can use a weapon and it can move. These two strategies (behaviours) need to be implemented.

We also add an id which will be unique in our game for each droid instance. I use a very simple id generation strategy. I increment the nextId static field and append it to the concrete droid type prefix in the constructor for each type.

Here is the new Droid class:


public abstract class Droid {

 

    protected static int nextId = 0;    // the next available ID

 

    protected String    id;         // unique id

    protected Weapon    weapon;     // the weapon which will be used in fights

    protected Chassis   chassis;    // the chassis on which the droid is placed

 

    // the unique ID of the droid in the game

    public String getId() {

        return id;

    }

 

    public Weapon getWeapon() {

        return weapon;

    }

    public void setWeapon(Weapon weapon) {

        this.weapon = weapon;

    }

 

    public Chassis getChassis() {

        return chassis;

    }

    public void setChassis(Chassis chassis) {

        this.chassis = chassis;

    }

 

    public void moveToPosition(int x, int y) {

        System.out.print(id + " > " );

        chassis.moveTo(x, y);

    }

 

    /**

     *  Engages the position on the screen whether it is occupied by

     *  an enemy or not. Each strategy should decide how to do it.

     */

    public void attackPosition(int x, int y) {

        System.out.print(id + " > ");

        weapon.useWeapon(new Vector2f(x, y));

    }

 

    /**

     * Displays some info on the droid

     */

    public abstract void display();

}

Let’s build some weapons

NoWeapon.java


/**

 * This is a null object. A null object is a dummy that does nothing and it

 * is a mere place-holder and eliminates the need to check for null.

 * @author impaler

 *

 */

public class NoWeapon implements Weapon {

 

    @Override

    public void useWeapon(Vector2f target) {

        // We are doing nothing

        System.out.println("No weapon equipped!");

    }

 

    @Override

    public String getDescription() {

        return "Nothing";

    }

}

This is the null object. The class description should give you an idea what it is.

LightLaserCanon.java


/**

 * This is a light laser cannon whit a quick reload time and high accuracy

 *

 * @author impaler

 *

 */

public class LightLaserCanon implements Weapon {

 

    private float damage = 0.5f; // the damage inflicted

 

    @Override

    public void useWeapon(Vector2f target) {

        System.out.println("Shooting my laser canon to " + (int)target.getX() + ","

                + (int)target.getY() + ". Bang on! Done " + damage + " damage.");

    }

 

    @Override

    public String getDescription() {

        return "First generation laser canon. Street use only!";

    }

}

HeavyLaserCanon.java


/**

 * This is a heavy assault laser cannon with high accuracy but slow reload time.

 * @author impaler

 */

public class HeavyLaserCanon implements Weapon {

 

    private boolean loaded  = true; // after fire needs to be reloaded

    private float   damage  = 1.5f; // the damage is considerable

 

    @Override

    public void useWeapon(Vector2f target) {

        if (loaded) {

            // we fire the canon

            System.out.println("Eat this! Laser beam hit target (" + (int)target.getX() + "," + (int)target.getY() + ") and dealt " + damage + " damage.");

            // next time needs reloading

            loaded = false;

        } else {

            System.out.println("Darn! Out of ammo! Reloading...");

            loaded = true;

        }

    }

 

    @Override

    public String getDescription() {

        return "DASS-5000 - The ultimate in siege weaponry provided by Obviam Enterprises.";

    }

}

You might notice the Vector2f class. This is a very basic 2D vector class which currently holds the x and y coordinates. Nothing more. You can find it in the downloaded source.

Let’s build some chassis

The getDescription() method says what the chassis is like.

NoChassis.java – null object (see weapons)


public class NoChassis implements Chassis {

 

    @Override

    public void moveTo(int x, int y) {

        System.out.println("It's just a frame. Can't move.");

    }

 

    @Override

    public String getDescription() {

        return "It's just a frame.";

    }

}

SteelStand.java


public class SteelStand implements Chassis {

 

    @Override

    public void moveTo(int x, int y) {

        System.out.println("Can't move.");

    }

 

    @Override

    public String getDescription() {

        return "Unmovable reinforced steel stand ideal for turrets and defensive units.";

    }

}

Wheels.java


public class Wheels implements Chassis {

 

    @Override

    public void moveTo(int x, int y) {

        System.out.println("Speeding to " + x + "," + y + " on my wheels!");

    }

 

    @Override

    public String getDescription() {

        return "4 wheel drive, very fast on flat terrain but struggling through obstacles.";

    }

}

Track.java


public class Track implements Chassis {

 

    @Override

    public void moveTo(int x, int y) {

        System.out.println("Don't get in my way! Moving slowly to: " + x + "," + y + ".");

    }

 

    @Override

    public String getDescription() {

        return "Slow moving tracks but able to go through many obstacles.";

    }

}

Now we can assemble our droids

First let’s create a DecoyDroid. This droid will have no weapon and will be placed on a steel stand. It’s for our target practice, remember?

DecoyDroid.java


public class DecoyDroid extends Droid {

 

    public DecoyDroid() {

        id = "DCY-" + (++Droid.nextId);

        weapon = new NoWeapon();

        chassis = new SteelStand();

    }

 

    @Override

    public void display() {

        System.out.println("+--------------------------------------------------------------------------------------------+");

        System.out.println("| I am a DECOY droid.");

        System.out.println("|\tID: " + id);

        System.out.println("|\tWeapon: " + weapon.getDescription());

        System.out.println("|\tChassis: " + chassis.getDescription());

        System.out.println("+--------------------------------------------------------------------------------------------+");

    }

}

Examine the default constructor. It creates an id and assigns an instance of NoWeapon and SteelStand to the droid.

The display() method is more elaborate than before but just to describe the droid better. It makes use of the components’ descriptions too.

If you instantiate a DecoyDroid and call its display method you will get a nice description of it.


+——————————————————————————————–+

| I am a DECOY droid.

|   ID: DCY-3

|   Weapon: Nothing

|   Chassis: Unmovable reinforced steel stand ideal for turrets and defensive units.

+——————————————————————————————–+

Let’s build the rest of the types:

ScoutDroid.java


public class ScoutDroid extends Droid {

 

    public ScoutDroid() {

        id = "SCT-" + (++Droid.nextId);

        weapon = new LightLaserCanon();

        chassis = new Wheels();

    }

 

    @Override

    public void display() {

        System.out.println("+--------------------------------------------------------------------------------------------+");

        System.out.println("| I am a SCOUT droid.");

        System.out.println("|\tID: " + id);

        System.out.println("|\tWeapon: " + weapon.getDescription());

        System.out.println("|\tChassis: " + chassis.getDescription());

        System.out.println("+--------------------------------------------------------------------------------------------+");

    }

}

AssaultDroid.java


public class AssaultDroid extends Droid {

 

    public AssaultDroid() {

        id = "ASS-" + (++Droid.nextId);

        weapon = new HeavyLaserCanon();

        chassis = new Track();

    }

 

    @Override

    public void display() {

        System.out.println("+--------------------------------------------------------------------------------------------+");

        System.out.println("| I am an ASSAULT droid.");

        System.out.println("|\tID: " + id);

        System.out.println("|\tWeapon: " + weapon.getDescription());

        System.out.println("|\tChassis: " + chassis.getDescription());

        System.out.println("+--------------------------------------------------------------------------------------------+");

    }

}

You will notice that the only things needed to be implemented are the constructor – which adds the chassis and weapon – and the display() method.

The following diagram shows the new architecture:

A Better Solution

A Better Solution

Let’s create a test script for it. We’ll simulate 5 turns in which each droid will use its weapon and move to a random location. Check the behaviour of each weapon and you will notice that the heavy laser will fire once every 2 turns. To make it interesting, in turn 4 we give a HeavyLaserCanon to DecoyDroid. Look at how it changes the droid’s behaviour and it starts firing. This is a hybrid droid created on the fly at runtime.

The simulator code (DroidSimulator.java):


public class DroidSimulator {

 

    public static void main(String[] args) {

        // for generating random numbers

        Random rand = new Random();

 

        Droid scout = new ScoutDroid();

        Droid assailant = new AssaultDroid();

        Droid decoy = new DecoyDroid();

 

        scout.display();

        assailant.display();

        decoy.display();

 

        // shoot-out - each droid fires once per turn

        for (int i = 1; i <= 5; i++) {

            System.out.println("\n<=== BEGIN TURN " + i + " ===>");

            // in turn 3 decoy droid is given an assault canon

            if (i == 4) {

                decoy.setWeapon(new HeavyLaserCanon());

                System.out.println("* " + decoy.getId() + " acquired " + decoy.getWeapon().getDescription() + "\n");

            }

            scout.attackPosition(rand.nextInt(10), rand.nextInt(10));   // we assume this is an enemy position

            scout.moveToPosition(rand.nextInt(10), rand.nextInt(10));

            System.out.println();

            assailant.attackPosition(rand.nextInt(10), rand.nextInt(10));

            assailant.moveToPosition(rand.nextInt(10), rand.nextInt(10));

            System.out.println();

            decoy.attackPosition(rand.nextInt(10), rand.nextInt(10));

            decoy.moveToPosition(rand.nextInt(10), rand.nextInt(10));

            System.out.println("<=== END TURN " + i + " ===>");

        }

    }

}

The output:


+——————————————————————————————–+

| I am a SCOUT droid.

|   ID: SCT-1

|   Weapon: First generation laser canon. Street use only!

|   Chassis: 4 wheel drive, very fast on flat terrain but struggling through obstacles.

+——————————————————————————————–+

+——————————————————————————————–+

| I am an ASSAULT droid.

|   ID: ASS-2

|   Weapon: DASS-5000 – The ultimate in siege weaponry provided by Obviam Enterprises.

|   Chassis: Slow moving tracks but able to go through many obstacles.

+——————————————————————————————–+

+——————————————————————————————–+

| I am a DECOY droid.

|   ID: DCY-3

|   Weapon: Nothing

|   Chassis: Unmovable reinforced steel stand ideal for turrets and defensive units.

+——————————————————————————————–+

<=== BEGIN TURN 1 ===>

SCT-1 > Shooting my laser canon to 0,3. Bang on! Done 0.5 damage.

SCT-1 > Speeding to 0,2 on my wheels!

 

ASS-2 > Eat this! Laser beam hit target (3,4) and dealt 1.5 damage.

ASS-2 > Don’t get in my way! Moving slowly to: 3,8.

 

DCY-3 > No weapon equipped!

DCY-3 > Can’t move.

<=== END TURN 1 ===>

 

<=== BEGIN TURN 2 ===>

SCT-1 > Shooting my laser canon to 4,0. Bang on! Done 0.5 damage.

SCT-1 > Speeding to 5,0 on my wheels!

 

ASS-2 > Darn! Out of ammo! Reloading…

ASS-2 > Don’t get in my way! Moving slowly to: 1,6.

 

DCY-3 > No weapon equipped!

DCY-3 > Can’t move.

<=== END TURN 2 ===>

 

<=== BEGIN TURN 3 ===>

SCT-1 > Shooting my laser canon to 3,0. Bang on! Done 0.5 damage.

SCT-1 > Speeding to 0,6 on my wheels!

 

ASS-2 > Eat this! Laser beam hit target (9,1) and dealt 1.5 damage.

ASS-2 > Don’t get in my way! Moving slowly to: 8,0.

 

DCY-3 > No weapon equipped!

DCY-3 > Can’t move.

<=== END TURN 3 ===>

 

<=== BEGIN TURN 4 ===>

* DCY-3 acquired DASS-5000 – The ultimate in siege weaponry provided by Obviam Enterprises.

 

SCT-1 > Shooting my laser canon to 8,6. Bang on! Done 0.5 damage.

SCT-1 > Speeding to 2,3 on my wheels!

 

ASS-2 > Darn! Out of ammo! Reloading…

ASS-2 > Don’t get in my way! Moving slowly to: 0,6.

 

DCY-3 > Eat this! Laser beam hit target (9,4) and dealt 1.5 damage.

DCY-3 > Can’t move.

<=== END TURN 4 ===>

 

<=== BEGIN TURN 5 ===>

SCT-1 > Shooting my laser canon to 1,7. Bang on! Done 0.5 damage.

SCT-1 > Speeding to 1,9 on my wheels!

 

ASS-2 > Eat this! Laser beam hit target (1,4) and dealt 1.5 damage.

ASS-2 > Don’t get in my way! Moving slowly to: 3,6.

 

DCY-3 > Darn! Out of ammo! Reloading…

DCY-3 > Can’t move.

<=== END TURN 5 ===>

Note in turn 4 how DecoyDroid got the new weapon and changed its behaviour. Now you should understand the null object pattern as well.

As it stands now, it is easy to create new weapons, chassis and droids by keeping the code to a minimum. Always favour composition over inheritance in these situations. You can create a very elaborate droid, with shields, sensors, an array of weapons and also an AI component which decides what weapons to use according to the situation. If you have noticed, I used the term “use” instead of fire, because a weapon can be a melee one too and not necessarily a ranged weapon.

The downloadable project contains only the strategy pattern implementation. The simple inheritance approach is left out. Play with it and get the grips of this useful pattern as I will use it extensively and it is considered good design. It is a simple Java application, it is not Android enabled.

Download the source here (TBD).

In the following parts I’ll try to add some AI capabilities and how to compose the final droid from images of its components.