Blake O'Hare .com

PyGame Tips & Tricks

PyWeek 15 starts in one week. So here's a few tips and tricks for a more lovely pygame coding experience.

Image Cache

Use a string->Surface dictionary as an image cache. Wrap this cache with a function called get_image(path). This path will be the key for the cache. If the image isn't found in the cache, load the image from file, but convert the path delimiters. i.e. real_path = path.replace('/', os.sep).replace('\\', os.sep). This way you can just use slashes or backslashes everywhere in your code without cluttering it with a bunch of unsightly os.path.join's.

Caps in file names

If you do your primary development on Windows, adopt a consistent convention for using caps in filenames. Ideally DO NOT USE CAPS AT ALL. Make all files lowercase. All other major OS's have case-sensitive file paths. Be mindful of this.

Use Spriting

There is a HUGE overhead to loading an image from a hard drive. Loading a giant image is only slightly slower than loading a tiny image. If you have billions of tiny images, write a script that combines all the images into one giant image and generates a manifest that describes where each file is on this giant image and its size. In your game, add code to your image cache function that blits this (cached) giant image onto a small empty surface that is the size of the image you want. This way you can load your billion tiny images with only calling pygame.image.load once.

(For this example, assume there is some sort of manifest data structure that is keyed off the filename and contains a position and size field for each file. The implementation of such a datastructure should be trivial.)

_images = {}
_img_manifest = read_manifest('image_manifest.txt')
_sprite_sheet = None
def get_sprite_sheet():
  global _sprite_sheet
  if _sprite_sheet == None:
    _sprite_sheet = pygame.image.load('sprite_sheet.png')
  return _sprite_sheet

def get_image(path):
  img = _images.get(path)
  if img == None:
    img_data = _img_manifest.get_image_data(path.replace('/', os.sep).replace('\\', os.sep))
    position = img_data.position
    size = img_data.size
    img = pygame.Surface(size)
    img.blit(_sprite_sheet, (-position[0], -position[1]))
    _images[path] = img
  return img


One Loop to Rule them All

Only write one game loop. Each logical scene should be an object.

Abstract Raw Input

In this single game loop, abstract the raw input from the framework into logical input that is relevant for your game. Convert the pygame events into MyEvent, a class you create. This class will have event names such as "left", "right", "jump", "shoot" instead of K_LEFT, K_RIGHT, K_UP, K_SPACE. This mapping ought to occur based on some sort of pygame event -> custom event dictionary. This gives you the option of later creating an input configuration menu where the user can edit these values. The rest of your code should be completely unaware of the notion of pygame events. Only logical events.

Joystick

Here be monsters.

Music

Through the complexity of your game and unique ways to hit certain code, it's a common error to get into a situation where the wrong music is playing because the user somehow bypassed a crucial mixer.music.play call. For each logical scene in your game, write a function called ensure_playing(song) that gets called EVERY frame. This function should maintain a state of what song is currently playing and no-op if the input matches that. If not, switch songs.

Abstract the complexities of creating and caching text

Write a function called get_text that takes in the text, font name, color, size, etc. This should return an image that matches this criteria from either a cache or generate it if not present in the cache. This cache should be keyed off the inputs of the function. For example, if you use a *args as the inputs, you can use this tuple directly as the key. Or construct the tuple manually from rigid arguments. If you have a game with lots of dynamic text, clear this cache periodically.

For Loop Bad

Use while loops instead of for loops when iterating through a simple range of numbers. The range function wastes a ton of memory. The xrange function isn't that great either since it's wasted time to call function, push stack info, etc. A simple while loop is extremely fast by comparison.

Update: So I was totally wrong about this. See the wonderful investigation Omni did in the comments section.
Basically, I projected my experience in other languages to Python where iterators use a tad more CPU than a simple integer-incrementing-loop and made a false assumption. Python (both 2.x and 3.x) are smart enough to optimize range out and basically give you the power of a highly optimized loop. Just be sure to use xrange in 2.x.


Don't reblit identical information each frame

If you know something is guaranteed to look the same each frame, then composite multiple blits into one image that's cached and blit that one image. There is quite a bit of overhead to blit. This is especially useful if the blitted region has a number of overlapping blits or if there's complexity to generate the information that needs to be blitted.

Lists Declared Inline Kill Kittens

Do not declare lists or tuples inline in code unless you really need to create a new, separate instance. Declare them once in some sort of initialization function and refer to it that way.

Use Source Control

Even if you're working alone, source control has benefits. It saves the state of your code if when you screw it up and need to revert back to a previous version. For me, personally, it helps me focus on one feature at a time without leaving something hanging with a TODO as I am more mindful when I have to submit complete changelists.

3.x is not your enemy

Embrace 3.x. PyGame tends to run noticeably faster on it. Seriously. And whether or not you plan on using 2.x till the day you die for principled reasons, over time people will be switching to 3.x as their default, and non-3.x compatible code will stop working. Embracing 3.x doesn't have to mean turning your back to 2.x users. For a typical PyGame game, there are only a couple simple things you have to dance around. And it's a fairly simple dance, too:

