Permalink
Browse files

Made the PDF

  • Loading branch information...
1 parent 2779cd1 commit e51cb852928a6aa7bf69a2dc063f249f097203d6 @kthakore kthakore committed May 21, 2011
Showing with 146 additions and 175 deletions.
  1. +146 −175 dist/SDL_Manual.html
  2. BIN dist/SDL_Manual.pdf
View
321 dist/SDL_Manual.html
@@ -901,123 +901,126 @@
<h1>The Game Loop</h1>
-<h2>Simplest Game Loop</h2>
+<p></p>
-<p>The simplest game loop can be boiled down to the following.</p>
+<p>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:</p>
-<pre><code> while(!$quit)
+<pre><code> while (!$quit)
{
get_events();
calculate_next_positions();
render();
}</code></pre>
-<p>In <code>get_events()</code> we get events from what input devices that
-we need. It is important to process events first to prevent lag. In
-<code>calculate_next_positions</code> we update the game state according to
-animations and the events captured. In <code>render()</code> we will update
-the screen and show the game to the player.</p>
+<p>The names of the functions called in this loop hint at their purposes,
+but the subtleties of even this simple code are important.
+<code>get_events()</code> obviously processes events from the relevant
+input devices (keyboard, mouse, joystick). Processing events at the start
+of every game loop iteration helps to prevent lag.</p>
+
+<p><code>calculate_next_positions</code> updates the game state according
+to user input as well as any active animations (a player walking, an
+explosion, a cut scene). <code>render()</code> finally updates and displays
+the screen.</p>
-<p>A practical example of this is a moving laser bolt.</p>
+<h2>A Practical Game Loop</h2>
+
+<p>Consider a game with a moving laser bolt:</p>
<pre><code> use strict;
use warnings;
use SDL;
use SDL::Event;
use SDL::Events;
use SDLx::App;
-
- my $app = SDLx::App-&gt;new(
- width=&gt; 200, height =&gt; 200,
- title=&gt; &#39;Pew Pew&#39;
- );
- #Don&#39;t need to quit yet
+ my $app = SDLx::App-&gt;new(
+ width =&gt; 200,
+ height =&gt; 200,
+ title =&gt; &#39;Pew Pew&#39;
+ );
+
my $quit = 0;
- #Start laser on the left
+
+ # start laser on the left
my $laser = 0;
- sub get_events{
- my $event = SDL::Event-&gt;new();
-
- #Pump the event queue
- SDL::Events::pump_events;
+ sub get_events {
+ my $event = SDL::Event-&gt;new();
- while( SDL::Events::poll_event($event) )
- {
- $quit = 1 if $event-&gt;type == SDL_QUIT
- }
+ SDL::Events::pump_events;
+
+ while( SDL::Events::poll_event($event) )
+ {
+ $quit = 1 if $event-&gt;type == SDL_QUIT
+ }
}
- sub calculate_next_positions{
- # Move the laser over
+ sub calculate_next_positions {
+ # move the laser
$laser++;
- # If the laser goes off the screen bring it back
- $laser = 0 if $laser &gt; $app-&gt;w();
+ # if the laser goes off the screen, bring it back
+ $laser = 0 if $laser &gt; $app-&gt;w();
}
-
- sub render {
-
- #Draw the background first
- $app-&gt;draw_rect( [0,0,$app-&gt;w, $app-&gt;h], 0 );
- #Draw the laser, in the middle height of the screen
- $app-&gt;draw_rect( [$laser, $app-&gt;h/2, 10, 2], [255,0,0,255]);
+ sub render {
+ # draw the background first
+ $app-&gt;draw_rect( [ 0, 0, $app-&gt;w, $app-&gt;h ], 0 );
- $app-&gt;update();
+ # draw the laser halfway up the screen
+ $app-&gt;draw_rect( [ $laser, $app-&gt;h / 2, 10, 2 ], [ 255, 0, 0, 255 ]);
+ $app-&gt;update();
}
-
- # Until we quit stay looping
- while(!$quit)
+ while (!$quit)
{
get_events();
calculate_next_positions();
render();
}</code></pre>
-<h3>Issues</h3>
-
-<p>This game loop works well for consoles and devices where the share of
-CPU clock speed is always known. The game users will be using the same
-processor characteristics to run this code. This means that each animation
-and calculation will happen at the exact same time in each machine.
-Unfortunately, this is typically not true for modern operating systems and
-hardware. For faster CPUs and systems with varying loads, we need to
-regulate updates so that game play will be consistent in most cases.</p>
-
-<h2>Fixed FPS</h2>
+<p>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
+<i>not</i> 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.</p>
-<p>One way to solve this problem is to regulate the "Frames Per Second" for
-your game's updates. A "frame" is defined as a complete redraw of the
-screen representing the updated game state. We can keep track of the number
-of frames we are delivering each second and control it using the technique
-illustrated below.</p>
+<h3>Fixed FPS</h3>
-<h3>Exercise</h3>
-
-<p>First run the below script with no fps fixing:</p>
+<p> </p>
-<pre><code> perl game_fixed.pl</code></pre>
+<p>One way to solve this problem is to regulate the number of frames per
+second the game will produce. A <i>frame</i> 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.</p>
-<p>You will see that the FPS is erratic, and the laser seems to speed up
-and slow down randomly.</p>
+<p>You can see this with the example program <i>game_fixed.pl</i>. When run
+with no arguments:</p>
-<p>Next fix the upper bounds of the FPS</p>
+<pre><code> $ <b>perl game_fixed.pl</b></code></pre>
-<pre><code> perl game_fixed.pl 1</code></pre>
+<p>.... 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:</p>
-<p>This will prevent the laser from going too fast, in this case faster
-then 60 frames per second.</p>
+<pre><code> $ <b>perl game_fixed.pl 1</b></code></pre>
-<p>Finally fix the lower bounds of the FPS</p>
+<p>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:</p>
-<pre><code> perl game_fixed.pl 1 1</code></pre>
+<pre><code> $ <b>perl game_fixed.pl 1 1</b></code></pre>
-<p>At this point the FPS should be at a steady 60 frames per second.
-However if this is not the case read on to the problems below.</p>
+<p>At this point the FPS should hold steady at 60 frames per second.</p>
<pre><code> use strict;
use warnings;
@@ -1030,30 +1033,24 @@
width =&gt; 200,
height =&gt; 200,
title =&gt; &#39;Pew Pew&#39;
- );
+ );
- # Variables
- # to save our start/end and delta times for each frame
- # to save our frames and FPS
my ( $start, $end, $delta_time, $FPS, $frames ) = ( 0, 0, 0, 0, 0 );
- # We will aim for a rate of 60 frames per second
+ # aim for a rate of 60 frames per second
my $fixed_rate = 60;
- # Our times are in micro second, so we will compensate for it
- my $fps_check = (1000/ $fixed_rate );
+ # compensate for times stored in microseconds
+ my $fps_check = (1000 / $fixed_rate );
- #Don&#39;t need to quit yet
my $quit = 0;
- #Start laser on the left
+ # start laser on the left
my $laser = 0;
sub get_events {
-
my $event = SDL::Event-&gt;new();
- #Pump the event queue
SDL::Events::pump_events;
while ( SDL::Events::poll_event($event) ) {
@@ -1068,161 +1065,145 @@
}
sub render {
-
- #Draw the background first
+ # draw the background first
$app-&gt;draw_rect( [ 0, 0, $app-&gt;w, $app-&gt;h ], 0 );
- #Draw the laser
+ # draw the laser
$app-&gt;draw_rect( [ $laser, $app-&gt;h / 2, 10, 2 ], [ 255, 0, 0, 255 ] );
- #Draw our FPS on the screen so we can see
+ # draw the FPS
$app-&gt;draw_gfx_text( [ 10, 10 ], [ 255, 0, 255, 255 ], &quot;FPS: $FPS&quot; );
$app-&gt;update();
}
-
# Called at the end of each frame, whether we draw or not
sub calculate_fps_at_frame_end
{
-
# Ticks are microseconds since load time
$end = SDL::get_ticks();
- # We will average our frame rate over 10 frames, to give less erratic rates
+ # smooth the frame rate by averaging over 10 frames
if ( $frames &lt; 10 ) {
-
- #Count a frame
$frames++;
-
- #Calculate how long it took from the start
$delta_time += $end - $start;
}
else {
+ # frame rate is Frames * 100 / Time Elapsed in us
+ $FPS = int( ( $frames * 100 ) / $delta_time )
+ if $delta_time != 0;
- # Our frame rate is our Frames * 100 / Time Elapsed in us
- $FPS = int( ( $frames * 100 ) / $delta_time ) if $delta_time != 0;
-
- # Reset our metrics
+ # reset metrics
$frames = 0;
$delta_time = 0;
}
-
-
-
}
while ( !$quit ) {
-
-
# Get the time for the starting of the frame
$start = SDL::get_ticks();
get_events();
- # If we are fixing the lower bounds of the frame rate
+ # if fixing the lower bounds of the frame rate
if( $ARGV[1] )
{
-
- # And our delta time is going too slow for frame check
+ # if delta time is going too slow for frame check
if ( $delta_time &gt; $fps_check ) {
- # Calculate our FPS from this
calculate_fps_at_frame_end();
- # Skip rendering and collision detections
- # The heavy functions in the game loop
- next;
-
+ # skip rendering and collision detections
+ # (heavy functions in the game loop)
+ next;
}
-
}
-
calculate_next_positions();
render();
- # A normal frame with rendering actually performed
+ # a normal frame with rendering actually performed
calculate_fps_at_frame_end();
- # if we are fixing the upper bounds of the frame rate
+ # if fixing the upper bounds of the frame rate
if ( $ARGV[0] ) {
- # and our delta time is going too fast compared to the frame check
+ # if delta time is going too fast compared to the frame check
if ( $delta_time &lt; $fps_check ) {
# delay for the difference
SDL::delay( $fps_check - $delta_time );
}
}
-
-
}</code></pre>
-<h3>Problems</h3>
+<p>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.</p>
+
+<p>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.</p>
-<p>Generally, this method is sufficient for most computers out there. The
-animations will be smooth enough that we see the same game play on
-differing hardware. However, there are some serious problems with this
-method. First, if a computer is too slow for 60 fps, it will skip a lot of
-rendering, and the animation will look sparse and jittery. Maybe it would
-be better for to set the fps bounds to 30 fps or lower for that machine.
-However, the developer cannot predict and hard code the best frame rate for
-a user. Secondly, if a CPU is fast, a lot of CPU cycles are wasted in the
-delay.</p>
+<p>The worst problem is that this technique still ties rendering speed to
+the CPU speed: a very fast computer will waste CPU cycles delaying.</p>
-<p>Finally, this method does not fix the fundamental problem that the
-rendering is tied to CPU clock speed.</p>
+<h3>Variable FPS</h3>
-<h3>Potential Fix: Variable FPS</h3>
+<p>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.</p>
-<p>One way to fix the problem of a computer being consistently faster or
-slower for the default Frames per Second set is to change the FPS
-accordingly. So for a slow CPU, the fps will be limited to 30 FPS and so
-on. In our opinion, although a consistent FPS can be achieved this way, it
-still presents the problem of differing animation speeds for different CPUs
-and systems. There are better solutions available that will maintain a
-decent FPS across all systems.</p>
+<p>Better solutions are available.</p>
<h2>Integrating Physics</h2>
-<p>The problem caused by coupling rendering to the CPU speed has a
-convenient solution. We can derive our rendering 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 just such features for our convenience
-through movement handlers and 'show' handlers.</p>
+<p>Describe movement and show handlers.</p>
-<p>A simple physics model for our laser has a consistent horizontal
+<p>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.</p>
+
+<p>A simple physics model for the laser has a consistent horizontal
velocity in pixels per time step at the window's mid-point:</p>
<pre><code> X = Velocity * time step,
Y = 100</code></pre>
-<p>Assuming a velocity of say 10, we will get points like:</p>
+<p>Assuming a velocity of 10, the laser will pass through the
+coordinates:</p>
-<pre><code> 0,100
- 10,100
- 20,100
- 30,100
+<pre><code> 0, 100
+ 10, 100
+ 20, 100
+ 30, 100
...
- 200,100</code></pre>
+ 200, 100</code></pre>
-<p>Note that it no longer matters at what speed this equation is processed,
-instead the values are coupled to the passage of real time.</p>
+<p>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.</p>
-<p>The biggest problem with this sort of solution is the required
-bookkeeping for the many objects and callbacks we may track. The
-implementation of such complex models is non-trivial and will not be
-explored in this manual. This topic is discussed at length in the
+<p>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
<code>SDLx::Controller</code> module.</p>
<h3>Laser in Real Time</h3>
-<p>This version of the laser example demonstrates the use of movement,
-'show' handlers, and the simple physics model described above. This example
-is also much simpler since SDLx::App is doing more of the book work for us.
-SDLx::App even implements the whole game loop for us.</p>
+<p>This version of the laser example demonstrates the use of movement, show
+handlers, and a simple physics model. This example also shows how
+<code>SDLx::App</code> can do more of the work, even providing the entire
+game loop:</p>
<pre><code> use strict;
use warnings;
@@ -1239,33 +1220,26 @@
my $laser = 0;
my $velocity = 10;
- #We can add an event handler
$app-&gt;add_event_handler( \&amp;quit_event );
- #We tell app to handle the appropriate times to
- #call both rendering and physics calculation
+ # tell app to handle the appropriate times to
+ # call both rendering and physics calculation
$app-&gt;add_move_handler( \&amp;calculate_laser );
$app-&gt;add_show_handler( \&amp;render_laser );
$app-&gt;run();
sub quit_event {
-
- #The callback is provided a SDL::Event to use
- my $event = shift;
-
- #Each event handler also returns you back the Controller call it
+ my $event = shift;
my $controller = shift;
- #Stopping the controller for us will exit $app-&gt;run() for us
$controller-&gt;stop if $event-&gt;type == SDL_QUIT;
}
sub calculate_laser {
- # The step is the difference in Time calculated for the
- # next jump
+ # The step is the difference in Time calculated for the next jump
my ( $step, $app, $t ) = @_;
$laser += $velocity * $step;
$laser = 0 if $laser &gt; $app-&gt;w;
@@ -1276,17 +1250,14 @@
# The delta can be used to render blurred frames
- #Draw the background first
+ # draw the background first
$app-&gt;draw_rect( [ 0, 0, $app-&gt;w, $app-&gt;h ], 0 );
- #Draw the laser
+ # draw the laser
$app-&gt;draw_rect( [ $laser, $app-&gt;h / 2, 10, 2 ], [ 255, 0, 0, 255 ] );
$app-&gt;update();
-
}</code></pre>
-<h2>Learn More</h2>
-
<p>To learn more about this topic please, see an excellent blog post by
<b>GafferOnGames.com</b>:
HTTP://GafferOnGames.Com/game-physics/fix-your-timestep.</p>
View
BIN dist/SDL_Manual.pdf
Binary file not shown.

0 comments on commit e51cb85

Please sign in to comment.