Game Prototyping

Welcome to the Game Prototyping Workshop!

Making a game can be easier than you think. With a few simple concepts, you can start to piece together a prototype, in any language, that can help you test your mechanics, sketch out an interface, or examine different possibilities for artwork. In this workshop, we'll use Python to:

  • Build the "main loop"
  • Get input from the user
  • Process the rules of the game
  • Display the result on screen as graphics

all by making a simple version of Space Invaders! In one short evening!

Let's get started!

Setting Up

We'll be using a Python module called Pygame to prototype this demonstration game. To install Pygame, go to the links below.

Python and Pygame downloads, for Windows:
http://www.python.org/download/
http://www.pygame.org/download.shtml

For Mac:
Python 2.4:
http://pythonmac.org/packages/py24-fat/index.html
or Python 2.5:
http://pythonmac.org/packages/py25-fat/index.html
Be sure to get the Python, pyobjc, and pygame packages from the list if you don't have them already.

Also, you'll need the demonstration files I've prepared for this class. Download one of the "demo" files you see linked below, either the .zip or .tar.gz file.

AttachmentSize
demo.zip73.59 KB
demo.tar.gz140 KB

The Basics

At their heart, games and other applications run on a very simple three-part structure.

  • The Controller
  • The Model
  • The View

Each is separate but dependent on the others. The controller takes all the input into the system, from the keyboard to the mouse to the internet connection. The model processes the inputs from the controller and makes decisions based on it. The view displays the results of those decisions as output to the screen.

That's it!

Let's Begin!

Open the first file in the demo (01_lincolnkitty_basics.py) in a Python editor. This editor might be Idle, which usually comes with Python, your own favorite development environment, or even just a simple text editor.

This file is the skeleton from which you can build just about any prototype. It loads up the pygame and other important modules, creates a main function, sets up the screen, and start running (although only briefly, since nothing is happening yet.)

