In the first tutorial, we created a simple window using Python and Pygame. The next step, will be building on that example, we will create a star field background, then write the code to animate it. One important point about these tutorials is the assumption that anyone following along already has at least a basic knowledge of how Python code is structured. If that is not the case, then you should, at the very least, want to have the Python documentation(included in the Python install) at hand incase you wanted it for a reference. There are some very good resources available for Python, either through THE OFFICIAL PYTHON WEB-SITE or by completing a Google search on the Python version you have installed. If you have already coded the first tutorial you should do just fine with this one. If there are any comments about the tutorials, whether they are critical or complimentary, you’re welcome to post them. If there’s information that I have glossed over or skipped entirely, let me know, and I will add it in, with future edits of the tutorials.
I typically use Editra v0.7.12 to write Python code, when including the psyco try/except block, Editra would occasionally crash. When I ran the starfield.py file in Python IDLE 2.6, it never crashed(but you need to have the pygame.quit() statement, so the app window does not crash). If you’re using a different IDE your mileage may vary, if in doubt, use IDLE as your benchmark(or use IDLE to run this example).
UPDATE: Psyco is no longer supported as of March 12, 2012. If you are already using it you can choose to include it, if you’re not using psyco then avoid the install unless your confident that you can make it work.
Skip the source code in red, it is the psyco try/except block!
One point of reference is that pygame deals with drawing in the same way that a cartoon animator would. The objects that would display more activity are on the top-most layers. Things that would be further in the background are draw at the bottom most layers(things like distant mountains or scenery). For this instance, our interpretation of outer space is the background to the game window. By drawing the background first, more active content (characters, moveable/static objects, and explosions) will reside above(or on top of the background). A NPC(non-playable character) or the character controlled by the gamer, using a game-pad or keyboard input, tends to be drawn later in the game loop, due to the higher level of activity.
Figure 1 is a simplified illustration of how this works, in pygame each box is a drawing surface(layer), the closer the surface is to the gamer, the more likely there will be more activity at that layer, therefore the longer the delay in the game loop until the object is drawn.
A real-world example, such as a 2D platformer could use a more complex structure but follow the same principle. The background(sky) layer is enabled to move in the opposite direction of the player, but at a slower rate, to reinforce the players horizontal movement. The next higher level, let’s say is a semi-transparent cloud layer, is set to move at a slower constant rate, but it’s independent of player’s direction. This type of structure tends to reinforce a sense of “depth of field” for the player. The layer, positioned above(in front of) the cloud layer, but below (behind) the player could contain static/ non-static platforms and the ground-line the player traverses. The non-static platforms would move, vertically or horizontally(at a steady rate), while the static platforms and ground could move at the same rate of the background. With all these aspects working together, the player gets a more vivid sense of movement in the game window, helping to make the gaming experience more immersive.
With the above information as the preliminary background for what we will be doing, let’s take a look at the code for this example. The source code will be first explained by logically breaking down each section, then the entire source code file will be presented at the end. In comparison to the first tutorial which had a total of 45 lines, this example will be a little more than twice the size, at 108 lines. An important concept to note; there is always more than one way to attack a programming task. Feel free to modify and post your results. The basis of this code if from the stars.py example included with the install of pygame. In the first example, to create a basic window we simply used one function, defined as main, and placed the bulk of the code there and at the end we simply called the code at lines 45 and 46 to execute the program.
|45||if __name__ == ‘__main__’:|
This will be the method of explaining the code, The smaller box is the line number, it is not included when writing the code, it is simply a reference number for the code’s position. It also allows for a much more precise and understandable explanation.
The source code that we will be covering, we will be calling the file starfield.py(you can name the file anything you like, to me this seemed the logical choice) and the first block of code we will actually write are the include statements and a try block. The includes are previously created python modules(programs) that are included in our code to add functionality for us to use. The try block is a test condition, in this instance, we will be checking if the Python module psyco is available, if it is, then use it, if not, print a message(to the console) and continue. Not having psyco will not crash the program, it will just run much slower(about twice as slow). Psyco is a Python module(it can be found at PyPI or Sourceforge) that speeds up the execution of compiled byte-code. It is only used for Python 2.6 (which has been around for some time and has a sizeable repository of additional modules for it). Those using Python 3.x do not need psyco to run the starfield.py program (that’s why it’s in a try/except block). Python 3.x will have similar or better performance results than using Python 2.6 with psyco. The code looks like this;
|11||from pygame.locals import *|
|13||#try: # Only used for version 2.6(no longer supported as of 5/12/2012)|
|14||# import psyco # otherwise, skip or comment out this 5 line code block|
|17||# print(‘Psyco not installed, import ignored, the app will just run slower.’)|
The next block of code covers our global constant variables. Typically, Python best practices dictate that global variables should be avoided. For our purposes, will we relax this requirement. But as a reminder that these values are static and should not be changed by any part of the program, we will write them in all capital letters. The reason for using global variables is to eliminate having the values they represent hard-coded in the program. For example, there are a few places that WIDTH and HEIGHT are used in this program. If the numerical values were hard-coded throughout the program, and you wanted to change one or both of the values, you would have to search and replace every instance. By creating these values as global variables, you only need to change the value at its listing, and every other instance will carry that change as the program is executed. Simple is better.
|19||# Constants – A popular practice is to create these in all capital letters so that they|
|20||# stand out as values that will not change during the course of the program.|
|21||WHITE = 255, 255, 255|
|22||BLACK = 0, 0, 0|
|23||RED = 255, 0, 0|
|24||GREEN = 0, 255, 0|
|25||BLUE = 0, 0, 255|
|26||STAR_SIZE = 3 # Mixes up the star’s intensity|
|27||COLORS = [BLACK, # Varies the stars colors|
|28||WHITE, # This list can be commented out,|
|29||RED, # and its occurrences also, this would make|
|30||GREEN, # all the stars white. This is purely|
|31||BLUE] # a preference choice.|
|32||WIDTH = 600 # Defines the window dimensions|
|33||HEIGHT = 600 # Remember: width by height|
|35||N_STAR = 100 # The number of stars to show at one time|
Aside from defining our main function, there are only three additional functions used in this example. They are; initialize_stars(), draw_stars(), and move_stars(). Initializing the stars consists of creating a list, populating it with the number of stars we want to display on the screen, and then using a for loop to populate a random x and y coordinate for each star and appending the coordinate info back to our list. This is taking our list of stars and to each element, adding a list of x and y coordinate pairs.
|39||“”” Creates a list of stars, each star will receive a random pair of x and y|
|40||coordinates for its position.|
|42||star_list = |
|43||for i in range(N_STAR):|
|44||x = random.randrange(0, WIDTH)|
|45||y = random.randrange(0, HEIGHT)|
If you check out the pygame documentation, we are drawing using circles, this might not be the most efficient method, but it allows us to use an interesting effect. When we use the pygame.draw.circle() function, by varying the size of the circle(between 1 and 4 for example) we can recreate a “twinkle” effect. This is not a required feature, but it does look nice. The way the code is written, STAR_SIZE is fixed at 3 once as the program reads and assigns the global variables, to change this so that the value changes more dynamically we use the function variable shimmerEffect. Then use STAR_SIZE as the upper bounds of our random function.
|51||def draw_stars(surface, star_list):|
|52||“”” This function takes a list of stars and displays them on the pygame surface.|
|53||If the color parameter is omitted, it will default to black.|
|55||for i in range(len(star_list)):|
|56||starColor = random.randrange(0, 4) # For white stars comment out|
|57||color = COLORS[starColor] # For white set color = WHITE|
|58||shimmerEffect = random.randrange(0, STAR_SIZE)|
|59||pygame.draw.circle(surface, color, star_list[i], shimmerEffect)|
The function move_stars() takes care of animation and repopulation. When a star has exceeded the window height, the if statement helps out by generating new x and y coordinates and replaces the stored star’s coordinates. To give Python some lead in time for placing the star, the y coordinate value is set above(using a negative value) the window height, this should allow for a smoother stream of stars traveling across the window. When we structure our for loops in the draw_stars() and move_stars() functions, the range parameter is len(star_list). This returns the length of the list(in elements) to range(), allowing the loop to iterate through the correct number of repetitions.
|62||def move_stars(surface, star_list, color):|
|63||“”” This function generates the star movement, if the star has fallen past the|
|64||height of the window, it is recreated with new x and y coordinates, the|
|65||added back into the list.|
|67||for i in range(len(star_list)):|
|68||pygame.draw.circle(surface, color, star_list[i], STAR_SIZE)|
|69||star_list[i] += 1|
|71||# loop the top/bottom of the window|
|72||if star[i] > HEIGHT:|
|73||y = random.randrange(-HEIGHT/4, –HEIGHT/8) # before re-entering window|
|74||star_list[i] = y|
|75||x = random.randrange(0,WIDTH)|
|76||star_list[i] = x|
Last, but certainly not least, is our main function. The work horse of this program. We first use clock to monitor the execution cycles of the program. This is also how to control the application’s frame rate or FPS(Frames Per Second). At the end of this function, on line 103, we see clock.tick(30), it’s our limiter set at 30, but depending on the strength of your system you can play with this value as you desire. If you start to see clipping effects( a visible line in the window which divides the current drawing pass from the previous drawing pass) then reduce the number inside clock.tick() until the clipping is eliminated. The random.seed() at line 85 get a value from your PC, usually in seconds, and uses that to calculate values for any random functions. If your situation requires even more randomness, check IDLE’s help for functions in the random module to help mix things up. Next we initialize pygame, create the window, title, and flood the window background with black (our first layer – also the least dynamic, so it occurs first). The while loop is technically the engine of our game(tutorial app in this case). There are many ways to set this up, I tend to prefer while 1:. You could do something like:
|game = true|
# loop statements here
# test condition
# exit point
This could be how you set up various screen exchanges, like splash, menu, game, high score, exit. It all depends on your requirements for your game’s structure. In the loop we use draw_stars() as our starting point, then call move_stars() to animate, and re-call draw_stars() to show the result. The star field that I coded adds color to the stars. If for example you were making an “old school” asteroids clone, when re-calling draw_stars() change line 57 to be color=WHITE and you now have monochromatic stars!
Picking up at line 97, we tell pygame to run an update, so it can handle its business, and the following for loop tests for user input with regards to exit conditions. If the user clicks the “X” button or hits the ESC key the program exits. By extending this for-loop with a nested if statement, you can monitor user key presses so that you can move a ship or fire a laser beam. Just insert your key control code before the current if statement, that way your key command execute before the check for exiting. Line 100 – pygame.quit() is primarily to help IDLE release the pygame assets, otherwise the app window will hang, when launched from IDLE. Including it is only one line, and also considered good form. Editra ran without this, but it might have contributed to my initial problem (the first note mentioned in this article). The remainder of the code(lines 106/107) simply tells Python if the main function is called, execute it, and run the program normally. The reason for setting a Python program in this way is, ideally, for the condition that you have, the starfield.py file in your project directory, so then you could import it and use the three functions initialize_stars(), draw_stars(), and move_stars() to create the background in your working game idea. This type of modularity it one of the Python cornerstones, the idea is if the code is written to closely follow the Python guidelines, the file will we successful as a stand alone application or as an imported module for a larger program.
|82||“”” This code assembles the starfield and sends it to pygame.|
|84||clock = pygame.time.Clock()|
|86||stars = initialize_stars()|
|87||pygame.init() # Entry point to pygame|
|88||screen = pygame.display.set_mode([WIDTH, HEIGHT]) # Create window|
|89||pygame.display.set_caption(‘Tut02: Empty Space…’) # Create window title|
|90||screen.fill(BLACK) # Create a “static” background|
|92||while 1: # The main game loop|
|93||draw_stars(screen, stars) # Hide stars prior to move|
|94||move_stars(screen, stars, BLACK)|
|95||draw_stars(screen, stars) # Show stars after move|
|96||# Add WHITE at line 57 if desired|
|98||for entry in pygame.event.get(): # Wait until input is generated|
|99||if entry.type == QUIT or (entry,type == KEYUP and entry,key == K_ESCAPE):|
|100||pygame.quit() # Exit gracefully|
|103||clock.tick(30) # Change this for FPS (Frames per second)|
The final listing is the file in its entirety, with comments to allow those who have been following along to compare their work. If you haven’t been following along, see if the code is understandable, and take it for a test drive. Aside from the documentation, one of the best ways to understand code is to break it down, dissect it, and then modify it to see what the outcome will be. I hope that this tutorial was helpful.
|#!/usr/bin/python# This example runs in Python 2.6(w/wo psyco) and 3.1 using Pygame 1.9
# Using psycho – if installed, it almost cuts CPU usage in half
# This example builds on the stars.py example included in pygame
# Its located at: [python install]\Lib\site-packages\pygame\example# Imports
from pygame.locals import *
#try: # Only needed for ver 2.6
def draw_stars(surface, star_list):
def move_stars(surface, star_list, color):
if __name__ == ‘__main__’: # Stand-alone execution