PyGame in Python Part 3.1 Walkthrough the WWII Bombing Pilot Game
So, we’ve introduced you to the basics of Python and PyGame over the last several blogs. Now it’s time to get your hands dirty and do something FUN! The next several blogs will be devoted to breaking down the game from Chapter 7 of “Game Programming: The L Line, The Express Line to Learning”.
What you will do: You will put everything together: images (sprites), sounds, user input, classes and objects, moving objects, collisions, etc. My goal will be to break it out in easy chunks.
The story: The story in the book has *obviously* been modified to be politically correct and avoid the subject it rips off: bombing islands in the South Pacific in WWII. I have no editor (clearly), so, I’ve restored the original intent to bombing anti-aircraft facilities scattered over islands. When you fly over an island, you bomb the target successfully. The clouds represent the frak from anti-aircraft cannons. If you touch them, you’ve just had shrapnel rip through parts of your plane. When you’ve been frakked 5 times, your engines fail and you take a short trip into the sea. If you’ve played any games like Modern Warfare… this should be like a trip to the ice cream parlor.
Obviously a game of this size takes a more serious approach to the structure. Additionally there are parts I really don’t care to cover in this blog (designing graphics) so I’m just going to have you take the graphics off the companion site. You could take the whole program, but, there’s no sense of accomplishment there.
So, let’s start by making a new directory under Python27/games (or whatever Python you are running) called 1945. Then go to this link: http://bcs.wiley.com/he-bcs/Books?action=resource&bcsId=3648&itemId=0470068221&resourceId=10183 and drag all the images and sounds over into your folder so we don’t have to worry about them later. Okay, that saves us a lot of trouble and explanations a book cannot avoid.
1. The bomber plane only moves LEFT and RIGHT, and is controlled through the MOUSE.
2. The background scrolls down forever (seemingly) simulating the plane flying forward.
3. Islands move down screen.
4. When the plane hits an island; we have successfully bombed the enemy and a sound will play. You get a combat bonus of $1,000.
5. Anti-Aircraft fire moves around randomly. If the plane touches it, that’s holes in the fuselage and a sound will play. You’re now 1 step closer to those great white sharks.
First, we’ll make all the sprites…
Create a file called bomber_plane.py
Create a file called bomber_plane.py """ bomber_plane step 1 of 1945 knockoff build plane sprite, control it with mouse """ import pygame pygame.init() class Plane(pygame.sprite.Sprite): def __init__(self): pygame.sprite.Sprite.__init__(self) self.image = pygame.image.load("plane.gif") self.rect = self.image.get_rect() def update(self): mousex, mousey = pygame.mouse.get_pos() self.rect.center = (mousex, 430) def main(): screen = pygame.display.set_mode((640, 480)) pygame.display.set_caption("Bomber Pilot! bomber_plane.py - creating the Plane sprite") background = pygame.Surface(screen.get_size()) background.fill((0, 0, 255)) screen.blit(background, (0, 0)) plane = Plane() allSprites = pygame.sprite.Group(plane) clock = pygame.time.Clock() keepGoing = True while keepGoing: clock.tick(30) pygame.mouse.set_visible(False) for event in pygame.event.get(): if event.type == pygame.QUIT: keepGoing = False allSprites.clear(screen, background) allSprites.update() allSprites.draw(screen) pygame.display.flip() #return mouse cursor pygame.mouse.set_visible(True) if __name__ == "__main__": main()
Let’s review some things here. At the tope we’ve imported and initialized the PyGame libraries. Then we setup the Plane class with a constructor/initialized and an update function. This is where we capture and respond to the user’s mouse. You can see we have fixed the Y-coordinate to the bottom of the screen but the X-coordinate goes where the user moves it.
In the main we build&blit the background, create an instance of the Plane class, setup the main loop and use the sprite group to manage screen updates. I haven’t gone over the sprites and sprite groups yet, so, let me summarize he explanation from PyGame’s tutorial that’s very well written:
The Sprite class is a base class for game objects. It only has methods to help it work with Group classes. The sprite keeps track of which groups it belongs to.
The Group class is a container. You can pass list of sprites to the constructor (__init__ method) to create a Group instance that contains some initial sprites. The sprites() method returns an object that can be looped on to access every sprite the group contains. The Group also has an update() method, which will call an update() method on every sprite in the group.
Next up is the Island Sprite. Hopefully you grabbed the graphics for this as I’m not going to describe the big fun of playing with pixel graphics. If you’re a programmer, I’m sure you don’t enjoy that part. Remember to give your nearest graphic designer a hug for taking on all the pain for you
Here’s a list of the behaviours we want to accomplish with this sprite:
1. Y-coordinate updates at a constant rate (it moves down)
2. X-coordinate is fixed
3. When it hits the bottom, it is re-drawn at the top
4. When it hits a plane, it is re-drawn at the top
Here’s the code:
class Island(pygame.sprite.Sprite): def __init__(self): pygame.sprite.Sprite.__init__(self) self.image = pygame.image.load("island.gif") self.rect = self.image.get_rect() self.reset() self.dy = 5 def update(self): self.rect.centery += self.dy if self.rect.top > screen.get_height(): self.reset() def reset(self): self.rect.top = 0 self.rect.centerx = random.randrange(0, screen.get_width())
Pretty basic. Notice in the update(self) function that it checks for the bottom screen “collision”. Notice in the reset(self) function how it randomly decides where to draw it at the top. It would be some kind of geographical miracle if the islands in the archipelago all lined up perfectly. Now we’ll modify the main():
def main(): screen = pygame.display.set_mode((640, 480)) pygame.display.set_caption("Bomber Pilot! bomber_plane.py - creating the Plane sprite") background = pygame.Surface(screen.get_size()) background.fill((0, 0, 255)) screen.blit(background, (0, 0)) plane = Plane() island = Island() allSprites = pygame.sprite.Group(island, plane) clock = pygame.time.Clock() keepGoing = True while keepGoing: clock.tick(30) pygame.mouse.set_visible(False) for event in pygame.event.get(): if event.type == pygame.QUIT: keepGoing = False allSprites.clear(screen, background) allSprites.update() allSprites.draw(screen) pygame.display.flip() #return mouse cursor pygame.mouse.set_visible(True) if __name__ == "__main__": main()
Now the sprite.Group thing is adding some value. We we’ll just continue to add sprites to this and let the Group functions do the heavy lifting of updating all it’s contained sprites.
We’ll end with the third sprite, the Cloud. Again, we need to figure out the rules of the cloud:
1. Y-coordinate will move at random rates (speed)
2. X-coordinate moves (side to side)
class Cloud(pygame.sprite.Sprite): def __init__(self): pygame.sprite.Sprite.__init__(self) self.image = pygame.image.load("Cloud.gif") self.image = self.image.convert() self.rect = self.image.get_rect() self.reset() def update(self): self.rect.centerx += self.dx self.rect.centery += self.dy if self.rect.top > screen.get_height(): self.reset() def reset(self): self.rect.bottom = 0 self.rect.centerx = random.randrange(0, screen.get_width()) self.dy = random.randrange(5, 10) self.dx = random.randrange(-2, 2)
Nothing super new going on here. You can see the only collision we’re coding for is the bottom of the screen when we reset it. We don’t care if they move off the side. You see us having a variable “speed” with the line: self.dy = random.randrange(5, 10). The next line picks a random side to side drift.
def main(): screen = pygame.display.set_mode((640, 480)) pygame.display.set_caption("Bomber Pilot! bomber_plane.py - creating the Plane sprite") background = pygame.Surface(screen.get_size()) background.fill((0, 0, 255)) screen.blit(background, (0, 0)) plane = Plane() island = Island() cloud = Cloud() allSprites = pygame.sprite.Group(island, plane, cloud) clock = pygame.time.Clock() keepGoing = True while keepGoing: clock.tick(30) pygame.mouse.set_visible(False) for event in pygame.event.get(): if event.type == pygame.QUIT: keepGoing = False allSprites.clear(screen, background) allSprites.update() allSprites.draw(screen) pygame.display.flip() #return mouse cursor pygame.mouse.set_visible(True) if __name__ == "__main__": main()
Again, we’ve added the cloud sprite to the group allSprites and we’re done. Pretty awesome. Okay, so if you run the program now, you’re going to see something that’s not Call of Duty, but, far and away cooler than even the Mandelbrot set (unless you’re a Math person and then Mandelbrot is still way, way cooler):
Next time we’ll move on building sprites and adding sounds to the collissions. Thanks for making it this far. I’m still trying to find the best way to display code in these posts. So far, I like screenshots best for visuals, but it leaves a lot to be desired since you’d have to copy it by hand. I’ll figure it out eventually.
Python PyGame:2.1 Mandelbrot (a short aside)
In my last blog I started off with a game that draws circles of different colors. That is boring. Personally, I never tire of rewriting programs to draw fractals. Today we’ll quickly jump through a program to draw the Mandelbrot set. Drawing 2d fractals may be so 1980, but, I get a kick out of it.
What is a fractal? Simply put, a fractal is worlds within worlds. It’s a geometric shape that no matter how far you zoom into or out of still holds the original shape. It’s a fairly new term coined by Benoit Mandelbrot in 1982 in his book, “The Fractal Geometry of Nature”.
More importantly, fractals are the closest approximation we have come up with for a Mathematics of Nature. They can approximate clouds, coastlines, snowflakes and earthquakes. It’s related to the popularization of Chaos Math by Edward Lorenz and his study of weather prediction in 1961. I cannot claim understanding, but, I do find it fascinating to ponder over. More on that after the code if you’re interested.
Python & PyGame by themselves don’t just carry around the code necessary to pull this off. This gives us an excuse to play with two cool libraries: NumPy and PIL (Python Imaging Library).
Once again, if you’re running Windows 32 bit, life is obvious, if you’re running 64 bit, you must download them from here:
Next I happened across a version of the program written in 2006. Updated it a bit to work with the newer libraries, and the result:
That’s much cooler than a bunch of circles.
Let’s quickly review what some of those NumPy functions do.
Arange – numpy.arange([start], stop[, step], dtype=None, maskna=False)
returns an evenly spaced set of values. Sp, arange(3)=[0,1,2]
Ravel – numpy.ravel(a, order=’C’)
Returns a flattened array. So a two dimensional array of  and [abc] is returned as [123abc]
Shape – ndarray.shape
Tuple of array dimensions.
Zeros – numpy.zeros(shape, dtype=float, order=’C’)
Return a new array of given shape and type, filled with zeros.
Here’s some further amateur discussion:
Free Will or Determinism is one of philosophy’s Coke vs. Pepsi discussions. You’ve seen it again and again through popular media: books (Ender’s Game) or movies (Matrix, Inception). In mathematics it has shown up as well. Newtonian Mechanics is pro-determinism. Then Quantum Mechanics became popular, which many felt was more aligned with Free Will. Then Chaos Math came back as a pro-determinism mathematics that can solve a lot of seemingly “random” problems in math: cloud formations, turbulence, weather patterns. The fractal is a mainstay in Chaos Math. Totally predictable and matches up with a lot of patterns in nature. Unfortunately there remain many unsolved problems in these mathematics, but it is fun for me to think about when I’m having trouble with a simple coding problem.
Here’s some more links:
This web page explains the math, theory, and how to do this in c++:
Python PyGame: 2
In my first blog I covered the basics of getting PyGame installed on your Widnows machine with Python 2.7. We also went over the core methodology of IDEA: ALTER presented in the “Game Programming The Express Line to Learning” book I’m using to start this series. For review:
- IDEA (the game framework)
- 1.Import & Initialize
- 2.Display Configuration
- 4.Action (broken into the ALTER steps)
- 1.Assign values to key variables
- 2.Loop (infinite w/breakout clause)
- 3.Timer (setup framerate)
- 4.Event Handling
- 5.Refresh Display
Today I’m going to take us a step further into the game development world with three programs. All of these are covered in great depth in the book, so, my purpose is simple introdcution to the concepts and you can walk through it with me if you like. I am a believer that if you do anything 10,000 times you’ll be much farther advanced than today, so, it may feel tedious to type the code out, but over time, you’ll engrain it into your brain.
The book itself is written largely towards total beginners to programming, so, I am sparsely choosing which ones I do. The three programs today are drawCircles, mouseEvents, and Paint. So, you’ll see how to draw stuff on your canvas, interact with the user through keyboard and mouse, and then put it all together.
Let’s check out the first program, drawCircles:
Two points of interest. We’re using PyGame’s draw command to create a lot of circles of various colors and starting at different points. Secondly, we’re demonstrating out ability to save files back to a folder (in this case we’re converting the screen into a bitmap). One additional thing is the use of a function call as opposed to just doing everything in one go. It’s not so impressive here, but, it will be more handy when we begin writing larger programs or using object oriented techniques.
Go ahead and run this and look in your file folder. You’ll find this image:
Personally, whenever I get a chance to draw things I inevitably want to draw fractals like it’s 1980. I’ll post a small addendum later to do that.
Okay, so the second program is called mouseEvents.py. It grabs a few user events and prints them to the console. Let’s take a look at the code:
So, you can see the IDEA:ALTER is still working for us here (it will keep working for us too). Outside of the normal stuff, we’re using the PyGame Event Handler to grab when the user hits the Close button, presses a key down, or clicks the mouse. One thing of interest is that the simple pygame.KEYDOWN event is all you need to create a keylogger and steal people’s passwords all day long. There’s a reason Python is a popular language among black and white hat hackers. It’s easy, light, and fast.
Run this program and you’ll be able to see the console spit out everything you do:
That’s good fun…
Okay now let’s put them together into a Paint program. This silly task shows us a lot of good features of game programming. Let’s take a look at the first part where we define some functions:
The first function is interfacing with the input from the user to determine what to do. Primarily what color and line width to use for the paint brush. The second function is reporting back what color and width the user has chosen. Setting the MyFont to the “None” of SysFont means it will use the default font on whatever system is running the program. That’s a handy thing to keep in mind.
Okay, the second part defines the main function:
So once we are inside the main loop, we check first to see if the user has quit, and then handle the mouse and keyboard. If any key is pressed we update the values of myData. If the mouse is held down we use pygame’s draw command and feed it MyData’s variables. Result? Paint:
Okay, so, that’s it for today. With these kind of fundamentals we are ready to tackle some bigger concepts and move towards more object oriented games. See you next time.