Designing a Game

In this note we’re going to discuss how to go about creating a game. While there is no guaranteed recipe for designing a good game (or for designing anything!), it wise to at least try to follow a systematic process and plan things out ahead of time.

Before you get started, you should have a basic idea of what you want your game to be about, and if you can actually implement it. For example, you might consider:

  • Theme, setting, characters: What is your game about? Are there characters in it? Or is it completely abstract (like checkers)? Does it have a story?

  • Game-play mechanics: How would you describe the basic actions that the player can do in the game? Is it a first-person shooter? A driving game? A puzzle-solving game?

  • Game audience: Who will be playing it, and how they’ll be interacting with it? For example, a game for kids in kindergarten to grade 1 can’t have much text in it because many such kids can’t read yet. Or if you are designing a game for a mobile phone, you probably don’t want your game to require extensive use of a keyboard.

  • Programming know-how: Do you know enough about programming to implement the game you have in mind? If not, you need to modify your idea so that it is something that you are pretty sure you can implement.

  • Time: How much time do you have to get the game done? This includes the time to write the code, and also to play-test the game.

    In general, it’s a good idea to plan to spend about 50% of your time writing the code for a game, and 50% of your time testing it. So if you have 10 hours to get a game done, spend about 5 hours writing code, and 5 hours testing it.

Iterative Implementation

Once you have the basic idea in mind of what you want to do, it’s time to start programming it. A good general approach to follow is this:

  • Step 1: Get a basic idea
  • Step 2: Make the basic idea more concrete.
  • Step 3: Implement the next version of your game.
  • Step 4: Add one feature and test it (fix it if it’s not working!).
  • Step 5: Go back to step 4.

The key idea is to work on one feature at a time. It’s easier to think about and test one thing at a time, and you will always be making a little bit of progress. You should have a working program at all times that you can test, or demo to people.

Here’s a bad process to follow when implementing a program: implement a bunch of features, and then test them all at once. This is sometimes called big bang implementation, and it usually doesn’t work out very well. The problem with implementing multiple features at once is that the features can interact with each other in unexpected ways. It can be very difficult to figure out which feature, or features, is the cause of a problem.

In what follows, suppose you are working for Science World, and they want you to create a game that helps explain germs to kids. The exact details of the game are up to you, so long as it is somehow about germs, and is easy enough for little kids to play. And, of course, it should be fun!

Step 1: Get a Basic Idea

The first step in creating a game is to get an idea of what you want the game to be about. Sometimes a boss, teacher, or client tells you what to do, and other times it will be up to you.

While there is no simple recipe for coming up with ideas, designers often think about games in two specific ways: theme-driven, or mechanics-driven:

  • Theme-driven game designs starts with a theme — Nazi zombies, bakery management, be Justin Bieber for day, etc. — and then adds game mechanics to fit the theme. Games based on movies are a good example of theme-first games.
  • Mechanics-driven games start with a game-play idea, and then add a theme later. For example, Tetris and Pong are examples of mechanics- driven games with very little theme.

In practice, most games are mix of theme and mechanics, and they may be developed haphazardly: sometimes a theme will suggest a game-play mechanic, and sometimes a game-play mechanic will suggest a theme.

For our Science World game, let’s take a mechanics-driven approach and start with the game-play idea of dodging obstacles. We will assume that the player controls an on-screen sprite that needs to avoid monster sprites. If a monster touches a player, it’s game over.

Using Make-believe Players

A trick that’s sometimes useful for designing games (or anything!) is to have specific players in mind. Thinking about what a make-believe player wants can help you make decisions about your game. Some designers even go so far as to give their make-believe players a name and a personal back story!

Let’s assume that the main audience for our game is young kids, around grades 1 or 2, who are just learning how to read. They understand how to play video games, of course, but they won’t play anything too complicated or with too many words. If older kids, or adults, find the game too simplistic that’s okay — they are not the target audience.

Step 2: Expand Upon the Basic Idea

