Game Programming Adventures - BOX AVOIDER

A crash course introduction to game programming by Richard Marks.

Last Updated November 25, 2017

Creating The Box Avoider Project

GP Course Admin Tools - The Template Code

All projects in this course will be started from the template code. In order to obtain the template code, you will need the admin tools developed for generating a course project.

First clone the tools repository

Now use the create-project admin tool to create the box-avoider project.

If for whatever reason you are unable to run the admin tools on your machine (not tested in Windows for example), then you will need to do the following steps manually.

Now you are ready to continue.

Template Code Examined

First let’s take a quick look at index.html

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>box-avoider</title>
    <link rel="stylesheet" href="style.css">
  </head>
  <body>
    <div class='game'>
      <h1 class='game-title-text'>box-avoider</h1>
      <canvas
        width='640'
        height='400'
        class='game-canvas'></canvas>
      <h3 class='game-credits-text'>by Richard Marks</h3>
    </div>
    <script src="main.js"></script>
  </body>
</html>

This file is what will be loaded by the browser to present your game. We can see looking at this file, that we are depending on two other files style.css (makes our presentation of the game pretty) and main.js (our game code)

Really this is some very basic HTML5 that shows header text, footer text, and a canvas element where our game will be rendered.

You don’t need all of this for your own games, but for my course projects to be consistent, I decided to fancy the presentation up a little.

The bare bones you could use is shown below for your own convenience.

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>box-avoider</title>
  </head>
  <body>
    <canvas
      width='640'
      height='400'
      class='game-canvas'></canvas>
    <script src="main.js"></script>
  </body>
</html>

Next we will look at the style.css file.

@import url('https://fonts.googleapis.com/css?family=Kelly+Slab');

* {
  box-sizing: border-box;
  font-family: 'Kelly Slab', cursive;
  image-rendering: pixelated;
}

body {
  margin: 0;
  padding: 0;
  height: 100vh;
  background: #303030;
  color: lime;
  image-rendering: pixelated;
  display: flex;
  justify-content: center;
  align-items: center;
}

.game {
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  box-shadow: 0 0 16px black;
  border-radius: 16px;
  padding: 32px;
}

.game-title-text {
  margin: 0;
}

.game-canvas {

}

.game-credits-text {
  margin: 0;
}

I have a few flexbox containers to center things on the page and minimal styling for the header and footer text.

The only real thing to note here is the use of the Kelly Slab font from Google Fonts. Feel free to change this to any other google font you like.

Let’s take a look at the main.js file now.

First a couple constants to define the width and height of our game screen

const SCREEN_WIDTH = 640
const SCREEN_HEIGHT = 400

Next we have the game object. This is where the game code will exist. There are 3 methods of importance on the object.

The init method is called before the main game loop starts. This is where you should construct any variables your game needs, like score, player, etc…

The update method is called every time the main game loop executes. This is roughly 60 times a second. The deltaTime argument to the method is a numerical value of the amount of time that has elapsed since the last update. This is used to obtain a smooth movement of objects no matter the framerate of your game.

The render method is where your game is rendered to the HTMLCanvasElement. The renderingContext argument to the method is a reference to the CanvasRenderingContext2D which provides the api for drawing things on the canvas.

const game = {
  canvas: undefined,

  init () {

  },

  update (deltaTime) {

  },

  render (renderingContext) {
    const ctx = renderingContext

    ctx.clearRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT)

    ctx.fillText('This is my Game', SCREEN_WIDTH * 0.5, SCREEN_HEIGHT * 0.5)

    ctx.strokeRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT)
  }
}

Finally we have the boot function. This gets called when the DOMContentLoaded event is fired by the browser.

A reference to the canvas is defined, a reference to the rendering context is created, the text rendering is configured to use our custom font, centered on the X and Y and our rendering will all be lime colored by default.

Next we call the game.init() method, then construct the main game loop where we calculate the elapsed timing, update the game and render the game. We use requestAnimationFrame to repeated call the main game loop after it finishes being called.

