Table of Contents for
Gaming Hacks

Version ebook / Retour

Cover image for bash Cookbook, 2nd Edition Gaming Hacks by Simon Carless Published by O'Reilly Media, Inc., 2004
  1. Cover
  2. Gaming Hacks
  3. Credits
  4. Contributors
  5. Acknowledgments
  6. Foreword
  7. Preface
  8. How to Use This Book
  9. How This Book Is Organized
  10. Conventions Used in This Book
  11. Using Code Examples
  12. Comments and Questions
  13. Got a Hack?
  14. 1. Playing Classic Games
  15. Legal Emulation
  16. Play Commodore 64 Games Without the C-64
  17. Play Atari ROMs Without the Atari
  18. Use Atari Paddles with Your PC
  19. Run Homebrew Games on the Atari 2600
  20. Create Your Own Atari 2600 Homebrew Games
  21. Play Classic PC Graphic Adventures
  22. Play Old Games Through DOSBox
  23. Play Reissued All-in-One Joystick Games
  24. Play Arcade Games Without the Arcade
  25. Add and Manipulate a MAME Frontend
  26. Keep Your ROMs Tidy and Organized
  27. Learn Game-Specific MAME Controls
  28. Filter Inappropriate MAME ROMs
  29. Autoboot into MAME Heaven
  30. Play Emulated Arcade Games Online
  31. Play Classic Pinball Without the Table
  32. Emulate the SNES on the Dreamcast
  33. 2. Playing Portably
  34. Play Games on Your iPod
  35. Mod Your Game Boy
  36. Take and Print Photos with Your Game Boy
  37. Compose Music on Your Game Boy
  38. Explore the GP32 Handheld Gaming System
  39. Take Your Console with You
  40. Explore the Bandai WonderSwan
  41. Play Real Games on Your PDA
  42. Install a PlayStation 2 in Your Car
  43. 3. Playing Well with Others
  44. Practice Proper MMORPG Etiquette
  45. Understand MMORPG Lingo
  46. Grind Without Going Crazy
  47. Make a Profit in Vana’diel
  48. Write MMORPG Macros
  49. Build an Effective Group
  50. Catch Half-Life FPS Cheaters Redhanded
  51. 4. Playing with Hardware
  52. Build a Quiet, Killer Gaming Rig
  53. Find and Configure the Best FPS Peripherals
  54. Adapt Old Video Game Controllers to the PC
  55. Choose the Right Audio/Video Receiver
  56. Place Your Speakers Properly
  57. Connect Your Console to Your Home Theater
  58. Tune Console Video Output
  59. Tune Your TV for Console Video
  60. PC Audio Hacking
  61. Optimize PC Video Performance
  62. Build a Dedicated Multimedia PC
  63. Use a Multimedia Projector for Gaming
  64. 5. Playing with Console and Arcade Hardware
  65. Play LAN-Only Console Games Online
  66. Hack the Nuon DVD Player/Gaming System
  67. Play Import Games on American Consoles
  68. Find a Hackable Dreamcast
  69. Play Movies and Music on Your Dreamcast
  70. Hack the Dreamcast Visual Memory Unit
  71. Unblur Your Dreamcast Video
  72. Use Your Dreamcast Online
  73. Host Dreamcast Games Online
  74. Burn Dreamcast-Compatible Discs on Your PC
  75. Burn Dreamcast Homebrew Discs
  76. Buy Your Own Arcade Hardware
  77. Configure Your Arcade Controls, Connectors, and Cartridges
  78. Reorient and Align Your Arcade Monitor
  79. Buy Cart-Based JAMMA Boards
  80. Programming Music for the Nintendo Entertainment System
  81. 6. Playing Around the Game Engine
  82. Explore Machinima
  83. Choose a Machinima Engine
  84. Film Your First Machinima Movie
  85. Improve Your Camera Control
  86. Record Game Footage to Video
  87. Speedrun Your Way Through Metroid Prime
  88. Sequence-Break Quake
  89. Run Classic Game ROM Translations
  90. Change Games with ROM Hacks
  91. Apply ROM Hacks and Patches
  92. Create PS2 Cheat Codes
  93. Hack Xbox Game Saves
  94. Cheat on Other Consoles
  95. Modify PC Game Saves and Settings
  96. Buff Your Saved Characters
  97. Create Console Game Levels
  98. 7. Playing Your Own Games
  99. Adventure Game Studio Editing Tips
  100. Create and Play Pinball Tables
  101. Put Your Face in DOOM
  102. Create a Vehicle Model for Unreal Tournament 2004
  103. Add a Vehicle to Unreal Tournament 2004
  104. Modify the Behavior of a UT2004 Model
  105. Download, Compile, and Create an Inform Adventure
  106. Decorate Your IF Rooms
  107. Add Puzzles to Your IF Games
  108. Add Nonplayer Characters to IF Adventures
  109. Make Your IF NPCs Move
  110. Make Your IF NPCs Talk
  111. Create Your Own Animations
  112. Add Interactivity to Your Animations
  113. Write a Game in an Afternoon
  114. 8. Playing Everything Else
  115. Tweak Your Tactics for FPS Glory
  116. Beat Any Shoot-Em-Up
  117. Drive a Physics-Crazed Motorcycle
  118. Play Japanese Games Without Speaking Japanese
  119. Back Up, Modify, and Restore PlayStation Saved Games
  120. Access Your Console’s Memory Card Offline
  121. Overclock Your Console
  122. Index
  123. Colophon

