A novice games programmer learns he can move a sprite by adding a constant to its position every frame. For a while he is happy and mocks the master games programmer for having wasted many years learning. But soon the novice realises his sprites are unpredictable and move at different rates on different machines. The novice is despondent and visits the master for advice.

"Master. Why do my sprites not obey my commands? They jump when I ask them to slide."

"Frame rate is like the wolf, it can not be tamed", replies the master.

The novice returns to his code and a revelation comes to him. "I must move my sprites with the frame rate!" The novice changes his sprite code to move a distance relative to the time since the previous frame, and his sprites become at one with the frame rate. "I must truly be a master", thinks the novice.

For a while the novice is happy, and produces many simple games. But as the novice's logic gets more complex he is faced with a problem. "My game updates are tied to the frame rate!", thinks the novice. He realises that his game will run differently on other machines and yearns for the simpler days when he thought frame rate was constant. Despondent, he visits the master for advice.

"Master. How can I make my game run predictably on all machines?"

"Time is not rigid as a tree. Time is supple, as a reed", replies the master.

The novice goes back to his code and ponders the master's advice. A revelation comes to him, "Game logic can run in it's own time!" With that thought in mind, he separates his game logic from the render code and soon his game logic runs independently of the frame rate.

The novice is humbled and thanks the master for his help.

"Truly, you are a master now", replies the master.

To become a master you can either meditate in a cave in Tibet or use the GameClock class in the Game Objects library. GameClock manages game time, which marches on at a perfectly constant frame rate, no matter how many frames you can actually render. The main advantage of this approach is that the game will run the same on a fast computer, as it does a slow computer, which is extremely useful for recording demos or adding network play. It also makes the math for moving sprites (2D or 3D) a little simpler, because you can just add constants each frame.

Here's a snippet from the example project that shows how to separate the render loop in to an update phase and a render phase. The call to game_clock.update is an iterator that returns the frame count and time for each game logic update. It will return as many updates as necessary for game time to 'catch up' with the real time.

# Update phase
        for frame_count, game_time in game_clock.update():

            for ball in balls:

        # Render phase
        for ball in balls:

The update function calculates the position of the sprite at the current update, and the position it will be at the next update. This is because the render phase will likely occur at some point between game logic updates, and the render position should be adjusted proportionally. The method get_between_time returns a value between 0 and 1 that you can use to interpolate value between two game logic updates.

For example, if the sprite contains its current position (self.position) and its position at the next update (self.next_position), then you could find the render position with the following code (lerp is a function in gameobjects.util).

x, y = lerp( self.position,
                     game_clock.get_between_frame() )

So what is a good rate for the game logic update? In my experience 20 logic updates per second is about right for most games. You can increase this rate if you want finer control, but it is probably not a good idea to go much lower as it will reduce the responsiveness of the game.

An additional advantage of GameClock is that you can control the speed of time with little effort. If you call game_clock.set_speed(0.5), then half as many game logic updates will be issued and the game will run at half speed. You can also make the game run faster by setting the speed to a value greater than 1.0. Alas, you can't set the speed to a negative value, as this would violate the laws of the cosmos.

You can experiment with the game logic update rate and time speed with the GameClock sample code. You will need PyGame of course, and Game Objects 0.0.2. Game Objects can be easy installed with the command: easy_install gameojects.

Download GameClock sample code

Here's a screenshot of the sample code running. I really must try to write samples that don't feature shiny spheres!

This blog post was posted to It's All Geek to Me on Friday June 15th, 2007 at 7:44PM