We must call the main game loop once manually to kick things off.

const boot = () => {
  console.clear()
  game.canvas = document.querySelector('.game-canvas')
  const renderingContext = game.canvas.getContext('2d')
  renderingContext.textAlign = 'center'
  renderingContext.textBaseline = 'middle'
  renderingContext.font = '16px "Kelly Slab"'
  renderingContext.fillStyle = 'lime'
  renderingContext.strokeStyle = 'lime'

  game.init && game.init()

  let lastTime = Date.now()
  const mainLoop = elapsedTime => {
    const currentTime = Date.now()
    const deltaTime = (currentTime - lastTime) * 0.001
    lastTime = currentTime
    game.update && game.update(deltaTime)
    game.render && game.render(renderingContext)
    window.requestAnimationFrame(mainLoop)
  }

  mainLoop(0)
}

Lastly we hook up the event to call our boot function and we are done.

document.addEventListener('DOMContentLoaded', boot, false)

Serving

You need a web server to run your game. There are tons of options, but for this course, we will use the simple HTTP Server from the npm package http-server.

Install the server on your machine globally

Run the server in the project’s directory

Note: I have not tried this on Windows, so I do not know if it will work or not. If it does not, look for a free windows web server solution.

You should see the template code “game”. If you don’t see anything, or have any errors, try following the steps above, or reach out to me at ccpsceo@gmail.com

Where should we begin?

Keyboard Controls

The first task we are going to tackle is adding the ability to check the state of the keyboard controls we will use in the game.

Every key on your keyboard has a value that is associated with it called a keyCode

We are going to control our player with the arrow keys, so we will define a few constants to more easily refer to the keys.

const KEY_UP = 38
const KEY_DOWN = 40
const KEY_LEFT = 37
const KEY_RIGHT = 39

TIP: The way that I obtained the values to use was by running the following piece of code and writing down what was output to the browser’s devtools console when I pressed a key on my keyboard.

window.addEventListener('keyup', event => {
  event.preventDefault()
  console.log(event.keyCode)
}, true)

In the game.init method, we are going to define a new object called input which will hold our input device handling code.

game.input = {
  keys: {}
}

Well that was a bit easy, wasn’t it?

But that’s not everything. We need to actually handle the keyboard input events keydown and keyup and we do that next. This code is still within the game.init method.

When a key on the keyboard is pressed down, we set a true Boolean value in our input object’s keys object.

window.addEventListener('keydown', event => {
  game.input.keys[event.keyCode] = true
}, true)

When a key is released, we remove the Boolean that we had set when it was pressed down.

window.addEventListener('keyup', event => {
  delete game.input.keys[event.keyCode]
}, true)

This is all that we need at this point. We will add more functionality later as the project grows. For now, let us move on to adding our little player to the screen.

Adding the Player

We need to define some rules for our player object.

We will create a player object that will have it’s own update and render methods that will be called by the game methods respectively.

We need to track the position and size of the player, we will do this using x and y for the position, width and height for the size. We also need to keep half the size as well to avoid multiple divisions since we will treat the center of the player’s shape as it’s position. halfWidth and halfHeight will be used to find the upper-left corner of the player relative to it’s centralized position.

Here is the initial player object construction.

game.player = {
  x: 16,
  y: 16,
  width: 32,
  height: 32,
  halfWidth: 16,
  halfHeight: 16,
  
  update (deltaTime) {
  },
  
  render (renderingContext) {
  }
}

Let us now write the code that will render our player to the screen. This should go in the render method of the player object.

// save the current context transformation
renderingContext.save()

// translate the context coordinate system to place the player `x` and `y` at the origin of `0, 0`
renderingContext.translate(game.player.x, game.player.y)

// draw a filled rectangle with the upper-left corner offset
// to the left and above the origin by half the player's size
// and is the player's full size. This renders the rectangle
// such that the center is at the origin.
renderingContext.fillRect(
  -game.player.halfWidth,
  -game.player.halfHeight,
  game.player.width,
  game.player.height
)

