Just as an interactive SDL app builds around an event loop, a game builds around a game loop. The simplest game loop is something like:
The names of the functions called in this loop hint at their purposes,
but the subtleties of even this simple code are important.
get_events() obviously processes events from the relevant input devices (keyboard,
Processing events at the start of every game loop iteration helps to prevent lag.
calculate_next_positions updates the game state according to user input as well as any active animations (a player walking,
a cut scene).
render() finally updates and displays the screen.
Consider a game with a moving laser bolt:
This game loop works very well for consoles and other devices where you know exactly how much CPU time the game will get for every loop iteration. That hardware stability is easy to predict: each animation and calculation will happen at the same time for each machine. Unfortunately, this is not true for modern operating systems and general purpose computing hardware. CPU speeds and workloads vary, so for this game to play consistently across multiple machines and myriad configurations, the game loop itself needs to regulate its updates.
One way to solve this problem is to regulate the number of frames per second the game will produce. A frame is a complete redraw of the screen representing the updated game state. If each iteration of the game loop draws one frame, the more frames per second, the faster the game is running. If the game loop limits the number of frames per second, the game will perform consistently on all machines fast enough to draw that many frames per second.
You can see this with the example program game_fixed.pl. When run with no arguments:
$ B<perl game_fixed.pl>
.... the FPS rate will be erratic. The laser seems to change its speed randomly. When run with a single argument, the game sets an upper bound on the number of frames per second:
$ B<perl game_fixed.pl 1>
This will prevent the laser from going faster than 60 frames per second. When run with a second argument, the game will set a lower bound of frames per second:
$ B<perl game_fixed.pl 1 1>
At this point the FPS should hold steady at 60 frames per second.
This method is generally sufficient for most computers. The animations will be smooth enough to provide the same gameplay even on machines with different hardware.
However, this method still has some serious problems. First, if a computer is too slow to sustain a rate of 60 FPS, the game will skip rendering some frames, leading to sparse and jittery animation.it will skip a lot of rendering, and the animation will look sparse and jittery. It might be better to set a lower bounds of 30 FPS, though it's difficult to predict the best frame rate for a user.
The worst problem is that this technique still ties rendering speed to the CPU speed: a very fast computer will waste CPU cycles delaying.
To fix the problem of a computer being consistently too fast or too slow for the hard-coded FPS rate is to adjust the FPS rate accordingly. A slow CPU may limit itself to 30 FPS, while a fast CPU might run at 300 FPS. Although you may achieve a consistent rate this way (consistent for any one particular computer), this technique still presents the problem of differing animation speeds between different computers.
Better solutions are available.
Describe movement and show handlers.
The problem caused by coupling rendering to the CPU speed has a convenient solution. Instead of updating object positions based on how fast the computer can get through the game loop, derive their positions from a physical model based on the passage of time. Objects moving according to real world time will have consistent behavior at all CPU speeds and smooth interpolation between frames. SDLx::App provides this behavior through movement and show handlers.
A simple physics model for the laser has a consistent horizontal velocity in pixels per time step at the window's mid-point:
X = Velocity * time step, Y = 100
Assuming a velocity of 10, the laser will pass through the coordinates:
0, 100 10, 100 20, 100 30, 100 ... 200, 100
Note that the speed of processing the game loop no longer matters. The position of the laser depends instead on the passage of real time.
The biggest problem with this approach is the required bookkeeping for the many objects and callbacks. The implementation of such complex models is non-trivial; see the lengthy discussion in the documentation of the
This version of the laser example demonstrates the use of movement, show handlers, and a simple physics model. This example also shows how
SDLx::App can do more of the work, even providing the entire game loop:
To learn more about this topic please, see an excellent blog post by GafferOnGames.com: HTTP://GafferOnGames.Com/game-physics/fix-your-timestep.
Hey! The above document had some coding errors, which are explained below: