{ Soham Kamani }

AboutBlog GithubTwitter

How games are programmed: An introduction to the core concepts required to build a video game 🎮

So you want to build a video game? Cool! I’m not really a professional game developer myself, but I have made a few games here and there for fun. The architecture and patterns used for building games are a bit different from, say, developing a website or back-end service.

In this post, we will walk through the core concepts that you, as a programmer, would need to know before you dive into creating your own game. These patterns are so essential to building a game, that if you left any programmer with the task of developing a game for the first time, they would probably come up with the same patterns themselves… eventually.

The state machine

A game is actually just one giant state machine. Picture a game of snake: Its “state” refers to the position of each block of the snake, along with the position of the next morsel of food. Now, the next state is calculated based on the current state, and the user input.

snake game state machine

This calculation of the next state, happening many times a second, more or less makes a video game.

Loading…

In an ideal world, we would just program the game like it was an ideal state machine and be done with it. However, games are visual, and need pictures and sounds to fully immerse the user. These pictures and sounds are commonly referred to as “assets”, They are too heavy to be loaded from disk and displayed in real time.

This is where the all-too-familiar loading screen comes in. The purpose of this screen is not solely to annoy you. During this time, the different images and audio clips that the game needs are loaded into memory, from where they can be fetched and displayed in real time while the game is being played.

loading screen

Initialization

Before the game begins, it’s initial state is decided. For our game of snake, this would mean deciding the initial position of the body segments of the snake, as well as the position of the first morsel of food. If we expressed the snakes state as a JSON object, it would look like this:

{
  "snake": {
    "body": [{
        "x": 5,
        "y": 5
      },
      {
        "x": 6,
        "y": 5
      },
      {
        "x": 7,
        "y": 5
      }
    ],
    "direction": "UP",
  },
  "food": {
    "x": 2,
    "y": 10
  }
}

The state represents the current frame in a game:

initial state

If the game is savable, then this would be the point where the previous saved state is fetched and loaded.

The update loop

Although games seem continuous to the player, they are actually a bunch of static frames, that are rendered based on the games current state. The frames are refreshed at a high rate (30-60 frames per second) to give the appearance of continuity.

As mentioned before, the next state is calculated based on the current state, and the input. This calculation is done for every frame, and is called the update loop. The update loop is probably the most important part in understanding game development, and is also the part that will contain the bulk of the logic that makes the game work.

For our snake game, let’s take a look at the simplified version of the update loop that we would see in a typical game. I have used javascript to illustrate this, and have only covered the actions of movement and eating food, leaving out the conditions for dying or ending the game:

// The "state" of the game, as described in the previous section
const state = {
  snake: {
    body: [
      { x: 5, y: 5},
      { x: 6, y: 5},
      { x: 7, y: 5}
    ],
    direction: 'UP'
  },
  food: {x: 2, y: 10}
}

// In the real world, the "input" will be replaced by
// the actual input device API (like a keyboard or controller)
const input = {
  upKeyPressed: false,
  downKeyPressed: false,
  leftKeyPressed: false,
  rightKeyPressed: false
}

function update () {

  // Change the direction of the snake if one of the movement keys is pressed
  if (state.snake.direction == 'UP' || state.snake.direction == 'DOWN') {
    // The snake can only go left or right, if it's facing up or down
    if (input.leftKeyPressed) {
      state.snake.direction = 'LEFT'
    } else if (input.rightKeyPressed) {
      state.snake.direction = 'RIGHT'
    }
  }

  if (state.snake.direction == 'LEFT' || state.snake.direction == 'RIGHT') {
    // The snake can only go up or down, if its facing left or right
    if (input.upKeyPressed) {
      state.snake.direction = 'UP'
    } else if (input.downKeyPressed) {
      state.snake.direction = 'DOWN'
    }
  }

  //Get the head of the snake
  const head = state.snake.body[0]

  //Calculate the next position of the head based on the direction of the snake
  const nextPos = {
    x: head.x,
    y: head.y
  }
  switch (state.snake.direction) {
    case 'UP':
      nextPos.y -= 1
      break
    case 'DOWN':
      nextPos.y += 1
      break
    case 'LEFT':
      nextPos.x -= 1
      break
    case 'RIGHT':
      nextPos.x += 1
      break
  }

  // If the snake has found food, add a segment to the snakes body
  // The position is left blank (0, 0) for now
  if (head.x === state.food.x && head.y === state.food.y) {
    state.snake.body.push({
      x: 0,
      y: 0
    })
  }

  // Now we assign the next position of a segment of a snakes
  // body to the current position of the previous segment
  // this "moves" the entire snake body while maintaining
  // the shape that the snake was in
  for (let i = state.snake.body.length - 1; i >= 0; i--) {
    // For the "head" of the snake, there is no previous segment, so we
    // assign the calculated `nextPos` variable as its next position
    let nextSegment = i === 0 ? nextPos : state.snake.body[i - 1]
    state.snake.body[i].x = nextSegment.x
    state.snake.body[i].y = nextSegment.y
  }
}