// restore the previous conterxt transformation
renderingContext.restore()

Now we need to tell the game about our player object so we can see it rendered on the screen.

This code replaces the game.render method from the template code.

render (renderingContext) {
  renderingContext.clearRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT)
  game.player.render(renderingContext)
}

If you serve up the game in your browser, you should now see the player in all his glory rendered in the top left corner of the screen. He doesn’t do anything, so let’s take care of that now, and implement his controls.

Moving the Player

Let’s add some new rules to our player object.

We will track the speed at which the player moves with the speed property. And the velocity of the player with vx and vy.

Let’s see the full player object and add our new property.

game.player = {
  x: 16,
  y: 16,
  width: 32,
  height: 32,
  halfWidth: 16,
  halfHeight: 16,
  speed: 150, // we added this new property to track speed
  vx: 0, // we added this new property to track the X velocity
  vy: 0, // we added this new property to track the Y velocity
  
  update (deltaTime) {
  },
  
  render (renderingContext) {
    renderingContext.save()
    renderingContext.translate(game.player.x, game.player.y)
    renderingContext.fillRect(
      -game.player.halfWidth,
      -game.player.halfHeight,
      game.player.width,
      game.player.height
    )
    renderingContext.restore()
  }
}

The code we will now write should go in the game.player.update method.

// reset the velocity of the player
game.player.vx = 0
game.player.vy = 0

// if the up arrow key is being pressed, use an upwards velocity
// otherwise if the down arrow is being pressed, use a downwards velocity
if (game.input.keys[KEY_UP]) {
  game.player.vy = -1
} else if (game.input.keys[KEY_DOWN]) {
  game.player.vy = 1
}

// if the left arrow is being pressed, use a velocity to the left
// otherwise if the right arrow is being pressed, use a velocity to the right
if (game.input.keys[KEY_LEFT]) {
  game.player.vx = -1
} else if (game.input.keys[KEY_RIGHT]) {
  game.player.vx = 1
}

Now that we can detect if the arrow keys are being pressed, we need to apply the velocity of the player to it’s position, which causes the effect of movement.

if (game.player.vx !== 0 || game.player.vy  !== 0) {
  // get the length of the player's velocity vector
  // distance formula: d = √ x² + y²
  const x2 = game.player.vx * game.player.vx
  const y2 = game.player.vy * game.player.vy
  const magnitude = Math.sqrt(x2 + y2)

  // if there is a length (not zero) then normalize the vector
  // scale the normalized vector by the player's speed
  // and scale by the deltaTime to obtain the amount of units
  // that the player needs to move this update
  if (magnitude) {
    // normalize the velocity vector
    const vx = game.player.vx / magnitude
    const vy = game.player.vy / magnitude

    // scale by the player's speed
    const moveX = game.player.speed * vx
    const moveY = game.player.speed * vy

    // scale by the deltaTime and apply to the position
    game.player.x += moveX * deltaTime
    game.player.y += moveY * deltaTime
  }
}

If we plug this into the game by calling the update method of the player from the update method of the game, like this:

update (deltaTime) {
  game.player.update(deltaTime)
}

And if you serve up your game, you should be able to move the player around the screen. However you can move outside of the bounds of the screen, and your player gets lost somewhere in the game world. Let us now address clamping the player’s position.

After the last piece of code we wrote in the game.player.update method, we need to check if the player moves off the screen, if so, move it back.

// if the left edge of the player goes off the left edge of the screen
// then move the player so it's left edge is equal to the left edge of the screen
// otherwise if the right edge of the player goes off the right edge of the screen
// then move the player so it's right edge is equal to the right edge of the screen
if (game.player.x - game.player.halfWidth < 0) {
  game.player.x = game.player.halfWidth
} else if (game.player.x + game.player.halfWidth > SCREEN_WIDTH) {
  game.player.x = SCREEN_WIDTH - game.player.halfWidth
}