Next let’s fill in some of the details of the game. One way to do this is to sketch a diagram on paper what some screens of the game look like. You will eventually need to write code everything you sketch, and so try to stick to things you know how to do, or are pretty sure you can figure out.

Here are some details:

  • The player is a big happy face, or some other representation of a human.
  • The germs are mean-looking greenish blobs. If the player touches a germ they get infected, and the player loses. If the player and the germs are both drawn as circles or rectangles, then it will be relatively easy to test if they overlap.
  • The number of germs on the screen will start out small, and then increase over time, e.g. every few seconds a new germ appears. Thus the game gets harder the longer the player survives.
  • The germs bounce around the screen like bouncing balls — because we know how to do that! To make things more interesting, lets also have the germs reverse their direction at random times. This may, or may not be a good idea: we’ll have to see how it works out in the implemented game.
  • The game score is the number of germs on the screen times the number of seconds the player survived. The longer the player survives, the higher their score.

That’s enough for now — if we can get these features working we will have a basic game to play with.

Iterative Design

An important aspect of design is that it should be iterative. That means that we start simple, and add features one at a time: after each new feature is added we have a new version of the game. Don’t start out by making it complex in the first version, because it is easy to get overwhelmed by all the details. There are many things we are not yet worrying about. For instance:

  • Since we decided to start with a dodging mechanic, we won’t be very concerned with the graphical look and feel. We’ll stick to colored circles and rectangles at first. Later, we can cover them with good-looking images.
  • There’s no notion of the player fighting germs. While fighting fits well with the theme, we’ve decided not to worry about it in the first few iterations. Later, we could some element where the user could, for example, kill some of the germs, or maybe make them smaller.

Step 3: Implement the Next Version

After you’ve fleshed out the details and are reasonably sure they are things you are able to implement, then it’s time to start writing the program.

A good general strategy is to start with a simple working program, e.g.:

void setup() {
    size(1000, 1000);
}

void draw() {
    background(255);
}

It doesn’t do much, but, it works, and it works correctly.

Step 4: Add One Feature — then Test It

At this point we have a simple working game. It’s may not yet be a great game, and or even that much fun to play, but that’s okay. It’s just one step along the way to the finished program.

When deciding what feature to add next try to pick one that you understand and is easy to implement. If a feature is too big or complicated, implementing it can turn into hours of frustration.

Testing is an important part of any program, and you should test each feature immediately after you implement it. If, instead, you were to add a bunch of features and then test them all at once, debugging tends to be much harder. Multiple features could interact in ways that make it difficult to determine the root problem. By adding, and then immediately testing, one feature at a time, it is usually easier to locate and fix bugs.

For our first feature, lets add a player sprite:

Player player;

void setup() {
    size(1000, 1000);

    player = new Player();
}

void draw() {
    background(255);

    player.update();
    player.render();
}

Of course, we haven’t yet written the Player class. So let’s do that next:

class Player extends Sprite {
    color fillColor;
    float radius;

    Player() {
        fillColor = color(255, 255, 0);  // yellow
        radius = 100;
    }

    void render() {
      pushMatrix();
      fill(fillColor);
      stroke(0);
      strokeWeight(3);
      ellipse(x, y, 2 * radius, 2 * radius);
      popMatrix();
   }

   void update() {
      x = mouseX;
      y = mouseY;
   }
}

If you add this to the source code (along with the Sprite class), you should have a working program that lets you move a yellow ball around the screen.

Don’t add any other features until you are confident this one is working correctly!

Step 5: Go Back to Step 4

When we finish testing the feature from the previous step, we can implement and test another feature. And then we repeat this until the game is done.

So for the next feature, lets add germs. Since there are multiple germs, we’ll store them in an ArrayList:

Player player;
ArrayList<Monster> monsters;

void setup() {
    size(1000, 1000);

    player = new Player();

    monsters = new ArrayList<Monster>();
}

void draw() {
    background(255);

    player.update();
    player.render();

    for(Monster m : monsters) {
        m.update();
        m.render();
    }
}

We’ve decided to call the germs “monsters”, just in case we later add other kinds of enemies.

