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: ball.update() # Render phase for ball in balls: ball.render(screen)
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
x, y = lerp( self.position, self.next_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:
Here's a screenshot of the sample code running. I really must try to write samples that don't feature shiny spheres!