loading pixels tweaking margins squinting eyes edging borders shifting elements correcting my posture

Recreating Games in Code (P5.js) – Tower Defence

18 July 2024 · Recreates

Continuing my personal development into game design, I’m curious to learn the math and logic (some could say magic) behind how game mechanics work on a technical level, and what makes them fun and addicting. This will be a series of articles that explore vertical-slices to feature these game mechanics in context, all while learning how it’s done and sharing my findings.

Tower Defence is a strategy game where the goal is to defend a player’s territories or possessions by obstructing the enemy attackers or by stopping enemies from reaching the exits, usually achieved by placing defensive buildings along their path of attack.

Wikipedia

So, with any project, what do I need to define a game similar to a typical tower defence?

  • Grid Object
  • Cell Object
  • Path Object
  • Enemy Object
  • Tower Object

Defining the Grid

The tower defence game I’m making uses grid-based building. I know at this stage of development I’m not looking for free building placement, so I’m safe to assume everything will be locked to the grid. By defining this, I can freely define my grid to a 1-integer scale (x: 0, 1, 2, 3) and multiply each point by some factor, let’s say 50, to make it visible to the player (x: 0, 50, 100, 150).

Every building, cell, and path the enemies will eventually travel along uses this same factor, so visually and logically everything is “snapped” to the grid. I can refine visual art later down the line to adjust if needed, but behind the scenes every object takes up the same equal amount of space.

An image showing the grid of playable area in my Tower Defence recreation project. Each cell spot is shown to be of equal size.

Grid Cells

Each spot on the grid is a cell, and every cell needed to know its own index, position, and the type of cell it was. If it’s an empty cell, it needs to know things can be placed there, and if it’s currently being hovered by the mouse, it needed to know that too to eventually handling building placement which in essence is just simple variable changing.

I first started by using a multi-dimensional array of indexes, positions, and text values (which I used for the type of cell it was) but I hit a brick wall when development scaled. I realised an object was the superior choice; I could have an array of objects, each in control of their own variables and functions, and only taking up 1 spot each on a global array.

class Grid {
  ...
  this.cols = 10;
  this.rows = 5;
  this.cellsize = 50;
  this.cells = [];
  
  //Fill all cells with objects
  for(let x = 0; x < this.cols; x++) {
    for(let y = 0; y < this.rows; y++) {
      let index = y * this.cols + x;
      this.cells[i] = new Cell(x, y, this.cellsize, ...);
    }
  }
  ...
}
class Cell {
  constructor(x, y, size, type, hovered) {
    this.x = x;
    this.y = y;
    this.size = size;
    this.type = type;
    this.hovered = false;
  }
  ...
}

The Path and Random Walking Algorithm

I now needed to define a path through the grid. This should be generated on the fly, due to the nature of my game being responsive for all screen sizes, creating predefined "maps" wasn't going to cut it.

I first started by choosing a random cell down the left edge of the grid to be the start, and a random cell down the right edge to be the finish. I colour coded these Green and Red respectively.

Once I had these cells defined and validated my code to ensure there's only ever 1 of each, I began thinking about how I could have a path generate between the two cells. I somehow needed to iterate each cell until I reached the finish, or recursively kept trying again and again.

I created a recursive function that calls itself, either on the current cell (if we need to try again) or on the next cell (if we successfully can move ahead in that direction).

Function GeneratePath(cell) {
  For(every cell in grid) {
    If(we are the current cell) {
      Choose a random direction (up, right, down, left)
      Move 1 unit in that direction

      If(cell NOT valid):
        Return SAME cell (to try function again)
      Else:
        Return NEW cell (to run function again from new spot)
    }
  }
}

If(cell exists):
  //Recursion
  cell = GeneratePath(cell);
Else:
  //Initialise function to begin recursion
  cell = GeneratePath(startCell);

If we reach the finish, or the path can't solve, we also need to exit out of the recursion... I learned that the hard way when my browser crashed again and again. I did this by hard-coding in an attempts limit inside this function that reset every time the function successfully retrieved a new cell.

attempts = 0;
maxAttempts = 10;
Function ... {
  If(attempts < maxAttempts) {
    attempts++;

    If(found valid next cell) {
      attempts = 0; //reset
    }
  }
}
An animated GIF showcasing the recursion to solve a path from start to finish.
Side note: I'm not really going for efficiency here on this little project, and there's some strange fascination in watching paths generate. I'm sure there's better ways to do this that probably include backtracking instead of trying to generate a full path from the start again (and again, and again).

Enemies, Spawning and Path Following

Next up I need to create some enemies. These enemies will need some health, movement, and some visuals. To avoid getting bogged down in the aesthetics, I'm using an aggressive-looking sharp red triangle for my enemies. Perhaps in the future these may become other variously random shapes or image sprites, but this project is limited to only be a vertical-slice for now.

How do I get them to follow a path? Well, turns out it’s more complex than I first thought. I assumed I could use a distance function to find the closest path, and the next closest path point to head towards, but a tight cluster of path points had them going round in circles to the next closest point (whether the next sequential point in the path or not). Instead, as my path is an ordered array of points from start to end, I know I can iterate to the next path point using index+1. If there’s no index+1 in the array, we’ve reached the end of the path.

Once they reach the end, we can destroy them to clean up the code for performance. Just before doing this, we can trigger some sort of negative score or health penalty here to signal to the player "Hey, you're really bad at this!"

TURRETS!!!

I'm mostly skipping over gun and bullet firing mechanics, as well as health and hit-collision, as I previously explored these in Asteroids.

Now the fun begins. I need an object to shoot at incoming enemies, deplete their health, and ultimately destroy them before reaching the exit. Copying the same code from Asteroids, I have a Bullet, Enemy (previously named Asteroid) and Enemy Spawner class I can reuse.

class Bullet {
  Function hits(Enemy) {
    Enemy.health -= this.damage
  }
}

class Enemy {
  If(this.health < 0) {
    Dead = true
    //Negative player score/health
    //Remove me from the game world
  }
}

Now that functionality is in place, I need a turret object to look at and be able to fire autonomously at incoming enemies within a certain range. I can use a distance function when looping over all enemies to find the closest one, and if it's within that range, begin shooting at the enemies (current*) location.

*Current location only works if the bullet speed is great enough. Predicting the enemy's future location would certainly be better as they will be in that spot, not leaving that spot. Vertical-slice, remember!

Play the Game and Dig into the Code

I had a lot of fun creating this, and it's great fun to play even without the “game feel” that particles, screen shake and great audio provides. It could certainly be improved by adding score counters and various types of turrets and enemies, and I encourage you to dig around in the code, but this is where I'm leaving this project. My main takeaways from this project were recursion, path following, and toughening up my knowledge on those pesky Vector Math Operations that still catch me out ever now and again.

Let's talk - 3D rendered graphic, bubble text

15 years of experience in one inbox, ready for your message.

I'm a Kent-based freelance brand designer and website developer with 15 years of experience, bringing creativity and technical expertise to clients across the UK and beyond.