We haven’t written the Monster class yet, but notice how much code we can still write: we can initialize the ArrayList, and also write the basic loop for updating and rendering all the individual monster objects.

Here’s a first version of the Monster class:

class Monster extends Sprite {
  color fillColor;
  float radius;

  Monster(float initX, float initY) {
    fillColor = color(107, 142, 35); // olive drab
    radius = random(25, 75);

    x = initX;
    y = initY;

    dx = random(3.0, 7.0);
    dy = random(3.0, 7.0);

    if (random(1.0) < 0.5) {
      dx = -dx;
    }
    if (random(1.0) < 0.5) {
      dy = -dy;
    }
  }

  void render() {
    pushMatrix();
    fill(fillColor);
    noStroke();
    ellipse(x, y, 2 * radius, 2 * radius);
    popMatrix();
  }

  void update() {
    super.update();

    //
    // bounce off edges
    //
    if (x < 0) { // left edge
      x = 0;
      dx = -dx;
    }

    if (x > width) {  // right edge
      x = width;
      dx = -dx;
    }

    if (y < 0) {  // top edge
      y = 0;
      dy = -dy;
    }

    if (y > height) {  // bottom edge
      y = height;
      dy = -dy;
    }
  }

  boolean overlaps(Player p) {
    if (dist(x, y, p.x, p.y) <= radius + p.radius) {
      return true;
    } else {
      return false;
    }
  }
} // class Monster

The render and update functions are similar to functions we’ve written for bouncing balls in the past. The overlaps function is interesting: it takes a Player as input, and tests if the monster overlaps with the player.

Adding this class to the source code, we can now add a monster or two like this:

// ...

void setup() {
    size(1000, 1000);

    player = new Player();

    monsters = new ArrayList<Monster>();
    monsters.add(new Monster());
}

// ...

Now, assuming we’ve implemented all this code correctly, when the program runs we should see a green ball bouncing around the screen alongside the user- controlled yellow ball.

Next, lets have the monsters check to see if they’ve hit the player. When that happens, the game is over, and so we’ll keep track of that using a boolean variable:

// ...

boolean gameOver;

void setup() {
    size(1000, 1000);

    player = new Player();

    monsters = new ArrayList<Monster>();
    monsters.add(new Monster());

    gameOver = false;
}

// ...

Inside draw(), we’ll have every monster call its overlap function to check to see if it has hit the player. It one of the monsters hits the player, then gameOver is set to true. Then, at the end of draw(), we use an if-statement to check if the game is over and, if so, print a message:

void draw() {
    background(255);

    player.update();
    player.render();

    for(Monster m : monsters) {
        m.update();
        m.render();
        if (m.overlaps(player)) {
            gameOver = true;
        }
    }

    if (gameOver) {
        println("Game over --- you lose!");
        player.fillColor = color(255, 0, 0);
    }
}

When the game is over, two things happen: a message is printed in the console window, and they players circle changes to red. We use println for the message because it is quicker and easier than using fonts — later we can add a fancier font message that appears on the main screen. Changing the player’s circle to red gives them useful feedback that the game has ended.

Lets take a moment to think about whether or not our pretend players — kids around grade 1 or 2 — would like these features. There’s no wordiness or complexity here, so the kids would probably not complain. They (and us!) might not think it’s fun to play, but that’s okay. Hopefully, the game will become more entertaining as more features are added.

We should also think if our client — Science World — would be happy with this feature. It’s still very early in the design of the program, but if a Science World executive saw this version of the game they would be able to at least see that the germ theme is starting to emerge.

Conclusion

The process we’ve described here is a good one to start with. It’s just a high-level overview, though, and you still need to figure out the details and get all the pieces to work together. It takes sweat, but stick to it. As you get more experience writing code, you’ll get better at estimating which features are likely to be easy to implement, and which are likely to be hard.

The Game

Here’s a later iteration of the germ game, with a number of extra features added. It still needs some work to be fun for more than a few seconds, but it’s useful to look at the code. You can see how all the basic techniques discussed previously in the course work together in a single program.