Hack 16. Interactive Graphical Apps


Paint pretty pictures with Perl.

People often see Perl as a general-purpose language: you start by using it to write short scripts, do administrative tasks, or text processing. If you happen to appreciate it, you end up enjoying its flexibility and power to perform almost anything that doesn't require the speed of compiled binaries.

Consider instead the number one requirement of games. Unless you're exclusively a fan of card games, you'd say "CPU power." Fortunately a crazy guy named David J. Goehrig had the mysterious idea to bind the functions of the C low-level graphical library SDL for the Perl language. The result is an object-oriented approach to SDL called sdlperl.

Blitting with SDL Perl

With SDL you will manipulate surfaces. These are rectangular images, and the most common operation is to copy one onto another; this is blitting.[2] To implement a basic image loader with SDL Perl in just four non-comment lines of code, write:

[2] A blit is the action of copying a series of bits between memory addresses. The main goal of this operation is to perform the copy as fast as possible.

use SDL::App; # open a 640x480 window for your application our $app = SDL::App->new(-width => 640, -height => 480); # create a surface out of an image file specified on the command-line our $img = SDL::Surface->new( -name => $ARGV[0] ); # blit the surface onto the window of your application $img->blit( undef, $app, undef ); # flush all pending screen updates $app->flip( ); # sleep for 3 seconds to let the user view the image sleep 3;

You might wonder how to perform positioning and cropping during a blit. In the previous code, replace the two undef parameter values with instances of SDL::Rect, the first one specifying the rectangle to copy from the source surface, and the second specifying the rectangle where to blit on the destination surface. When you use undef instead, SDL uses top-left positioning and full sizing. Here's a blit replacement that specifies a 100x100 area in the source surface at a horizontal offset of 200 pixels:

$img->blit( SDL::Rect->new(     -width => 100, -height => 100, -x => 200, -y => 0 ), $app, undef);

Animating with SDL Perl

You're already closer than you think to being able to write a full-fledged game with SDL Perl. The previous example opened the application, created a surface from an image file, and showed it onscreen. Add sound and input handling, and you're (mostly) done! Sound is too easy to use to show here; input handling requires the proper monitoring of events reported by an instance of SDL::Event.

The simplest option for input handling is to loop waiting for new events. This is fine if you have no animated sprites on screen (movement will block while you wait on the loop), but if you need to handle animations, you need a main loop that performs more steps:

  • Erase all sprites at their current position.

  • Check events to see if user interaction changes anything in the game.

  • Move sprites and update game states.

  • Draw all sprites at their new position.

  • Tell SDL Perl to display any changes on screen.

  • Synchronize the animation by sleeping for a short amount of time, corresponding to the target animation speed.

Iterate forever through these steps, and you have the core of a game engine.

You might wonder if any visual artifacts (flickers) are visible between the moment you erase all sprites and the moment you draw them at their new positions. This will not happen, because windowing systems use back buffers, synchronizing them only when the program explicitely asks for a screen update.


A Working Animation

To illustrate everything, here's a short example program animating a colored rectangle and its fading tail (as shown in Figure 2-4). It first creates the needed series of surfaces, with a fading color and transparency, then implements the above main loop, animating the sprites along a periodic path. Monitoring events allows the user to temporarily stop the animation by pressing any key and to exit by hitting the escape key or by closing the application window. Read through this example for extensive details about its implementation.

Figure 2-4. An animation with SDL Perl


