GameEngine is a simple but powerful and easy module that speeds up game development using Python and Pygame. It's still handy to know these two technologies, but GameEngine provides some very nice features of its own, including:
The gameEngine module is powerful and fun to use. The GameEngine module is fully described in the book "Game Programming - The L Line" by Andy Harris (2007, Wiley Press.)
Even if you haven't read all the way through the book, you can use this module to create some interesting and fun games. Of course, if you want to learn more about how all this works and how you can make your own improvements, be sure to check out a copy of the book!
The easiest way to use gameEngine is to simply copy the gameEngine file into your program's working directory. You can then import gameEngine to have access to all the features of the module. If you want to distribute a game using gameEngine, just include gameEngine.py.
Please check Appendix C for more information on packaging and distributing your modules and games.
If you really like gameEngine, you can install it as a module, and then it will be available to all your Python programs, just like pygame.
I've included a file on the website called gameEngine-1.1win32.exe. If you're running Windows, you can simply download that program, run it, and it will automatically install gameEngine into your Python directory.
If you can't use the automatic installer, here's how you install gameEngine as a module:
python setup.py install
This program will automatically copy gameEngine.py to an appropriate spot in your Python system so it will be available to all your Python programs.
The setup.py program and installation process are described in Appendix C, including how to make that nifty automatic installer.
The gameEngine Scene class is a simple but powerful tool that encapsulates the IDEA/ALTER framework (described in the book.) You can make an instance of the Scene class right away, or you can extend the Scene class to make a custom starting point for your games.
Once you've created a Scene, you can add sprites to its sprites attribute. You can also modify the background object, and even make new sprite lists that will all automatically be updated at the right time. You can add any sprites you wish to a scene, regular sprites, instances of the SuperSprite class, gameEngine widgets, or extensions of any of these things.
When you want the game to begin, call your scene instance's start() method to get things rolling. Of course you can stop a scene with the stop() method. One program can have multiple scenes to handle various states, sub-games, instruction screens, or whatever you want.
If you've created an extension of the Scene class, you can override one or both of two event-handling functions. The doEvents() method passes a copy of the event object from the main loop so you can do any traditional event-handling there. The update() method runs every frame (just like pygame.sprite.Sprite's update() method) so you can use it to put any code you wish.
Table 1 describes the Scene attributes which can be modified externally.
The following chart describes the Scene attributes which can be modified.
Name | Type | Description |
---|---|---|
background | Surface | The background drawing surface of the scene. |
screen | Surface | The primary drawing surface of the scene. |
sprites | List of sprite objects | The primary sprite group. You can create other sprite groups with the Scene class's makeSpriteGroup() and addGroup() methods. |
Table 2 shows the publicly accessible methods of the Scene object.
Name | Parameters | Description |
---|---|---|
__init__() | none | Initiator. If you're writing an extension of the Scene class, be sure to call Scene.__init__(self) at the beginning of the method. |
start() | none | Starts the __mainLoop() method. |
stop() | none | Ends the Scene object's main loop and sends control back to the calling program. |
makeSpriteGroup(sprites) | sprites: a list of sprites to add to the new sprite group. | Returns a sprite group. |
addSpriteGroup(group) | group: a sprite group created through the Scene class's makeSpriteGroup() method or the pygame.sprite.Group() | Adds the group to the Scene's list of groups. Group is automatically cleared, updated, and redrawn inside main loop. |
setCaption(title) | title: a string containing new caption | Sets the window caption to the text specified by title. |
The Scene methods shown in Table 3 are meant to be overridden in subclasses of Scene. Both are empty in the standard Scene class. They are used to add event or collision functionality to extensions of the Scene class.
Name | Parameters | Description |
doEvents(event) | event: the event object passed to the doEvents() method. | Override this method to write code that has access to the event object and runs every frame. |
update() | none> | Override this method to write code that runs every frame. Note that doEvents() is called before update(). |
The gameEngine Label class is a lightweight but powerful GUI widget. Use a label instance when you want to easily place text on the screen. All Label control is done through its attributes. All attributes have a default value, so you don't absolutely have to set any of them (but you should for best results). You can change any Label attributes even after the label has been displayed.
Name | Type | Description |
---|---|---|
font | pygame.font.Font instance | The font and size that will be used in this label. |
text | string | Text to display. |
fgColor | color tuple (R, G, B) | Foreground color of text. |
bgColor | color tuple (R, G, B) | Background color behind text. |
center | position tuple (x, y) | Position of label center, used to position entire label. |
size | size tuple (width, height) | Used to set label's size. |
The gameEngine Button class is an extension of the Label class that has the added ability to detect mouse clicks. It has two Boolean attributes for detecting mouse activity. active is True if the mouse's left button is down over the button. clicked is True if the mouse left button was pressed and released over the button.
The default background color is set to gray to differentiate it from the Label, but you can change it to whatever you wish. Table 5 lists the attributes you can modify.
Name | Type | Description |
---|---|---|
font * | pygame Font class | The font that will be used in this label. |
text * | string | Text to display. |
fgColor * | color tuple (R, G, B) | Foreground color of text. |
bgColor * | color tuple (R, G, B) | Background color behind text. |
center * | position tuple (x, y) | Position of label center, used to position entire label. |
size * | size tuple (width, height) | Used to set label's size. |
* This attribute is inherited from the Label class.
The Button class has two Boolean attributes that are not intended to be changed from the outside, as shown in Table 6. Instead, these attributes report the current status of the button.
Name | Type | Description |
---|---|---|
Active | Boolean | True if mouse button is down and mouse is over button rect. |
Clicked | Boolean | True if mouse was pressed and released over button rect. |
gameEngine includes a simple scroller that serves the purpose of a scroll bar in traditional GUI environments. The main purpose of the scroller is to act as a graphical way to edit and view numerical input. The Scroller class is an extension of the Button class, so it has all the characteristics of Button (including those inherited from the Label class). Scroller also has its own attributes used to control the scroller's behavior (see Table 7).
The scroller has a numeric value attribute that ranges from minValue to maxValue. If the user clicks the mouse on the left half of the scroller, the value is reduced by increment. If the user clicks on the right half, the value is increased by increment.
Name | Type | Description |
---|---|---|
font * | pygame.font.Font object | The font that will be used in this label. |
text * | string | Text to display. |
fgColor * | color tuple (R, G, B) | Foreground color of text. |
bgColor * | color tuple (R, G, B) | Background color behind text. |
center * | position tuple (x, y) | Position of label center. Used to position entire label. |
size * | size tuple (width, height) | Used to set label's size. |
Value | integer or float | The numeric value that the scroller displays. |
minValue | integer or float | Minimum value attainable by scroller. Can be negative. |
maxValue | integer or float | Maximum value attainable by scroller. |
Increment | integer or float | If mouse is currently clicked, change amount this amount. |
* This attribute is inherited from the Label class.
The scroller also inherits the active and clicked attributes from the Button class, but these are not meant to be used externally.
The SuperSprite class is a powerful and flexible extension of the pygame.sprite.Sprite class. SuperSprite incorporates many of the sprite features described throughout this book. For example, you can set a SuperSprite instance's speed and direction, and the sprite will not only rotate automatically to face that direction, but also travel in that direction at the indicated speed. If you prefer, you can indicate the sprite's dx and dy attributes, and it will move accordingly.
The SuperSprite class has five different boundary-checking behaviors built in. It also has built-in functions to speed up, slow down, and rotate. If you want to use complex physics, you can add a force vector to the sprite, and this new vector will be added to the sprite's current motion vector.
SuperSprite has a couple of features designed for collision detection. You can check to see if the sprite collides with a particular sprite or an entire group of sprites. If you need more specific information, you can easily determine the distance between the sprite and any other point, or the angle between the sprite and a point.
A few utility methods round out the SuperSprite class's bag of tricks. It has a method that prints out the sprite's current position and motion vector (useful for debugging). It also has Boolean methods that can determine whether the sprite is currently being dragged or clicked. Finally, you can change the sprite's image attribute easily to make the sprite handle any bitmap.
The SuperSprite object (like the Scene object) can be instantiated as is, or it can be extended to form your own class based on SuperSprite. If you extend SuperSprite, you can add your own event-handling code. Although the SuperSprite is a powerful beast, it's pretty easy to use. The upcoming section offers a rundown of its attributes.
The SuperSprite object has some attributes that could conceivably be referred to from outside the method, but generally there isn't a good reason to do so. If possible, use methods to control the behavior of the SuperSprite object.
The one place I tend to use SuperSprite attributes directly is in the dx and dy attributes. If you do the same, remember also to call the updateVector() method to make sure your changes are recorded. (Of course, if you use the built-in vector-changing methods, you won't need to do this.)
The SuperSprite has a lot of methods (29 public and 4 private). To manage all these choices, I'll break them into categories.
Whenever you use the SuperSprite object, you'll almost always use a few extremely common methods @@md which are shown in Table 8.
Name | Parameters | Description |
---|---|---|
__init__(scene) | scene: a gameEngine Scene object. The scene to which this instance is associated | Initializes the SuperSprite instance. If you are extending the class, be sure to call SuperSprite.__init__(self) at beginning of method. |
setImage(image) | image: the filename of an image (If the image has a front, the front of the image should face East.) | Used to set the image attribute. Creates an image master that can be automatically rotated as needed. |
setPosition(position) | position: (x, y) coordinates of the desired position on-screen | Moves the sprite immediately to the given coordinates. |
setSpeed(speed) | speed: The number of pixels that the sprite will travel per frame | Sets the speed to any arbitrary value, including negative values. No speed limits imposed. |
speedUp(amount) | amount: the pixel-per-frame increase in speed. Can be floating-point and/or negative value. (Negative will slow the sprite down and eventually cause the sprite to move backward.) | Changes the speed by amount pixels per frame. Respects speed limits (@@nd3 to +10 by default, or changed by setSpeedLimits()). |
setAngle(dir) | dir: an angle in degrees (East is 0, increases counterclockwise) | Arbitrarily sets both the visual rotation and the direction of travel to dir. |
turnBy(amount) | amount: an angle in degrees (negative values turn clockwise, positive values turn counterclockwise) | Changes both visual rotation and direction of travel by amount degrees. |
Sometimes you need more specific controls for the sprite's motion. The methods shown in Table 9 give you several techniques for altering or setting the sprite's motion vector.
Name | Parameters | Description |
---|---|---|
setDX(dx) | dx: new dx value | Changes the dx attribute of the sprite. Use this method instead of directly assigning a dx value. |
setDY(dx) | dy: new dy value | Changes the dy attribute of the sprite. Use this method instead of directly assigning a dy value. |
addDX(ddx) | ddx: change in dx | Changes the dx attribute of the sprite by the indicated amount. |
addDY(ddx) | ddy: change in dy | Changes the dy attribute of the sprite by the indicated amount. |
setComponents((dx, dy)) | dx, dy: new motion-vector components | Assigns dx and dy in one step. |
updateVector() | none | If you must set dx or dy attributes directly, this method makes the change permanent when invoked. |
moveBy(vector) | vector: motion vector in component form (dx, dy) | Moves the sprite according to the vector without affecting rotation, direction, or speed. |
addForce(amt, angle) | amt: length of force vector angle: angle of force vector | Adds force vector to current speed and direction of travel. Used for simulating skidding, gravity, and so on. |
forward(amt) | amt: number of pixels to move. | Moves in the current facing direction. Does not change speed. |
rotateBy(amt) | amt: degrees of rotation to add to visual rotation | Changes visual rotation without affecting direction of travel. |
Table 10 lists several methods for adding handy features to the SuperSprite.
Name | Parameters | Description |
---|---|---|
setBoundAction(action) | action: one of the following constants: WRAP around screen BOUNCE off boundary STOP at edge of screen HIDE offstage and stop CONTINUE indefinitely | Sets the boundary action to the specified value. If none is specified, then WRAP is the default behavior. |
setSpeedLimits(max, min) | max: maximum speed (pixels per frame) min: minimum speed (pixels per frame) | Sets the upper and lower speed limits. Respected by the speedUp() method. |
mouseDown() | none | Returns True if mouse button is currently pressed over sprite. Can be used for drag-and-drop. |
clicked() | none | Returns True if mouse button is pressed and released over sprite. Makes sprite act like a button. |
collidesWith(target) | target: any sprite (ordinary or SuperSprite) | Returns True if colliding with target. |
collidesGroup(group) | group: a sprite group | Returns True if a sprite in the group was hit in this frame, or None if there are no collisions between sprite and group. |
distanceTo(point) | point: a specific set of (x, y) coordinates on-screen | Determines the distance from the center of the sprite to point. |
dirTo(point) | point: a specific set of (x, y) coordinates on-screen | Determines direction from center of sprite to point. |
dataTrace() | none | Prints x, y, speed, dir, dx, and dy. Used for debugging. |
The SuperSprite has one method that is meant to be overridden in subclasses of SuperSprite. Like the update() event in Scene, the checkEvents() method is designed to be overridden. Use this method to add event handling to descendants of SuperSprite. If you have a boundary-checking situation that isn't covered by the standard boundary actions, you can also override checkBounds() (see Table 11).
Name | Parameters | Description |
---|---|---|
checkEvents() | none | Overwrite this method to add code that will run on each frame (usually event- or collision-handling code). |
checkBounds() | none | Overwrite this method if you need a boundary-checking technique that isn't covered in the built-in techniques. |
The SuperSprite (since version 1.3) now has a simple but effective state mechanism. You can add any number of states to a sprite. Each state consists of a name (which must be unique) and an image (which can be duplicated).
When you create a state you indicate its name and the associated image. You can then use setState(name) to set the sprite to the indicated state. The image associated with that state will automatically appear, and this image will be used for all automatic rotations and blitting.
If you want to know what state the sprite is currently in, use the getState() method.
States can be used both to change the visual appearance of a sprite (thrusters on, turning left) and to manage the behavior of a sprite (EG you may want active and dormant states that look the same but act differently.)
Name | Parameters | Description |
---|---|---|
addState(stateName, imageFileName) |
stateName: unique string describing state imageFileName: file name of image for state |
Set a state with the given name and image |
setState(stateName) | stateName: state to switch to | switches sprite to given state |
getState() | none | Return sprite's current state |
The following programs illustrate the gameEngine's features in more detail. (For a complete description of these programs, please see Chapter 10.)
The simpleGE.py program is the gameEngine equivalent to the traditional "Hello World" test program:
""" simpleGE.py example of simplest possible game engine program """ import pygame, gameEngine game = gameEngine.Scene() game.start()
This program uses a standard instance of the Scene class. The default instance has a SuperSprite object built in (which you will usually replace with your own sprites). Even with this extremely limited code, the program runs and the sprite moves around on the screen.
The SuperSprite class is a powerful tool, but if you want to add event-handling capability, you'll need to extend it. The carGE.py program shows how this is done:
""" carGE.py extend SuperSprite to add keyboard input """ import pygame, gameEngine class Car(gameEngine.SuperSprite): def __init__(self, scene): gameEngine.SuperSprite.__init__(self, scene) self.setImage("car.gif") def checkEvents(self): keys = pygame.key.get_pressed() if keys[pygame.K_LEFT]: self.turnBy(5) if keys[pygame.K_RIGHT]: self.turnBy(-5) if keys[pygame.K_UP]: self.speedUp(.2) if keys[pygame.K_DOWN]: self.speedUp(-.2) def main(): game = gameEngine.Scene() game.background.fill((0xCC, 0xCC, 0xCC)) car = Car(game) game.sprites = [car] game.start() if __name__ == "__main__": main()
The Car class is simply an extension of the SuperSprite class with the checkEvents() method overwritten. To add the custom class, create an instance of the Car class with the scene instance as its argument, add the car to the scene's sprites list, and start up the scene.
You can also customize a sprite to get a version of drag-and-drop behavior, as shown in dragDrop.py:
""" dragDrop.py illustrate click and mouseDown methods with gameEngine """ import pygame, gameEngine class Ball(gameEngine.SuperSprite): def __init__(self, scene): gameEngine.SuperSprite.__init__(self, scene) self.setImage("ball.gif") def checkEvents(self): #drag and drop if self.mouseDown(): self.setPosition(pygame.mouse.get_pos()) def main(): game = gameEngine.Scene() ball = Ball(game) game.sprites = [ball] game.start() if __name__ == "__main__": main()
The Ball class is an extension of SuperSprite. I use its mouseDown() method to determine whether the mouse pointer is currently over the sprite. If it is, I set the position to the mouse pointer's current position.
Sometimes it's more convenient to customize the scene, particularly when you're working with ordinary sprites or the GUI components. The guiDemoGE.py program illustrates all the GUI elements. I put them together using an extension of the Scene class.
""" guiDemoGE.py demonstrates the GUI objects in gameEngine """ import pygame, gameEngine class Game(gameEngine.Scene): def __init__(self): gameEngine.Scene.__init__(self) self.addLabels() self.addButton() self.addScroller() self.addMultiLabel() self.sprites = [self.lblTitle, self.label, self.lblButton, self.button, self.lblScroller, self.scroller, self.multi] def addLabels(self): self.lblTitle = gameEngine.Label() self.lblTitle.text = "GameEngine GUI Demo" self.lblTitle.center = (320, 40) self.lblTitle.size = (300, 30) self.label = gameEngine.Label() self.label.font = pygame.font.Font("goodfoot.ttf", 40) self.label.text = "Label" self.label.fgColor = (0xCC, 0x00, 0x00) self.label.bgColor = (0xCC, 0xCC, 0x00) self.label.center = (320, 100) self.label.size = (100, 50) def addButton(self): self.lblButton = gameEngine.Label() self.lblButton.center = (200, 180) self.lblButton.text = "Button" self.button = gameEngine.Button() self.button.center = (450, 180) self.button.text = "don't click me" def addScroller(self): self.lblScroller = gameEngine.Label() self.lblScroller.text = "scroller" self.lblScroller.center = (200, 250) self.scroller = gameEngine.Scroller() self.scroller.center = (450, 250) self.scroller.minValue= 0 self.scroller.maxValue = 250 self.scroller.value = 200 self.scroller.increment = 5 def addMultiLabel(self): self.multi = gameEngine.MultiLabel() self.multi.textLines = [ "This is a multiline text box.", "It's useful when you want to", "put larger amounts of text", "on the screen. Of course, you", "can change the colors and font." ] self.multi.size = (400, 120) self.multi.center = (320, 400) def update(self): if self.button.clicked: self.lblButton.text = "Ouch!" self.lblScroller.center = (self.scroller.value, 250) def main(): game = Game() game.start() if __name__ == "__main__": main()
The rgbScroller.py program shows an RGB-color-choosing application that uses the scroller component.
""" rgbScroller.py demonstrates use of scrollers to make a color picker """ import pygame, gameEngine class ColorScr(gameEngine.Scroller): def __init__(self): gameEngine.Scroller.__init__(self) self.minValue = 0 self.maxValue = 255 self.value = 255 self.increment = 5 self.format = "<< %d >>" class Game(gameEngine.Scene): def __init__(self): gameEngine.Scene.__init__(self) #make scrollers self.scrRed = ColorScr() self.scrRed.center = (320, 200) self.scrGreen = ColorScr() self.scrGreen.center = (320, 240) self.scrBlue = ColorScr() self.scrBlue.center = (320, 280) self.sprites = [self.scrRed, self.scrGreen, self.scrBlue] self.setCaption("RGB Scrollers") def update(self): red = self.scrRed.value green = self.scrGreen.value blue = self.scrBlue.value self.background.fill((red, green, blue)) #blit the background self.screen.blit(self.background, (0, 0)) def main(): game = Game() game.start() if __name__ == "__main__": main()
This program uses three instances of the Scroller class. The GUI objects don't have any event handling built-in, so I extend the Scene class and use its update() method to handle the event-handling duties.
A complete game will usually include several custom Scene objects. You might have one scene to handle the instructions, another for the game itself, and a third for reporting the player's progress. The Scene class is easy to extend, and your game can have as many as you need. Simply write your main loop code to control which scenes are available when.
The betterAsteroids.py game illustrates all these features. It's still not quite a complete game (sound effects and a more effective control system are obvious needs), but it does illustrate how to create a multi-state game. Take careful note of the special carrier object used to pass information between scenes. Most of the data is encapsulated inside individual scenes, but two pieces of information should be shared among scenes: the player's score and his/her intention to keep going. I placed both variables as attributes in a small object that preserves its value while the various scenes are created and destroyed.
""" betterAsteroids.py Show more features of gameEngine demonstrates multiple states, multiple sprite groups, carrier object """ import pygame, gameEngine, random class Ship(gameEngine.SuperSprite): """ player avatar. standard space controls arrows to turn, accel, space to fire """ def __init__(self, scene): gameEngine.SuperSprite.__init__(self, scene) self.setImage("ship.gif") self.setSpeed(0) self.setAngle(0) def checkEvents(self): keys = pygame.key.get_pressed() if keys[pygame.K_LEFT]: self.rotateBy(5) if keys[pygame.K_RIGHT]: self.rotateBy(-5) if keys[pygame.K_UP]: self.addForce(.2, self.rotation) if keys[pygame.K_SPACE]: self.scene.bullet.fire() class Bullet(gameEngine.SuperSprite): """ bullet fired from spacecraft """ def __init__(self, scene): gameEngine.SuperSprite.__init__(self, scene) self.setImage("bullet.gif") self.imageMaster = pygame.transform.scale(self.imageMaster, (5, 5)) self.setBoundAction(self.HIDE) self.reset() def fire(self): self.setPosition((self.scene.ship.x, self.scene.ship.y)) self.setSpeed(12) self.setAngle(self.scene.ship.rotation) def reset(self): self.setPosition ((-100, -100)) self.setSpeed(0) class Rock(gameEngine.SuperSprite): """ asteroid. Rotates, wraps, and generally gets in the way """ def __init__(self, scene): gameEngine.SuperSprite.__init__(self, scene) self.setImage("rock.gif") self.reset() def checkEvents(self): self.rotateBy(self.rotSpeed) def reset(self): """ change attributes randomly """ #set random position x = random.randint(0, self.screen.get_width()) y = random.randint(0, self.screen.get_height()) self.setPosition((x, y)) #set random size scale = random.randint(10, 40) self.setImage("rock.gif") self.imageMaster = \ pygame.transform.scale(self.imageMaster, (scale, scale)) self.setSpeed(random.randint(0, 6)) self.setAngle(random.randint(0, 360)) self.rotSpeed = random.randint(-5, 5) class Game(gameEngine.Scene): """ primary gameplay state manages player, bullet, rocks returns score in carrier object """ def __init__(self, carrier): gameEngine.Scene.__init__(self) self.carrier = carrier self.ship = Ship(self) self.bullet = Bullet(self) self.numRocks = 10 self.points = 2000 self.lblPoints = gameEngine.Label() self.lblPoints.center = (100, 30) self.lblPoints.text = "%d" % self.points self.lblPoints.fgColor = (0xFF, 0xFF, 0xFF) self.lblPoints.bgColor = (0, 0, 0) self.rocks = [] for i in range(self.numRocks): self.rocks.append(Rock(self)) self.rockGroup = self.makeSpriteGroup(self.rocks) self.addGroup(self.rockGroup) self.sprites = [self.lblPoints, self.bullet, self.ship ] self.setCaption("asteroids") def update(self): rockHitShip = self.ship.collidesGroup(self.rocks) if rockHitShip: rockHitShip.reset() rockHitBullet = self.bullet.collidesGroup(self.rocks) if rockHitBullet: rockHitBullet.kill() if len(self.rockGroup) <= 0: self.stop() self.carrier.score = self.points self.bullet.reset() self.points -= 1 self.lblPoints.text = "%d" % self.points if self.points <= 0: self.stop() score = 0 class Intro(gameEngine.Scene): """ introduction. Simply sets the stage """ def __init__(self): gameEngine.Scene.__init__(self) instructions = gameEngine.MultiLabel() instructions.textLines = [ "You are caught in an asteroid", "field. Use your blasters to ", "destroy the asteroids.", "", "Blow them up quickly for more " "points."] instructions.size = (400, 300) instructions.fgColor = (0xFF, 0xFF, 0xFF) instructions.bgColor = (0, 0, 0) instructions.center = (320, 200) self.button = gameEngine.Button() self.button.center = (320, 400) self.button.text = "Play" self.sprites = [instructions, self.button] self.setCaption("Asteroids") def update(self): if self.button.clicked: self.stop() class Report(gameEngine.Scene): """ reports the player's score, determines if player wants to try again score and data passed in carrier object """ def __init__(self, carrier): gameEngine.Scene.__init__(self) self.carrier = carrier lblPoints = gameEngine.Label() lblPoints.center = (320, 240) lblPoints.fgColor = (0xFF, 0xFF, 0xFF) lblPoints.bgColor = (0, 0, 0) lblPoints.size = (300, 50) lblPoints.text = "Score: %d points" % carrier.score self.btnAgain = gameEngine.Button() self.btnAgain.text = "play again" self.btnAgain.center = (100, 400) self.btnQuit = gameEngine.Button() self.btnQuit.text = "quit" self.btnQuit.center = (540, 400) self.sprites = [lblPoints, self.btnAgain, self.btnQuit] def update(self): if self.btnAgain.clicked: self.carrier.goAgain = True self.stop() if self.btnQuit.clicked: self.carrier.goAgain = False self.stop() class Carrier(object): """ an object meant to hold multi-state data: score: current number of points goAgain: boolean continue """ def __init__(self, score, goAgain): self.score = score self.goAgain = goAgain def main(): intro = Intro() intro.start() carrier = Carrier(20000, True) while carrier.goAgain: game = Game(carrier) game.start() report = Report(carrier) report.start() if __name__ == "__main__": main()