20 Responses to "Master time with PyGame"

  • phaero
    June 15th, 2007, 9:59 p.m.

    phaero@jsbig ~/tmp/gametime $ python gametimesample.py
    Traceback (most recent call last):
    File "gametimesample.py", line 180, in ?
    File "gametimesample.py", line 148, in run
    for frame_count, game_time in game_clock.update():
    File "build/bdist.linux-i686/egg/gameobjects/gametime.py", line 142, in update
    ZeroDivisionError: float division

  • Pete
    June 15th, 2007, 10:06 p.m.

    Great class, and fun article. I like the implementation with generators.

  • patrick
    June 16th, 2007, 10:47 a.m.

    Awesome, elegant solution to one of the most common game programming problems, with the added bonus of being able to go all max payne style :)

  • June 16th, 2007, 12:47 p.m.

    I get this error :

    for frame_count, game_time in game_clock.update():
    File "/usr/lib/python2.5/site-packages/gameobjects/gametime.py", line 142, in update
    self.fps = 1.0 / self.real_time_passed
    ZeroDivisionError: float division

    I add a check before this division and it works :

    if self.real_time_passed != 0:
    self.fps = 1.0 / self.real_time_passed
    self.fps = 1.0

  • June 16th, 2007, 2:51 p.m.

    Thanks for the bug report. Strange that that can occur. It must be down to the granularity of time.clock.

    Fix in SVN.

  • andriyko
    June 25th, 2007, 6:39 a.m.

    I noticed that when you create alot of balls at like speed 200% and let them move around at the bottom for a while, the frame rate keeps linearly decreasing, until it becomes intolerably slow.
    It seems that once the balls settle the frame rate should increase... hm..

  • June 25th, 2007, 11:44 a.m.

    More bouncing means that the collision detection code is firing more often. Although I suspect that the slow down is just due to the number of sprites being rendered.

  • andriyko
    June 25th, 2007, 1:58 p.m.

    Um... no. What I mean is this:
    The framerate is fine with I spawn alot of balls. Eventually all the balls don't bounce as high and come down.
    When all the balls are done bouncing (i.e. they are at the very bottom - just hopping a little and going one wall to another) the frame rate keeps dropping (and does not stay constant).

    Could it be because the bounces are begging shorter? Doesn't seem so.

  • June 25th, 2007, 2:29 p.m.

    Strange, I don't see that here. The frame rate is always consistent with the number of sprites on screen.

    What platform are you running it on? And are you using the latest code, with the fix that Batiste suggested?

  • Foone
    September 6th, 2007, 12:11 p.m.

    Hmm. I'm getting weird performance on python 2.5.1/pygame 1.7.1.
    If I just add one ball, it's laggy and looks like 20FPS (but the indicator says 800fps)
    if I go crazy and add 100 objects, the displayed FPS goes to 50 but it gets very smooth.

  • September 7th, 2007, 8:41 p.m.

    I don't see that behavior (same Python and Pygame). It actually sounds like a v-sync issue. What platform are you running it on?

    Try changing it to full-screen...

  • Foone
    September 10th, 2007, 4:02 p.m.

    Fullscreen didn't help. I'm running on Ubuntu 7.04
    Switching to double buffered (and making pygame.display.update => pygame.display.flip) didn't help either

  • Foone
    September 10th, 2007, 4:05 p.m.

    oh, and plugging in a frame count (not frame rate count) indicator shows that the screen is actually updating at full speed, just the ball isn't moving smoothly.

  • December 27th, 2007, 12:03 a.m.


    I am using a Mac and I was wondering how to get GameObjects to work. I downloaded the .0.0.3 version in the .zip file (cause .exe doesn't work of course...)

    I tried running the setup.py file and got :

    Traceback (most recent call last):
    File "/Users/ryansweeney/Sites/gameobjects-0.0.3/setup.py", line 31, in -toplevel-
    classifiers = classifiers.splitlines(),
    File "/Library/Frameworks/Python.framework/Versions/2.4//lib/python2.4/distutils/core.py", line 137, in setup
    raise SystemExit, gen_usage(dist.script_name) + "\nerror: %s" % msg
    SystemExit: usage: setup.py [global_opts] cmd1 [cmd1_opts] [cmd2 [cmd2_opts] ...]
    or: setup.py --help [cmd1 cmd2 ...]
    or: setup.py --help-commands
    or: setup.py cmd --help

    error: no commands supplied

  • December 27th, 2007, 11:51 a.m.

    You should be able to install it with "easy_install gameobjects". You can get easy_install from (http://peak.telecommunity.com/DevCenter/EasyInstall). I'm assuming it works on Mac -- I'd be shocked if it didn't!

    Alternatively, since you have downloaded the zip file, you can install it with the following command:

    python setup.py install

    Hope that helps!


  • December 29th, 2007, 4:10 a.m.

    Alright... I have easy_install and I have easy_install-2.4 here's what happened when I tried what you said:

    -bash: /Library/Frameworks/Python.framework/Versions/Current/bin/easy_install: "/Applications/MacPython: bad interpreter: No such file or directory

    it always comes up with that last line, even if I state which directory "gameobjects" is in.

    I tried that other method "python setup.py install" and that tells me a bunch of other stuff that doesn't produce the preferred result I even put directories in front of setup.py and it does more but not what I think should happen. Sorry most of this is vague. It usually comes down to "no such file or directory"

    any ideas?

  • December 29th, 2007, 4:27 p.m.

    I'm afraid I'm not sure what is going on there. Could be some kind of Mac Python configuration error. The comp.lang.python newsgroup should be able to help you...

  • February 25th, 2008, 9:33 p.m.

    Will, thanks for this article and your latest gameobjects library. I revised a game of mine to use your GameClock class. Now it works properly on slower machines, and it also has a working pause feature. I was stumped on how to implement this before studying this article and your code.

    I recommend to other readers that they consider how to implement game time logic before they start their own designs. If you have to retrofit your own game to implement virtual time correctly using Will's techniques, you will have to rework a lot of the internals to get your game to flow correctly again. However, it's worth it for the resulting polish and consistency in your game across all classes of machines.

  • Paulo Barbeiro
    March 22nd, 2008, 1:37 a.m.

    An updated information.
    To install Game Objects in MAC OS X Leopard, I downloaded the .zip file, unpacked it, and in the Console - reached the unpacked Game Object folder - , i've typed "python setup.py install", just like Will says... and it works!

    I'm using an iMac Intel based, with Leopard, and Python2.5.

    that's it!

    Ah... Will, your book is awesome! Thanks.

  • The novice
    April 28th, 2014, 8:38 p.m.

    Thanks for the lesson and library, but:

    What if I wanted to limit the frames as well? Is it really necessarry to render more than 60 or 120 fps?

Leave a Comment

You can use bbcode in the comment: e.g. [b]This is bold[/b], [url]http://www.willmcgugan.com[/url], [code python]import this[/code]
Preview Posting...
Previewing comment, please wait a moment...

My Tweets

Will McGugan

My name is Will McGugan. I am an unabashed geek, an author, a hacker and a Python expert – amongst other things!

Search for Posts
Possibly related posts
Popular Tags
Recent Comments
don't know why this was tempting.. (#1)import re from collections import Counter, OrderedDict cnt=Counter() with open(./t) as f: #--- strip ...
- Mike on Python Coder Test
Hello! I've seen this test and tried to do them. Result added bellow. First path: def thousands_with_commas(i): i = str(i) ...
Why another framework? what wrong with django, pyramid, flask?will be have answer for this question in the docs)
Hi! Really great code, good work! But trying to use it on a responsive site, it didn't resize images. So, ...
using of a recursion: def thousands_with_commas(i): def _recurse(n): x, y = divmod(n, 1000) if x 1000: return [x, y] return ...
© 2008 Will McGugan.

A technoblog blog, design by Will McGugan