Tuesday, July 10, 2007

A General Pygame Main-Loop

Will McGugan's post about mastering time in pygame started me thinking about my game loops, and how I might implement frame skipping, and other things.

The benefit of having a fixed 'step size' for your simulation might not be immediately apparent, however it has obvious benefits when trying to synchonrize network games, or work with physics libraries (eg ODE) which can go non-deterministic when working with a variable step size...

This is what my new general game loop looks like. If I ever need to implement frame-skipping, I believe I'll need to write some more code at line 27, to detect if every frame is being skipped... :-) I use this loop to generate Tick and Render events which get handled elsewhere.

1 import pygame
2 from pygame.locals import *
4 def main():
5 pygame.init()
6 pygame.display.set_mode((320,200))
8 #time is specified in milliseconds
9 #fixed simulation step duration
10 step_size = 20
11 #max duration to render a frame
12 max_frame_time = 100
14 now = pygame.time.get_ticks()
15 while(True):
16 #handle events
17 if QUIT in [e.type for e in pygame.event.get()]:
18 break
20 #get the current real time
21 T = pygame.time.get_ticks()
23 #if elapsed time since last frame is too long...
24 if T-now > max_frame_time:
25 #slow the game down by resetting clock
26 now = T - step_size
27 #alternatively, do nothing and frames will auto-skip, which
28 #may cause the engine to never render!
30 #this code will run only when enough time has passed, and will
31 #catch up to wall time if needed.
32 while(T-now >= step_size):
33 #save old game state, update new game state based on step_size
34 now += step_size
35 else:
36 pygame.time.wait(10)
38 #render game state. use 1.0/(step_size/(T-now)) for interpolation
40 if __name__ == "__main__":
41 main()

Update: Thanks to a suggestion from Marius in the comments, the loop is now environmentally friendly. :-)


Marius Gedminas said...

For extra bonus, could you make the code not do any busy-waiting? Consuming 100% of the CPU unnecessarily is not really polite (and makes laptops hot).

Simon Wittber said...

Good idea. 'Not really polite' is a good phrase to describe the current behavior... I'll drop in a fix for that.

Anonymous said...

pygame.locals is deprecated with newer versions of Pygame. The newer versions of the library include constants like QUIT and K_ESCAPE directly in the pygame module:

if pygame.QUIT in [pygame.event.get()]:

Just a minor nit-pick. The from ... import * syntax just irks me. :)

Unknown said...

The else case in line 35 does not match up with any if cases. I know its pseudo code but I feel its still not clear exactly when you wouldn't wait.

Simon Wittber said...

It's not pseudo code, it's Python.

Python lets you have an else clause on for and while loops.

Popular Posts