After the update() function is run, the state of the snakes body would change from

[
  { x: 5, y: 5},
  { x: 6, y: 5},
  { x: 7, y: 5}
]

to

[ 
  { x: 5, y: 4 },
  { x: 5, y: 5 },
  { x: 6, y: 5 }
]

update-loop changing

Rendering

Ok, so we have the state object, which lets us know the state of everything in the game. The update loop controls how this state changes. Now, all that’s left is to actually draw the game on screen from the state. This is referred to as “rendering”, or “painting”. This stage normally takes place just after the update function has completed. Now that we have the updated state, we can paint the corresponding frame.

rendering

Scaling

The resolution of the screen that the game is rendered to is often different from the in-game co-ordinate system. In our example, the coordinate system should be scaled to the pixels on screen. If the resolution of our assets (the picture of the snake head, body, and food) is 20x20, and the resolution of the area we are rendering to is 300x300, then that would make our game coordinate system 15 (300/20) blocks wide and 15 blocks tall.

scaling diagram

The code required for painting differs based on the platform. The code for painting for an android game would be quite different from, say, a game made in Unity. For the sake of completeness, let’s see the code that renders our snake game on to an HTML5 Canvas:

const render = () => {
  // Clear all elements currently on the canvas
  ctx.clearRect(0, 0, boardWidth, boardHeight)

  // Draw the food
  // Here, `state.food.x` corresponds to the in-game co-ordinate system
  // and `blocksize` is the scaling factor
  ctx.drawImage(imgFood, state.food.x * blockSize, state.food.y * blockSize)
  
  const head = state.snake.body[0]
  // Next, we draw the head of the snake 
  ctx.drawImage(imgHead, head.x * blockSize, head.y * blockSize)

  for (let i = 1; i < state.snake.body.length; i++) {
    // Finally, we draw each block of the snakes body
    ctx.drawImage(imgBody, state.snake.body[i].x * blockSize, state.snake.body[i].y * blockSize)
  }
}

This is a simplified version of the actual rendering code. Here, we skipped the rotation of the head based on the direction of the snake (Which you can see in the full source code).

Optimizations

Games are one of the most resource intensive applications that run on a regular computer. For 3D games, this means rendering many models and shapes at a rate fast enough to simulate motion. For web based games, this involves downloading many heavy assets to be displayed in game. There are a lot of optimizations that happen behind the scenes.

One such optimization used for 2D games is using sprite sheets. A sprite sheet is a single image that contains all the images used in-game. For our snake game, we used 3 20x20 images for the head, body, and food. Instead of downloading all 3 separately when the game was loading, we could download a single 60x20 image containing all the images within it. This reduces the number of HTTP requests required, and also makes better use of image compression when possible.

sprite sheet

Finishing up

In this post, we went through the basic concepts of game development, and the lifecycle that a game goes through. But, as with everything, the devil is in the details. There’s a bit more that we need to do to make the game work as expected. This is dependent on the platform or framework we’re using. For our web-based snake game, we need to:

  1. Wait for the assets to load before starting the game loop
  2. Run the update and render function periodically, and the desired fps (frames per second) rate.
  3. Update the input state when we get a keydown event from the keyboard.
  4. Calculate the blockSize based on the height and width of the canvas, and the height and width of our assets.

You can view the complete source code for this game on Github, and play the actual game here.


Like what I write? Join my mailing list, and I'll let you know whenever I write another post

Comments

Soham Kamani

Written by Soham Kamani, an author,and a full-stack developer who has extensive experience in the JavaScript ecosystem, and building large scale applications in Go. He is an open source enthusiast and an avid blogger. You should follow him on Twitter