use SDL; use SDL::App; use strict; # specify the target animation speed here, in milliseconds between two # frames; for 50 frames per second, this is 20 ms  our $TARGET_ANIM_SPEED = 20; # define an array where to store all the rectangles changed between two # frames; this allows faster screen updates than using SDL::App#flip our @update_rects; # initialize the background surface to an image file if user specified one # on the commandline, to blank otherwise our $background = SDL::Surface->new(-f $ARGV[0] ? (-name   => $ARGV[0])                                                 : (-width  => 640,                                                    -height => 480     )); # open a 640x480 window for the application our $app = SDL::App->new(-width => 640, -height => 480); # copy the whole background surface to the application window $background->blit(undef, $app, undef); # update the application window $app->flip; # define an array where to store all the surfaces representing # the colored sprites with all the levels of color and transparency our @imgs = map {     # create a 30x20 surface for one sprite     my $surface = SDL::Surface->new(         -width => 30, -height => 20, -depth => 32     );     # fill the surface with a solid color; let it fade from     # blue to white while the mapped int value is iterated over     $surface->fill(undef,         SDL::Color->new(-r => 128+$_*255/45, -g => 128+$_*255/45, -b => 255)     );     # set the transparency of the surface (more and more transparent)     $surface->set_alpha(SDL_SRCALPHA, (15-$_)*255/15);     # convert the surface to the display format, to allow faster blits      # to the application window     $surface->display_format( ); } (1..15); # define a helper function to blit a surface at a given position on the # application window, adding the rectangle involved to the array of needed # updates sub blit_at {     my ($surface, $x, $y) = @_;     my $dest_rect = SDL::Rect->new(         -width => $surface->width( ), -height => $surface->height( ),         -x => $x, '-y' => $y     );     $surface->blit(undef, $app, $dest_rect);     push @update_rects, $dest_rect; } # define a helper function to blit the portion of background similar to the # area of a surface at a given position on the application window, adding # the rectangle involved to the array of needed updates; this actually # "erases" the surface previously blitted there  sub erase_at {     my ($surface, $x, $y) = @_;     my $dest_rect = SDL::Rect->new(         -width => $surface->width( ), -height => $surface->height( ),         -x => $x, '-y' => $y     );     $background->blit($dest_rect, $app, $dest_rect);     push @update_rects, $dest_rect; } # define an array to store the positions of the sprites, a counter to # calculate new positions of the sprite while it's animated, and a boolean # to know if the animation has stopped or not our (@pos, $counter, $stopped); # define an instance of SDL::Event for event monitoring our $event = SDL::Event->new( ); # start the main loop here while (1) {     # store the current value of the sdlperl milliseconds counter; the end     # of the mainloop uses it for animation synchronization     my $synchro_ticks = $app->ticks( );     # erase all sprites at their current positions (stored by @pos)     for (my $i = 0; $i < @pos; $i++)     {         erase_at($imgs[$i], $pos[$i]{'x'}, $pos[$i]{'y'});     }     # ask for new events     $event->pump( );     if ($event->poll != 0)     {         # if the event is a key press, stop the animation         if ($event->type( ) = = SDL_KEYDOWN)         {             $stopped = 1;         }         # if the event is a key release, resume the animation         if ($event->type( ) = = SDL_KEYUP)         {             $stopped = 0;         }         # if we receive a "QUIT" event (user clicked the "close" icon of the         # application window) or the user hit the Escape key, exit program          if ($event->type = = SDL_QUIT ||             $event->type = = SDL_KEYDOWN && $event->key_sym = = SDLK_ESCAPE)         {             die "quit\\n";         }     }     # if the animation is not stopped, increase the counter     $stopped or $counter++;     # insert a new position in top of @pos; let positions be a sine-based     # smooth curve     unshift @pos,     {         'x' => 320 + 200 * sin($counter/30),         'y' => 240 +  80 * cos($counter/25),     };     # remove the superfluous positions     @pos > 15 and pop @pos;     # draw all sprites at their new positions     for (my $i = @pos - 1; $i >= 0; $i--)     {         blit_at($imgs[$i], $pos[$i]{'x'}, $pos[$i]{'y'});     }     # tell sdlperl to flush all updates in the specified rectangles     $app->update(@update_rects);     # empty the array of rectangles needing an update     @update_rects = ( );     # wait the time necessary for this frame to last the target number of     # milliseconds of a frame.  This allows the animation to look smooth     my $to_wait = $TARGET_ANIM_SPEED - ($app->ticks - $synchro_ticks);                                     $to_wait > 0 and $app->delay($to_wait); }



Perl Hacks
Perl Hacks: Tips & Tools for Programming, Debugging, and Surviving
ISBN: 0596526741
EAN: 2147483647
Year: 2004
Pages: 141

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net