// if the top edge of the player goes off the top edge of the screen
// then move the player so it's top edge is equal to the top edge of the screen
// otherwise if the bottom edge of the player goes off the bottom edge of the screen
// then move the player so it's bottom edge is equal to the bottom edge of the screen
if (game.player.y - game.player.halfHeight < 0) {
  game.player.y = game.player.halfHeight
} else if (game.player.y + game.player.halfHeight > SCREEN_HEIGHT) {
  game.player.y = SCREEN_HEIGHT - game.player.halfHeight
}

And that wraps up the player for the time being. We will come back to the update method of the player later when we implement other features.

Adding the score timer

We gain score points as time elapses in the game. We will track these points with the score property on our game object. We will also create an entity that is responsible for updating the score based on time and rendering the score to the screen called scoreTimer also placed on the game object.

In the game.init method, we add the following code.

game.score = 0

game.scoreTimer = {
  time: 0,
  
  update (deltaTime) {
    // add the elapsed time this update to the accumulated time
    game.scoreTimer.time += deltaTime
    // when the accumulated time reaches a second, reset the time and increment the score
    if (game.scoreTimer.time >= 1.0) {
      game.scoreTimer.time -= 1.0
      game.score += 1
    }
  },
  
  render (renderingContext) {
    // render the score at the top center of the screen
    renderingContext.fillText(`SCORE: ${game.score}`, SCREEN_WIDTH * 0.5, 16)
  }
}

You need to plug this new object into the game now in the update and render methods of the game object, just as we did with the player. Hopefully you are seeing a pattern by now.

update (deltaTime) {
  game.player.update(deltaTime)
  game.scoreTimer.update(deltaTime)
},

render (renderingContext) {
  game.player.render(renderingContext)
  game.scoreTimer.render(renderingContext)
}

Now if you serve up your game in the browser, you will have a score counter that continuously grows as time goes on. It’s officially a game. It’s done. Ship it.

…Well no, not yet. It’s a bit boring right now. We have nothing to do but move around the screen.

Let’s fix this and add an evil red enemy box to our game!

Adding the first enemy

We are going to do the same thing as we have been doing, create an object, write some update and render code, plug it into the game. I will not repeat saying where the code should go after this point, it should be pretty obvious if you have been paying attention. Here is a reference for you use if you forget.


Q. Where do I put my code?!

A.


We will place all the enemy boxes inside an array property named boxes on the game object.

game.boxes = []

We are going to create two helper methods updateBoxes and renderBoxes that will iterate through the boxes array and call the update and render methods of each box.

game.updateBoxes = deltaTime => {
 game.boxes.forEach(box => box.update(deltaTime))
}

game.renderBoxes = renderingContext => {
  renderingContext.save()
  renderingContext.fillStyle = 'crimson'
  game.boxes.forEach(box => box.render(renderingContext))
  renderingContext.restore()
}

Call these new methods from the update and render methods of the game object to update and render the boxes respectively.

Nothing will change if you serve the game right now, because we have not put any boxes into the boxes array! Let’s add our first enemy box!

const box = {}
game.boxes.push(box)

This is fine for just one, but if we want multiple enemies, we shouldn’t repeat our code for each enemy… What to do you ask?

We will create a new method named spawnBox that will create a new box object that is all setup for us, and will call the new method to populate the boxes array with the return value of the spawnBox calls.

game.spawnBox = ({
  x,
  y,
  vx,
  vy,
  width,
  height
}) => {
const box = {
  x,
  y,
  vx,
  vy,
  width,
  height,
  get halfWidth () { return box.width / 2 },
  get halfHeight () { return box.height / 2 },
  
  update (deltaTime) {
    box.y += box.vy * deltaTime
    box.x += box.vx * deltaTime
  },
  
  render (renderingContext) {
    renderingContext.translate(box.x, box.y)
    renderingContext.fillRect(
      -box.halfWidth,
      -box.halfHeight,
      box.width,
      box.height
    )
    renderingContext.translate(-box.x, -box.y)
  }
}

return box
}