Python 3.x compatibility Tips

  • Write legacy_map, legacy_filter, etc. that re-implements the behvior of the 2.x versions of map and filter, etc. if those are functions you even use. You could also do the same with range and create a legacy_range, but as stated earlier, you really shouldn't use [x]range.
  • Put parenthesis around all print statements.
  • Use // for integer division, and add 0.0 to ints if you intend for float division.
  • Don't throw custom exceptions. If exceptions are occuring as part of your intended codepath in a game, you are probably doing something wrong anyway.
  • Install Python 2.5, 2.6, 2.7, 3.1, and 3.2. Create execution scripts (.sh/.bat) that runs your game using these versions. Call them run26.bat, run32.bat, etc. Before you check in code, run your game using a 2.x version and 3.x version. This is also especially useful for ensuring you don't have any stray print statements you were using for debugging. If you write debug print statements without parenthesis and accidentally leave it there, the 3.x version will give a syntax error if you try to run it.

JavaScript Tutorial, Part 5 - Interacting with HTML

What good is JavaScript if you can't interact with the HTML page?

Modify your index.html to look like this:
<html>
    <head>
        <title>JavaScript tutorial</title>
        <script type="text/javascript" src="code.js"></script>
    </head>
    <body>
        <input type="text" id="typey_box" />
        <button onclick="do_things()">Click</button>
        <div id="output">Results will appear here.</div>
    </body>
</html>
onclick is a JavaScript event. I will talk about this more later. Just nod and smile for now.

Modify your code.js file to look like this:
function do_things()
{
    var text_box = document.getElementById('typey_box');
    var results_box = document.getElementById('output');
    var text = text_box.value;
    var message = "string length is " + text.length;
    results_box.innerHTML = message;
}
do_things is a user-defined function. I will talk about this more later. Just nod and smile for now.

Your page will look something like this when you launch it.
js_tutorial_10.png

Type something in the box and click the button:
js_tutorial_11.png

Aside from the event and the function definition, two nifty new things happen here.

Getting an element from the page

Here I use the getElementById which exists on the document object. Any HTML element that has its id="..." attribute set, can be accessed by calling this function. The DOM object is stored into the variable and you can do neat things with it. Most importantly...

Reading/Modifying the contents of a DOM object

As you can see there is a handy-dandy property on the DOM object called innerHTML. Reading this property will return a string of the HTML code INSIDE the element. So for example, if you use document.getElementById('foo') to get an element that looks like this:
<div id="foo">Hello</div>

then calling innerHTML on the foo element will return Hello and not <div id="foo">Hello</div>.
Setting it will change the value of Hello on the visible page.

This finally liberates us from being forced to use the window.alert function to get any output from the code. Moreover, it allows you to actually do useful things on the page itself.

function do_things()
{
    var text_box = document.getElementById('typey_box');
    var results_box = document.getElementById('output');
    var text = text_box.value;
    var message = "string length is " + text.length;
    results_box.innerHTML = message;
    results_box.innerHTML = '<div style="color:#f00;">' + results_box.innerHTML + '</div>';
}
This will take the text in results_box and wrap it in div that makes the font red. If you were to set the value of innerHTML, be aware that it will include the div itself, not just the text. Suppose you wanted to just modify the text inside the red div. Then you can add an additional ID to the div and requery for that div, then modify the innerHTML of that...
function do_things()
{
    var text_box = document.getElementById('typey_box');
    var results_box = document.getElementById('output');
    var text = text_box.value;
    var message = "string length is " + text.length;
    results_box.innerHTML = message;
    results_box.innerHTML = '<div id="inner_div" style="color:#f00;">' + results_box.innerHTML + '</div>';
    var red_div = document.getElementById('inner_div');
    red_div.innerHTML = "New Text that appears as red.";
}


As a side note, you may notice that I mix and match double and single quotation marks to indicate strings. They are completely equivalent. As a general rule of thumb, I use double-quotes for written text and single-quotes for HTML code. This is because you are likely to encounter apostrophes in written text and those conflict with single-quote-delimited strings. In HTML, double-quotes are common, so I use the single quotes to delimit them.

JavaScript Tutorial, Part 4 - Strings

So far in all the examples, everything has dealt with numbers and math. One of the other primary data types in JavaScript are strings.

Strings are just pieces of text.

var greeting = "Hello";
window.alert(greeting);

js_tutorial_8.png
You can join strings together, just like adding numbers...

var greeting = "Hello";
greeting = greeting + ", World!";
window.alert(greeting);

js_tutorial_9.png

As for getting a subsection of a string, there are a few ways to do that:

Slice is a function that exists on each string that will extract all characters from a given starting point and up to (but not including) some end point. These are called indices (plural of index) and they count starting from 0.
var text = "abcdefg";
var newtext = text.slice(3, 5);
window.alert(newtext);

"de"

A similar function to slice is substr which stands for "Sub-string". The only difference is the 2nd number you pass in to .substr is the number of characters you want starting from the first index, as opposed to specifying an end index.

var text = "abcdefg";
var newtext = text.substr(3, 2);
window.alert(newtext);

This code does the same things as the previous code snippet and yields "de".

With both slice and substr, you can omit the 2nd number and they will both return the string starting from the first index and going all the way to the end.

If you only want 1 character at a given index, then its more efficient to just use a function called charAt.

var text = "abcdefg";
var char = text.charAt(2);
window.alert(char);

"c"

There's also another way, but I won't get in to that until we talk about lists.

It's also important to note that all of these functions do NOT modify the original string. They return copies. There is no way to modify a string once it has been created. This is true of most modern languages.

Lastly, it's useful to know how many characters are in a string. There is a property on every string called length which simply returns the number of characters in a string.

var text = "abcdefghijklmnopqrstuvwxyz";
window.alert(text.length);

"26"

length is a property, not a function. That means don't put parenthesis after it.
text.length();
This is WRONG.

text.length;
Correct.

These are only a couple of the built-in string methods. But these are the probably the most common/useful.
Go back further...