Write a Game in an Afternoon

Learn to create your own games by dissecting a simple homebrew game.

With a little knowledge, some time, and the right tools, game programming is within your reach. As [Hack #91] and [Hack #92] demonstrated, Python and PyGame are two excellent tools for creating interactive animations. They’re also good for the rest of game programming.

Let’s explore a simple game that has all of the essential features of any 2D arcade game: animation, collision detection, user input, and a winnable challenge. Best yet, it’s a couple of hundred lines of code that you can enhance, change, polish, and adapt to create your own masterpieces.

Introduction and Initialization

In Bouncy Robot Escape, you control a robot trying to escape from the laboratory into the wild world of freedom. Several colorful, giant, bouncy balls (a tribute to The Prisoner) block your path. You can block their attack with a small force field. Can you find the door in time?

The game starts by loading several other Python modules:

#!/usr/bin/python

import math
import random
import sys
import time

import pygame
from   pygame.locals import *

The math, random, sys, and time modules provide math, random number, operating system, and time-related functions, respectively. You’ll encounter them all later. The pygame lines should look familiar; the second imports some variables used for input handling.

The main entry point of the game is the main function. It’s very similar to that in [Hack #92] .

def main( ):
    pygame.init( )
    pygame.display.set_caption( 'Bouncy Robot Escape' )

    max_x       = 640
    max_y       = 480

    screen      = pygame.display.set_mode(( max_x, max_y ))
    background  = pygame.Surface( screen.get_size( ) ).convert( )

    player      = Player( 'robot.png', max_x, max_y )
    door        = Door(   'door.png',  max_x, max_y )

    balls       = [  ]
    ball_images = [ 'ball_blue.png', 'ball_red.png', 'ball_yellow.png' ]
    rand_ball   = random.Random( )

    for i in range( random.randint( 3, 7 ) ):
        ball_image = ball_images[ rand_ball.\
            randint( 0, len(ball_images) -1 ) ] 
        balls.append( Ball( ball_image, max_x, max_y ) )

    loop( screen, background, player, balls, door )

The first two lines initialize the screen (the surface to which the game will draw everything) and add a window title showing the name of the game. The next several lines declare some variables and create some objects.

player and door are Python objects, initialized with a graphic and the max_x and max_y coordinates. The same goes for the balls that chase the player, though that code is more complex.

Finally, the code starts the main loop, calling loop() with the important variables so far: the two drawable surfaces, the player, the list of balls, and the door.

There are three different ball images: blue, red, and yellow. There will always be three to seven balls chasing the robot. This code creates an empty list, balls, to hold all the balls and a list, rand_ball, of available ball colors. Within the loop, it selects a random image from the list of colors, creates a new Ball object, and appends it to the balls list. You’ll use this list later (without having to know how many balls it contains).

Warning

Beware reusing the same random number generator for multiple purposes. I had terrible results until I created a new Random object to initialize the ball color. It’s difficult to generate truly random numbers, so the algorithm passes a seed to a fixed mathematical function. Different Random objects use different seeds, thus generating different results.

The Main Loop

All games have some sort of main loop. This loop handles user input, updates player and enemy positions, checks for victory and loss conditions, and draws the following screen.

def loop( screen, background, player, balls, door ):
        run         = 1
        player_turn = 1
        start_time  = time.time( )

This snippet fetches the variables passed from main() and sets up a few other variables. The run flag indicates whether to continue running the main loop. player_turn is another flag that indicates whether the player should move in this loop iteration. Right now, the robot moves at half the speed of the balls, so he can move only on every other turn. Finally, start_time is the current time. The robot has to scan the room for 10 seconds before he can find the door. This variable keeps track of the elapsed time:

while run:
    for event in pygame.event.get( ):
            if event.type =  = QUIT:
                    return
            if event.type =  = KEYDOWN:
                    run = handle_key( event.key, player )

This snippet of code checks for game-ending QUIT events as well as keypresses. If the user closes the window, PyGame detects a QUIT event. This function returns to main(), in that case. If the user has pressed a key, call the handle_key() function with the value of the key pressed and the Player object, so that function can make the player appropriately. The value returned from the function ends up in run, so that the player can press a key to quit the game by ending the loop.

draw_background( background, screen )

if door.visible:
    door.draw( background )
elif time.time( ) - start_time > 1.0:
    door.visible = 1

for ball in balls:
    ball.move( )
    ball.draw( background )

if player_turn =  = 1:
    player.move( )
    player_turn = 0
else:
    player_turn = 1

player.draw( background )

This snippet starts by drawing the background onto the screen. You’ll see this function shortly. Next, it checks to see if the door is visible. If so, it draws it. If not, it checks if 10 seconds have elapsed since starting the level. At that point, the robot should see the door, so it sets the door’s visibility to true.

The code next loops through the balls list, moving and drawing each ball. Notice that this code does not need to know how many balls there are in the list; it will move and draw each of them. Note that you draw to the background, so you can update the main screen in one move.

The next code is a bit tricky. It makes sure that the player can move every other turn. If the player_turn flag is true, the player moves and the flag flips to false. Otherwise, the flag flips to true. Either way, you have to draw the player on every turn because you’re redrawing the entire screen. If you drew the robot only when the player moved, the robot would flicker; he’d be visible only every other turn.

That takes care of moving everything. Now let’s check for end conditions and draw everything to the main screen:

handle_collisions( screen, player, balls, door )
screen.blit( background, ( 0, 0 ))
pygame.display.flip( )

By the way, draw_background() is very simple. It fills the background with the color white and draws it on the main screen:

def draw_background( background, surface ):
        background.fill(( 255, 255, 255 ))
        surface.blit( background, background.get_rect( ) )

Input and Motion

handle_key() translates user keypresses into actions for the robot. It’s deceptively simple:

def handle_key( key, player ):
      key_events = {
              K_UP:    player.move_up,
              K_DOWN:  player.move_down,
              K_LEFT:  player.move_left,
              K_RIGHT: player.move_right,
              K_SPACE: player.enable_shield
      }

      if key_events.has_key( key ):
              key_events[ key ]( )
      elif key =  = K_ESCAPE:
              return 0

        return 1

This function calls the robot’s appropriate move_ direction method for every cursor keypress and enables the robot’s shield if the user presses the spacebar. As in [Hack #92] , the responsibility for moving lies with the robot. If the user presses the Escape key, this function returns a false value, causing the main loop to exit and quitting the program; any unhandled keypress continues the game.

Collision Detection

One of the trickiest parts of game programming is collision detection, figuring out if any game objects have collided and deciding what to do about it. We’ll use the bounding sphere method, where every object has an invisible circle around it (it’s a 2D game, so there’s not really a sphere). If two circles overlap, the objects have collided. This is fairly easy to program. Every object has a center point and knows the radius of its bounding sphere. If the object is less than its radius in distance from another object, it has collided with the other object.

Here’s the code:

def handle_collisions( screen, player, balls, door ):
    for ball_count in range( len(balls) ):
            ball = balls[ball_count]

            if ball.collision( player ):
                    if player.shield:
                            ball.bounce( )
                    else:
                            game_over( screen )

The first snippet fetches the necessary variables. It then loops through all balls in the ball list. It first checks that the ball has collided with the player. If the player has the shield enabled, the ball bounces harmlessly away. Otherwise, the ball hits the robot, and the game ends.

for other_ball in balls[ ball_count: ]:
    if ball.collision( other_ball ):
            ball.bounce( )
            other_ball.bounce( )

Of course, the ball might collide with another ball. This code loops over the remaining balls in the list with a list slice (from the element with the index found in ball_count through the end of the list), so as not to repeat any calculations already processed; checks for collisions; and bounces each ball away from the collision point if necessary:

if door.visible and ball.collision( door ):
    ball.bounce( )

Balls can also collide with the door, but only if it’s visible.[21] Again, the ball bounces away on this collision:

if door.visible and door.collision( player ):
        win_game( screen )

Finally, if the door is visible, and the player collides with it, the player has won.

How do the game objects know they’ve collided? Good question.

Game Objects

The player, the balls, and the door are all game objects, represented as Python objects. Each object has its own data (or attributes) and can perform certain behaviors (known as methods). Each object belongs to a class that describes its data and behavior.

Because Player, Ball, and Door share common attributes (such as a radius) and behavior (including the ability to check for collisions and the ability to draw itself to the screen), let’s start by defining a general class, GameObject. The other three classes are more specific versions of GameObject.

class GameObject:
    def place_random( self ):
        self.rect.x = random.randint( 0, self.max_x )
        self.rect.y = random.randint( 0, self.max_y )

    def draw( self, surface ):
        surface.blit( self.image, self.rect )

This snippet starts the class definition and defines two important methods. place_random() sets the object’s location to a random position; draw() draws the image to the given surface. Both methods assume the object has several attributes, namely rect, max_x, max_y, and image. You’ll see those defined soon.

def calc_radius( self ):
    half_width  = self.rect.width  / 2
    half_height = self.rect.height / 2

    return int( math.hypot( half_height, half_width ) )

def center_point( self ):
    x_offset = int( self.rect.width  / 2 )
    y_offset = int( self.rect.height / 2 )
    return [ x_offset + self.rect.x, y_offset + self.rect.y ]

These two methods calculate the information needed for the object’s bounding sphere. calc_radius() uses the object’s width and height to draw a triangle, then uses the Pythagorean theorem to discover its hypotenuse, also the radius of the bounding circle. center_point() figures out the center point of the object, returning a two-element list: the X1 and Y coordinates of the midpoint.

Every object measures its X and Y coordinates relative to the one true coordinate system, the screen. The object’s center point is half its image’s width in pixels right and half its image’s height in pixels down from its position in the screen. It’s vitally important to use the same origin when comparing object distances.

def collision( self, other ):
    my_center    = self.center_point( )
    other_center = other.center_point( )

    delta_x      = my_center[0] - other_center[0]
    delta_y      = my_center[1] - other_center[1]
    distance     = int( math.hypot( delta_x, delta_y ) )

    if distance < self.radius or distance < other.radius:
        return distance

    return 0

Finally, collision() finds the distance between this and another game object by drawing a right triangle parallel to the X and Y axes, through their center points, and calculating the length of the hypotenuse. The objects have collided if this distance is less than either of their radii.

The Player

The first place the Player class differs from GameObject is in its constructor. It has several attributes: an image, a shield image, its rectangle, its maximum X and Y coordinates, its current move direction, a flag to mark whether its shield is up, X and Y velocities, and its radius.

As before, the velocity is the value with which a coordinate will change in one turn. That is, a ball with an X velocity of -1 is moving left; a velocity of 1 means that it is moving right. 0 indicates that the ball is stationary along the X axis.

The constructor also places the player randomly in the arena by calling place_random():

class Player( GameObject ):
    def _ _init_ _( self, image, max_x, max_y ):
        self.image      = pygame.image.load( image ).convert_alpha( )
        self.shield_img = pygame.image.load( 'force_field.png' ).\
             convert_alpha( )
        self.rect       = self.image.get_rect( )
        self.max_x      = max_x - self.rect.width
        self.max_y      = max_y - self.rect.height
        self.x_vel      = 0
        self.y_vel      = 0
        self.shield     = 1
        self.radius     = self.calc_radius( )

        self.place_random( )

To make things easier, the enable_shield() method stops the robot from moving by clearing its current X and Y velocities, then toggles the shield flag. This is a good place to put logic that depletes energy or to make special kinds of robots that can move with the shield enabled. This robot’s code is pretty simple, though:

def enable_shield( self ):
    self.x_vel  = 0
    self.y_vel  = 0
    self.shield = 1

Moving the robot is only a little more complicated than the example in [Hack #92] , only because moving disables the robot’s shield:

def move( self ):
    if self.x_vel or self.y_vel:
        self.shield = 0

    if ( self.x_vel and 0 < self.rect.x < self.max_x ):
        self.rect.x += self.x_vel

    if ( self.y_vel and 0 < self.rect.y < self.max_y ):
        self.rect.y += self.y_vel

def move_up( self ):
    self.y_vel = -1

def move_down( self ):
    self.y_vel = +1

def move_left( self ):
    self.x_vel = -1

def move_right( self ):
    self.x_vel = +1

Finally, the draw() method blits the image to the screen. It must also draw the shield image over the top of the robot, if it’s enabled:

def draw( self, surface ):
    surface.blit( self.image, self.rect )

    if self.shield:
        surface.blit( self.shield_img, self.rect )

The Ball

Like Player, Ball also has an image, a rectangle, maximum X and Y coordinates, a radius, and X and Y velocities, randomly chosen as -1, 0, or 1. The constructor is very similar to that of Player:

class Ball( GameObject ):
    def _ _init_ _( self, image, max_x, max_y ):
        self.image  = pygame.image.load( image ).convert_alpha( )
        self.rect   = self.image.get_rect( )
        self.max_x  = max_x - self.rect.width
        self.max_y  = max_y - self.rect.height
        self.x_vel  = random.randint( -1, 1 )
        self.y_vel  = random.randint( -1, 1 )
        self.radius = self.calc_radius( )

        self.place_random( )

The move() method is more complicated. It checks that the object remains in bounds and bounces the ball off any wall that it encounters. With the simple velocity scheme here, this is as easy as reversing the direction of the velocity:

def move( self ):
    x = self.rect.x + self.x_vel

    if x < 0:
        x      = 0
        self.x_vel = -self.x_vel
    elif x > self.max_x:
        x          = self.max_x
        self.x_vel = -self.x_vel

    y = self.rect.y + self.y_vel

    if y < 0:
        y          = 0
        self.y_vel = -self.y_vel
    elif y > self.max_y:
        y          = self.max_y
        self.y_vel = -self.y_vel

    self.rect.x = x
    self.rect.y = y

Finally, there’s a bounce() method the collision detection scheme uses to change the velocities of the ball without it having hit a wall. This is pretty silly, but at least it’s simple:

def bounce( self ):
    self.x_vel = -self.x_vel
    self.y_vel = -self.y_vel

The Door

The Door is the simplest class. Aside from the common attributes, it has one flag, visible, which governs whether the robot can see the door (and if the collision detection should take it into account). The entire class is the constructor:

class Door( GameObject ):
    def _ _init_ _( self, image, max_x, max_y ):
        self.image   = pygame.image.load( image ).convert_alpha( )
        self.rect    = self.image.get_rect( )
        self.max_x   = max_x - self.rect.width
        self.max_y   = max_y - self.rect.height
        self.visible = 0
        self.radius  = self.calc_radius( )

        self.place_random( )

Everything Else

Only a few odds and ends remain. There are two normal ways to exit the game: by winning or losing. The game_over() and win_game() methods provide some small notification if either has happened. The sleep() calls ensure that the messages stay visible long enough for people to read them, then the exit() calls end the program:

def game_over( screen ):
    write( screen, "BONK!  Game over.  So sorry." )
    time.sleep( 2 )
    sys.exit( )

def win_game( screen ):
    write(  screen, "YOU WIN!  Game over.  What a letdown." )
    time.sleep( 2 )
    sys.exit( )

What actually writes the message to the screen? A bit of pygame.font.Font() hackery. Given the screen to which to draw and the text of a message, write() creates a new 36-point font with the default font face, draws it in a nice blue color to a new surface, aligns the centers of the new surface and the screen, blits the new surface to the screen, and, finally, updates the screen. It takes longer to describe than to write:

def write( screen, message ):
    font          = pygame.font.Font( None, 36 )
    text          = font.render( message, 1, ( 0, 0, 255 ) )
    text_rect     = text.get_rect( )
    text_rect.centerx = screen.get_rect( ).centerx
    screen.blit( text, text_rect )
    pygame.display.flip( )

Finally, the last line of code in bouncy_robot_escape.py actually launches the program, if someone has invoked it directly from the command line. This code makes main() the main starting point:

if _ _name_ _ =  = '_ _main_ _': main( )

Hacking the Hack

Bouncy Robot Escape is pretty good, for a couple of hours of programming one afternoon and a few minutes of polish a couple of days later. It’s a long way from a finished product, though, needing several enhancements. For example, the entire game logic, right now, is effectively only a single level. Adding a title screen and multiple levels would help, as would sound effects and music.

Also, the ball collision physics aren’t quite right. Balls bounce off immovable walls appropriately, but they bounce off each other incorrectly. Fixing this means tracking their velocities and, on collision, calculating the resulting linear impulse in order to produce new velocities.

Finally, the game is too easy. With the force field as it stands, it’s always possible to beat the level with good reflexes. The force field should have limited uses, or maybe time spent shielded shouldn’t affect the door countdown. It’d also be nice to have internal walls or other stationary obstacles to navigate around.

Still, for a few hours of work, this is pretty good. Hopefully, it’s inspired you to do better and demonstrated that actually writing a game is much easier than you may have thought. It’s the polish that takes time.



[21] Either the robot is a materialist, or the door is a transport portal.