Next we call spawnBox inside our boxes array

game.boxes = [
  game.spawnBox({
    x: SCREEN_WIDTH * 0.25,
    y: SCREEN_HEIGHT * 0.25,
    vy: 60,
    vx: 60,
    width: 48,
    height: 48,
  })
]

Now you should see a red box moving on the screen slowly towards the bottom right. It will go offscreen when it reaches the edge, because we didn’t tell it not to.

We will address this later when we look at AI, for now, we can write some code to detect when the player hits the enemy, we can reset the score to zero.

Collision Detection

We will add a new method to the player object named checkCollisionAgainstBoxes that will iterate through the boxes array and check rectangle vs rectangle intersection to determine if the player has hit (collided with) another box.

checkCollisionAgainstBoxes () {
  const playerLeft = game.player.x - game.player.halfWidth
  const playerTop = game.player.y - game.player.halfHeight
  const playerRight = game.player.x + game.player.halfWidth
  const playerBottom = game.player.y + game.player.halfHeight

  const boxCount = game.boxes.length
  let collision = false

  for (let i = 0; i < boxCount; i += 1) {
    const box = game.boxes[i]

    const boxLeft = box.x - box.halfWidth
    const boxTop = box.y - box.halfHeight
    const boxRight = box.x + box.halfWidth
    const boxBottom = box.y + box.halfHeight

    if (!(
      (playerBottom < boxTop) ||
      (playerTop > boxBottom) ||
      (playerLeft > boxRight) ||
      (playerRight < boxLeft)
    )) {
      collision = true
      break
    }
  }

  return collision
}

We will now modify the update method of the player object to check for collisions by calling the new method we just created. This code should come first in the method before the velocity vector is reset to zero.

const collision = game.player.checkCollisionAgainstBoxes()

if (collision) {
  game.score = 0
  return
}

Now if you serve the game up and move your player into the path of the enemy box, and watch your score at the top, it will reset to zero when the two boxes collide.

We are getting closer to having a finished game now!

There are a lot of other features in the example project that I will not cover in such detail here, as the code is readily available and you should be able to figure it out once you’ve completed this course.

Game States

Our next task will be splitting the game into 3 distinct game states each of which will be responsible for updating and rendering the respective objects for that state.

At the beginning of the code before the game object and before the key code constants, we will add some constants for our game states.

const GS_TITLE = 'title'
const GS_PLAY = 'play'
const GS_GAMEOVER = 'gameover'

We are going to need to use the spacebar key and escape key, so let us add a few new key code constants.

const KEY_SPACE = 32
const KEY_ESCAPE = 27

We will want to test keyboard input as a toggle state for these keys, so let’s add another property to game.input called keysPressed

game.input = {
  keys: {},
  keysPressed: {}
}

Our game object is going to need to track the current game state, so we will add the state property and initialize to the title state.

game.state = GS_TITLE

Now things are going to get a little tricky. We will use a few if/else if blocks in the update and render methods of the game object to specify what to update and render for what game state.

This is not the best way to approach the problem of multiple game states, but it’s one of the easiest and so that is why I have chosen this architecture for the code.

A better solution would be to create a new object per state, map the state objects to string names, and use a lookup into the known states with the current state string as the key, and then call the methods of the state object… something along the lines of this:

const titleState = {
  update (deltaTime) {
    // ...
  },
  render (renderingContext) {
    // ...
  }
}
// ...
game.states = {
  [GS_TITLE]: titleState,
  [GS_PLAY]: playState,
  // ...
}

// ...
update (deltaTime) {
  const state = game.states[game.state]
  state && state.update && state.update(deltaTime)
}

render (renderingContext) {
  const state = game.states[game.state]
  state && state.render && state.render(renderingContext)
}

Implementing The Title Game State

update (deltaTime) {
  if (game.state == GS_TITLE) {
    // update the title state
  }
  // ... existing code from update omitted ...
}

For our title, we will render some text and just wait for the user to press the spacebar which will change the current game state from the title state to the play state, starting the game.

