Saturday, January 16

Decorate Thy Keyboard Controls

As part of a game engine called Grease I'm working on (more on that sometime soon) I was thinking about ways to create a clean and efficient api for handling keyboard events. Too often this ubiquitous section of game code consists of a tangle of if/elif statements, a construct that is a personal pet peeve of mine. So to avoid that mess, a typical strategy I've employed is to map keys to methods using a dictionary for easy dispatch. Doing this for one-off applications is simple enough, if not particularly clean and tidy.

Anyway taking a step back for a sec, let's examine some goals. Basically what I'm after is a way to define some methods (or even functions) that get executed in response to key events. Specifically there are three types of key events that I'm interested in: key press, key release and key hold. The first two get dispatched once per key "stroke" as you'd expect. The key hold event fires every game "tick" that a key remains down; useful for continuous functions like thrust, etc.

So I need a clean way to map methods to specific keys and key event types. Sounds like a job for: decorators! Truth be told I haven't felt the need to write many decorators, but after prototyping a non-decorator version that came out less than clean even though it only supported one type of key event, I thought I'd give decorators a go. Below is an example of how to implement key controls using what I've cooked up:

class PlayerControls(KeyControls):

    @KeyControls.key_press(key.LEFT)
    def start_turn_left(self):
        ship.rotation = -ship.turn
    
    @KeyControls.key_release(key.LEFT)
    def stop_turn_left(self):
        if ship.rotation < 0:
            ship.rotation = 0

    @KeyControls.key_press(key.RIGHT)
    def start_turn_right(self):
        ship.rotation = ship.turn
    
    @KeyControls.key_release(key.RIGHT)
    def stop_turn_right(self):
        if ship.rotation > 0:
            ship.rotation = 0

    @KeyControls.key_hold(key.UP)
    def thrust(self, dt):
        ship.body.apply_local_force(
            ship.player.thrust * dt)
    
    @KeyControls.key_press(key.P)
    def pause(self, dt):
        global paused
        paused = not paused
Here's the code needed to wire this into pyglet:

window = pyglet.window.Window()
controls = KeyControls(window)
pyglet.clock.schedule_interval(controls.run, 1.0/60.0)
pyglet.app.run()
Though using the decorators binds the keys to specific methods of the class at compile-time, KeyControls also contains additional methods for changing the key bindings at run-time. I may also add support to load and store key bindings from a configuration file if that feature is needed.

This implementation is designed to work with pyglet, though I think the same general approach could be used with pygame. The module for this, although part of a larger project I am working on, doesn't depend on anything else. Feel free to give it a try and see what you think.

[Edit a new version of this module is now available]

Get the module here.

Monday, January 11

_______ is Better Than _______

If explicit truly was better than implicit, Python code would look like this:

StringBuilder s = new StringBuilder()

And let's face it, that's not enlightening to anyone.