Zombie splat – creating random zombies


Code for this tutorial is on this git branch:

git clone -b zombies https://dglencross@bitbucket.org/dglencross/zombiegametutorial.git

Have a look here for help setting it up


Zombies

If you have already downloaded and run the game, you will see zombies running towards the main character. In this tutorial, we will describe how these zombies are created and how they choose what to do. It is fairly straightforward – but leaves a lot of room for more complex behaviour, if you wish to adapt it.

Zombie behaviour

  • Stand in a random position
  • If the player comes within a certain radius, run towards him
  • Keep moving towards the player in a straight line, unless the player goes out of range
  • If there is an obstacle in the way, stop
  • If we have reached the player, kill him

If you have followed the tutorial all the way through, you may remember that the player character originally moved in straight lines, just walking towards the clicked point. This is the behaviour that I have implemented for the zombies, with an additional bit of logic to check that the zombie is not about to walk into an obstacle (in our case, water).

Zombie.java

First off, when we create the zombie we stick him in a random start position using the same helper method as we do for the player – however, we make sure the zombie does not spawn close to the player. This is to stop zombies attacking the player as soon as the game launches – it is better to let the player make some movement first.

while(pos.dst(screen.gameController.getPlayer().getPos()) < attackRadius) {
            pos = screen.getWorld().getRandomStartPosition();
        }

As with the player character, the core of the class is in its update method:

public boolean update(Vector2 playerPos) {
    if (hasBeenShot) {
        return false;
    }

    if (withinAttackDistance(playerPos)) {
        updateOrientation(playerPos);
        moveTowardsPlayer(playerPos);
    }
    return touchingPlayer(playerPos);
}

Firstly, we check if the zombie has already been shot – if so, do nothing.

If the zombie is close enough to the player to attack (I have hard-coded the radius – the larger you make it, the harder the game is), it will rotate to look at him, and move towards him.

For an added creep-factor, you can make the zombies update orientation regardless of whether or not the player is in view – it adds a certain level of creepiness to know that they are all pointed at the player, all the time.

private void updateOrientation(Vector2 playerPos) {
    float angle = (float) Math.toDegrees(Math.atan2(playerPos.x - pos.x, playerPos.y - pos.y));
    this.orientation = - angle;
}

The updateOrientation logic should look familiar to you if you have followed the tutorial – it is very similar to the way it works for the player character.

private void moveTowardsPlayer(Vector2 playerPos) {
    diff = playerPos.cpy();
    diff.sub(pos);

    diff.setLength(ZOMBIE_SPEED);

    Vector2 nextPos = pos.cpy().add(diff);

    // attempt to stop zombies crossing water
    Pair nextPair = screen.getWorld().getPair((int)nextPos.x, (int)nextPos.y);
    if (nextPair.isLand) {
        pos.set(pos).add(diff);
    }

}

Again, for moving towards the player we use vector maths to calculate the next step. We only update the zombie’s position if he is moving to a land tile – if not, he gets stuck. This does make the zombie pretty stupid – you can try changing this if you want.

The ZOMBIE_SPEED is hard-coded, and if you want a more challenging game, try increasing it – it definitely makes the game more fun.

private boolean touchingPlayer(Vector2 playerPos) {
    return pos.dst(playerPos) < 0.5;
}

The last step is to check if the zombie is touching the player, and return that value – if it is, we wanted to take some action. In this version, we kill the player (and the zombie). In future, we might allow (for example) three zombies to hit the player before he dies.

Creating the zombies

In GameController, during the creation of a new game, we allocate our zombies.

private void createZombies() {
   zombies = new ArrayList<Zombie>();
   for(int i=0; i < startingZombies; i++) {
      zombies.add(new Zombie(screen));
      zombiesAlive++;
   }
}

Fiddle with the startingZombies variable if you want to try the game out with more or fewer zombies.

No path-finding?

Instead of having the zombies run in a straight line, you can implement Jump Point Search for them. However, just in the interests of running quickly on a mobile phone, I have decided to make my zombies do no path-finding.

Conclusion

We have seen how to create some simple zombies and imbue them with basic behaviour. Next up, we will learn how to shoot them.



If you like these tutorials, or want to see a game that was written in much the same way, please download Dinowar from the Google Play Store. It costs nothing and is ad-free.

Zombie Splat – a playable game


Code for this tutorial is on this git branch:

git clone -b zombies https://dglencross@bitbucket.org/dglencross/zombiegametutorial.git

Have a look here for help setting it up


Zombie Splat

For the first time, the code for this tutorial features the outline of an actual playable game. Its working title is Zombie Splat, due to lack of imagination on my part. Here’s a little sample of it:

What I’ve added since the last tutorial:

  • Zombies who attack when you get close enough to them
  • The ability to shoot the zombies by tapping on them (or clicking, as in the above video)
  • A bit of text to display how many zombies are remaining to shoot
  • A ‘You win/You lose’ message depending on the outcome of the game

These are the key features that we’ve added in this tutorial series so far:

Feel free to download the code for the above video, and in the next few posts I’ll go into some depth explaining the code that has been added to enable these new features.



If you like these tutorials, or want to see a game that was written in much the same way, please download Dinowar from the Google Play Store. It costs nothing and is ad-free.

Making the camera follow the character


Code for this tutorial is on this git branch:

git clone -b sticky_camera https://dglencross@bitbucket.org/dglencross/zombiegametutorial.git

Have a look here for help setting it up


Keeping the camera centred on the player – intelligently

This tutorial can be used independently to the ongoing series, as this is probably a useful bit of code for a lot of top-down 2D games.

In previous tutorials, the camera has been following the player around the screen, always keeping the camera centred over the player. However, when the player moves towards the edge of the game world, the edge of the map and beyond is visible. This looks pretty awful.

What we want is for the camera to follow the player around, but never get to the edge of the map. So when they player gets within a certain distance of the edge of the map, the camera stops following.

Code

float x = getPlayer().getX();
float y = getPlayer().getY();
// We divide the viewPort sizes by 2, because the player
// is always in the middle of the screen - with half of the
// screen above it, half below, half to the left side of it,
// half to the right

// You can extract these out (as they are in the full project)
// These are always the same, so only need to be calculated once
float halfViewPortWidth = camera.viewportWidth / 2;
float halfViewPortHeight = camera.viewportHeight / 2;

if (x < halfViewPortWidth) {
    camera.position.x = halfViewPortWidth;
} else if (x > ZombieGame.getWorldWidth() - halfViewPortWidth) {
    camera.position.x = ZombieGame.getWorldWidth() - halfViewPortWidth;
} else {
    camera.position.x = x;
}

if (y < halfViewPortHeight) {
    camera.position.y = halfViewPortHeight;
} else if (y > ZombieGame.getWorldHeight() - halfViewPortHeight) {
    camera.position.y = ZombieGame.getWorldHeight() - halfViewPortHeight;
} else {
    camera.position.y = y;
}

When the player is in the middle of the map, the camera will track normally. However, if the player steps outside the artificial boundaries then the camera will stay in a position in which it can never see off the map.

In the above code, we check if the player is too far to the left of the map, or too far to the right. If so, we limit where the camera can be on the X-axis. Otherwise, we just set the camera’s X parameter to match that of the player.

Similarly, we check if the player is too far towards the top or bottom of the map, and do the same.

In this way, the camera can track along one axis but not the other, or both at the same time – which will happen when the player is in a corner.



If you like these tutorials, or want to see a game that was written in much the same way, please download Dinowar from the Google Play Store. It costs nothing and is ad-free.