render (renderingContext) {
  renderingContext.clearRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT)
  
  if (game.state === GS_TITLE) {
    renderingContext.fillText(
      'Press Space to Play',
      SCREEN_WIDTH * 0.5,
      SCREEN_HEIGHT * 0.5
    )
  }
}

And the code that goes into the update the title state block from earlier looks like this.

if (game.input.keys[KEY_SPACE] && !game.input.keysPressed[KEY_SPACE]) {
  game.input.keysPressed[KEY_SPACE] = true
} else if (!game.input.keys[KEY_SPACE] && game.input.keysPressed[KEY_SPACE]) {
  game.input.keysPressed[KEY_SPACE] = false
  game.score = 0
  game.state = GS_PLAY
}

We are going to be really repetitive when testing input for the sake of simplicity. In the real world, you should abstract this stuff into a method and use that where you need to.

Okay so now the code that runs when you are staring at the title is just a check to see if the spacebar was pressed and then released. When it is released, the game score will reset to zero and the game state will change to the play state.

Implementing The Play Game State

update (deltaTime) {
  if (game.state == GS_TITLE) {
    // update the title state
    // ... existing code from update omitted ...
  } else if (game.state == GS_PLAY) {
    // update the play state
  }
}

We already have the code written for the play state, it is what was there before we added the title state. Move the code into the else if block in both the render and update methods.

Implementing The Gameover Game State

update (deltaTime) {
  if (game.state == GS_TITLE) {
    // update the title state
    // ... existing code from update omitted ...
  } else if (game.state == GS_PLAY) {
    // update the play state
    // ... existing code from update omitted ...
  } else if (game.state == GS_GAMEOVER) {
    // update the gameover state
  }
}

For the gameover state, we will render the same elements as the play state, a translucent black fill over the entire screen to dim the screen, and finally the game over text and instructional text. The game will wait for the spacebar key to be pressed same as the title, only the state will change from the gameover state to the title state.

Render code for the gameover state.

// render game elements
game.scoreTimer.render(renderingContext)
game.drawBoxes(renderingContext)
game.player.render(renderingContext)

// dim the screen
renderingContext.save()
renderingContext.globalAlpha = 0.5
renderingContext.fillStyle = 'black'
renderingContext.fillRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT)
renderingContext.restore()

// write the text
const oldFont = renderingContext.font
renderingContext.fillStyle = 'red'
renderingContext.font = '48px "Kelly Slab"'
renderingContext.fillText(
  'GAME OVER',
  SCREEN_WIDTH * 0.5,
  SCREEN_HEIGHT * 0.5
)
renderingContext.font = oldFont
renderingContext.fillStyle = 'lime'
renderingContext.fillText(
  'Press Space to Restart',
  SCREEN_WIDTH * 0.5,
  SCREEN_HEIGHT * 0.65
)

And the update code is as follows.

if (game.input.keys[KEY_SPACE] && !game.input.keysPressed[KEY_SPACE]) {
  game.input.keysPressed[KEY_SPACE] = true
} else if (!game.input.keys[KEY_SPACE] && game.input.keysPressed[KEY_SPACE]) {
  game.input.keysPressed[KEY_SPACE] = false
  game.state = GS_TITLE
}

Wrapping things up

In our player update method we check for collisions against the boxes, and if there is a collision, we reset the score to zero. Instead of resetting to zero, just change the game state to gameover and we have a finished playable simple game.

if (collision) {
  game.state = GS_GAMEOVER
}

There are many things that we have not covered here that exist in the example project, but you should have no problem understanding them by now.

You can check out all of the code from https://github.com/RichardMarks/gp-course-example-game-box-avoider

That’s All Folks…

I sincerely hope that you enjoyed this little crash course introduction into game programming adventures as much as I did writing it. 😜

To be notified when the Game Programming Adventures full course is available, email ccpsceo@gmail.com with the subject GPA2D and the body SUBSCRIBE and you will get emails from me, Richard Marks when the course is ready for primetime.