Tuesday, 25 March 2008

restructuring pygame flow

I've been programming in Python for quite some time now, and I have really fallen in love with the language. It's simple yet powerful, and there's a whole slew of libraries included. And even more out in the wild. Almost everything I do is in python now, and I'm liking it.

One of the things that has always had my particular attention is game programming. Python as no built-in library just for that, but there is an excellent library available for that called Pygame. It consists mostly of bindings to the SDL library (which is also pretty good), and a bit of python specific stuff to make everything easy for you. So I've been writing small games with pygame for a while, and it's mostly been good. One gripe I have with it, however, is the way the program is structured. I'm talking about the event loop:
while 1:
for event in pygame.event.get():
if event.type == pygame.QUIT:
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_UP:
#et cetera et cetera
In short, checking for all events occurs in a single loop. That's good, but the actual handling of the event (player.move) occurs in the same single loop. This I find bad. Code used handling the player, enemies, menu options, all of it is gathered up in this single loop, which will grow into monstrous proportions because of that.

To counter this behavior, I have written an EventManager class that is based on callbacks: objects can register functions with the event manager, and these functions will be called if the event is triggered. The function is passed the event in question as an argument, and can act on the event accordingly. With this class checking for events occurs in the EventManager instance, and handling of events occurs inside the relevant objects. All the code is where it belongs, and everyone is happy.

Allow me to demonstrate how a simple class may make use of the eventmanager:
class SimpleObject:
def __init__(self):
(KEYDOWN, self.onKeydown),
(KEYUP, self.onKeyup),
(MOUSEBUTTONDOWN, self.onLeftMousebutton, {'button':1}))

def onKeydown(self, event):
print "keydown:", event.key

def onKeyup(self, event):
print "keyup", event.key

def onLeftMouseButton(self, event):
print "left mouse button pressed!"
the above is a simple example of how the event manager may be used. The bindToGloblal method is a class method which makes the requested event bindings to the global EventManager instance, which is stored inside the class itself (usually only one global instance is needed. This method simplifies acces to this instance). The arguments to the function are tuples consisting of the event type, the handling function, and optionally a filter which can make sure only events with certain attributes trigger the handler. In the above example, onLeftMouseButton is only called if event.type equals MOUSEBUTTONDOWN, and event.button equals 1.

So, you ask, where is the source to this elegant class, so I may use it in my games and reach ascension? Good Question. The EventManager class is part of a package that I now use to develop all of my games. Other parts of the package are collision detection, resource management (images, sounds), a primitive GUI, a simple vector class, and some more random tidbits I find useful in my projects. The entire package together is a complete mess, and I really wanted to clean things up a bit before I throw it to you wolves out there.

That said, though, the functionality is there for those who want it. I've tarred the package together under the (slightly) dubious name of stdtools, and it's available here. The entire package is unstable and subject to change. It's also under the GPL.

1 comment:

Thadeus said...

Hey you should check out a pygame event input wrapper module I just finished.

It basically just creates a dictionary of events. Check it out