The most important thing to observe here is the "while" statement. This is the "main loop" of the application. It will repeat, over and over again, for every frame of the game sent to the screen (which we've set to be updated at 60 frames per second.)

Think of this like an animation--frames go to the movie screen one at a time, creating motion through our persistence of vision. This works the same way, but with the added possibility of controlling that movie. We'll look at that next.

Control

Ok, now open the second file. The first thing you'll notice is that we've added to functions, loadImageFile and loadSoundFile. Don't sweat these. They're just handy wrappers around a few functions that help us load up files that are located in the data subfolder. Feel free to use these as is in your own prototypes. They work well, and you almost certainly won't need to modify them.

Next, in the main function, we use the loadImageFile function to pull in the background image. Then we use the screen.blit function to blit that image.

Wait, blit? Wazzat? Blitting is like pasting an image onto the screen. Everytime we update graphics on the screen, we "blit" them. Think of this like a Monty-Python clip art animation. Right now, there's just the background, but we'll be pasting a few more things on here later.

The most important change, however, is control! Inside the main loop, you'll see a new loop starting with

for event in pygame.event.get():

An event is just what it sounds like--it's something that happens. It's the controller that makes these things happen. That could be a key-down event, a mouse-movement event, a joystick-button event, a window-closing event, and so on.

The loop here is checking for events. In this case, it checks to see if the user has asked to close the window or if the user has hit the ESC key. If either of those conditions are true, the main loop will quit and the game will end.

Now we're in control!

Your First Object

Okay, we're in control, but so what? This is boring! So, let's start blitting a few more things on here.

Open the third file. Near line 71, you'll see a line like this:

class PlayerSprite( pygame.sprite.Sprite ):

This is a class, the chief concept in object-oriented programming. DO NOT PANIC! Object-oriented code is designed to make your life easier, not harder. This class is a sprite, and you can think of it like a sticker or one of those animated Monty-Python characters. All a class does is define what makes a sticker a sticker. In this case, the sticker represents a player on the screen.

In python, every class starts off with a __init__ function. That's just an ugly way of stating what every sticker will look like once we start it up. In this case, all it says is that each sticker will load up the "lincoln.png" image and will start at the bottom middle of the screen.

The other function is called update, and it runs at every frame. This update function is really just how we ask the sticker to animate itself. Depending on a couple of the sticker's properties (its x and y velocity,) the sticker will move in a particular direction (or not) every frame.

That's the beauty of object-oriented code--we leave each object, like this sprite, to define what itself does. That keeps things nice and neat. So don't sweat object-oriented--it's a harsh-sounding buzzword that just means being tidy.

Sprites

Okay, keep looking at that third file. In the main function, we've added a couple new variables called player_sprite and player_sprites. player_sprite creates a new sprite, or "sticker" as we've been calling it. It does this by instatiating the PlayerSprite class. All this means is that we use the PlayerSprite class, which is just a definition, to create an actual sprite that we can move around on the screen. Since this is a one-player game, we just create one sprite and add it into the player_sprites (note the plural) group.

Now let's move it around! If you look in the event loop, you'll see we've added a few more key commands. These new commands set the velocity for the player sprite. It does not, however, move the sprite! This is important to remember. The keys are changing values of the sprite, but we leave it to the sprite to move itself! Let's see how this happens.

Below the event loop, we have a few more lines. First, we ask the player_sprites group to clear away the old "stickers" from the screen and replace them with the background image. Then we ask the group to run the update functions of the each of the sprites. Well, in this case, there's only one sprite, but it could easily do this for 100 sprites. This changes the position of where our sprite will be "stuck" on the screen.

Lastly, we ask the group to draw the sprite in its new location. See how this works? Just like a flipbook or a stop-action movie--take one position, show it screen, take it away, move it to a new position, and then show that one on screen. We do this 60 times every second, and the sticker appears to move around everytime we hit the arrow keys.

You have your first living game character!

More Sprites... Lots More!

Okay, open up the fourth file, and let's get nuts!

Now we've added three new sprite classes. Heavy! Well, not really. They do the same thing as the PlayerSprite class, they're defining new "stickers" that we can place on our screen.

What are the differences? Well, they load up different images and, therefore, are different sizes. The area that each takes up is called its bounding box--that will help us figure out how they will react to each other. More on that later.

They also have different update functions. The enemy will move around in a given direction until it hits a boundary, then it will bounce back in the reverse direction. It will do this for every frame--keeping moving one way until it can't, then move the opposite. Dumb AI, but it works.

The other classes, the player and enemy "lasers", will go up or down respectively, for each frame, until they reach the screen boundary. That's it!

Now look at the main function. While things might seem like they're getting complicated, they're just more of the same. We create three enemies to start off with. Every 200 frames, we add another enemy. And for every group of sprites (players, enemies, and each of their sets of laser blasts) we do exactly what we did before. We erase. We ask them to update. We draw them again. And round and round we go.

Live, Collide, Die

Fine, fine, there are a couple more things happening in the fourth file.

First, on line 261, we have this line:

player_laser_sprites.add( PlayerLaserSprite( player_sprite.rect.midtop ) )

Wuh? All that does is ask that a new laser sprite be added to the board every time we hit the spacebar. And we ask that it appear in the area of the player_sprite... at the middle top of it, to be exact. Once we've added the sprite, it just goes, passed on its own rules established in "update." Bang!

If you look in the update function for the EnemyClass, you'll see that is does the exact same thing for enemy laser blast, except that it does it randomly instead of at every press of the spacebar.

The other feature worth noticing is at line 292. This is where we figure out if sprites in the player's laser group and sprite in the enemy group overlap. This is called collision detection. This function tests to see if there was a collision, removed sprites from both groups that have collided, and then returns a list of those collisions.

Right now, we don't want to do much of anything beyond removing the sprites, but we could use this list of collision in a loop to play sounds or have the game display a message or whatever at each collision. In fact, at line 297, we ask that, when the player sprite collides with the enemies' laser sprites, the main loop should stop. That's another way of saying...

GAME OVER!

A Little Polish

Take a peek at the fifth file. All that's been added is a new window title at line 191 ("Lincoln Versus Kitties"), creating a few sound objects starting at line 201, and few calls sprinkled around the file that play those sounds at the right time. I'll leave those for you to discover, but their placement should make sense at this point. Also, note how easy it is to add sound effects here in just a couple of lines!

And that is that... for making space invaders. But what about other games? Using other languages?

First off, the basics are always the same. Collect input. Make decisions. Wipe away the old drawings and paste on some new ones. Repeat.

Secondly, think about changing some of the rules, like what we've done in those update functions or with the collision detection.

What if the player moved around in a circle, instead of left and right? Not too hard to change the update function to do that.

What if, instead of destroying the enemy, the laser blasts bounced back? And then the player could bounce it back again? That's Pong.

What if the background scrolled down and the sprites looked like cars? That's Spy Hunter and just about every driving game.

What if, instead of bullets, geometric shapes came down from the top? And what if you assigned keys to control and position them? That's Tetris.

What if you used 3D instead of 2D? Pygame will do that, with a bit more patience. And you'd have Doom, Hexen, Quake, and on and on and on.

The basics will apply to most every situation you're in. The important bits are how you set up the controls, what rules the game follows, and how they get displayed on the screen. We've seen how we can control each of these. It's just Controller, Model, and View. The rest is up to you.

Wokka, wokka, wokka, wokka, wokka...