Beefy Boxes and Bandwidth Generously Provided by pair Networks
Problems? Is your data what you think it is?
 
PerlMonks  

Sprite animation with SDL_perl

by Mirage (Sexton)
on May 05, 2002 at 13:33 UTC ( [id://164118]=perlquestion: print w/replies, xml ) Need Help??

Mirage has asked for the wisdom of the Perl Monks concerning the following question:

Fellow Monks, I recently tried to do some graphics with perl, and ended up in using SDL_perl, using frozen bubble(btw, great game) as a reference. However, my little game, a clone of that old side-scrolling shoot-em up's, is kinda slow, which I think mainly is reasoned in the way I did the whole thing. So all I ask is to give me some hints, how it could be improved and maybe if there is a major mistake I did.
The code:
#!/usr/bin/perl -w use strict; use SDL::App; use SDL::Surface; use SDL::Event; $| = 1; my (@update_rects, @fire_items, %images, $app, $event, $background); sub init_sdl { my $sdlflags = SDL_ANYFORMAT | SDL_HWSURFACE | SDL_DOUBLEBUF | SDL +_HWACCEL | SDL_ASYNCBLIT; $app = new SDL::App ( -flags => $sdlflags,-title => 'Space',-width + => '800',-height => '600'); $event = new SDL::Event; $event->set(SDL_SYSWMEVENT,SDL_IGNORE); $background = new SDL::Surface(-width => $app->width, -height => $ +app->height, -depth => 32, -Amask => '0 but true'); #load images foreach ('ship', 'beam1') { $images{$_} = new SDL::Surface(-name => "/home/mirage/perl/space/i +mages/$_.png"); $images{$_}->{-surface} or die "FATAL: Couldn't load image file $_ +.\n"; } } sub put_image($$$) { my ($image, $x, $y) = @_; my $rect = new SDL::Rect(-width => $image->width, -height => $imag +e->height); my $drect = new SDL::Rect(-width => $image->width, -height => $ima +ge->height, -x => $x, -y => $y); $image->blit($rect, $app, $drect); push @update_rects, $drect; } #sub fillrect { # my ($width, $height, $x, $y) = @_; # my $rect = new SDL::Rect(-width => $width, -height => $height, -x + => $x, -y => $y); # $app->fill($rect,0x000000); #} sub erase_image_from($$$$$) { my ($width, $height, $x, $y, $img) = @_; my $drect = new SDL::Rect(-width => $width, -height => $height, -x + => $x, '-y' => $y); $img->blit($drect, $app, $drect); push @update_rects, $drect; } sub erase_image($$$$) { my ($width, $height, $x, $y) = @_; erase_image_from($width, $height, $x, $y, $background); } sub handle_fire { my ($new, $x, $y) = @_; my $skip = 0; if($new){ push(@fire_items, $x.' '.$y); } my $i = 0; while($i < @fire_items) { my ($xc, $yc) = split(' ',$fire_items[$i]); splice(@fire_items,$i,1) and $skip = 1 unless $xc < $app->widt +h + $images{'beam1'}->width; if($skip){ $skip = 0; next; } &erase_image($images{'beam1'}->width, $images{'beam1'}->height +,$xc-10,$yc); &put_image($images{'beam1'}, $xc, $yc); $fire_items[$i]=join(' ',$xc + 10, $yc); $i++; } } &init_sdl; my $x = 50; my $y = ($app->height / 2) - $images{'ship'}->height; my ($xplus, $yplus, $xminus, $yminus, $fire) = 0; while(1) { $event->pump(); if ($event->poll != 0) { if ($event->type == SDL_KEYDOWN) { my $key = $event->key_sym(); $xplus = 1 if $key eq SDLK_RIGHT; $xminus = 1 if $key eq SDLK_LEFT; $yminus = 1 if $key eq SDLK_UP; $yplus = 1 if $key eq SDLK_DOWN; $fire = 1 if $key eq SDLK_SPACE; exit if $key eq SDLK_ESCAPE; } if ($event->type == SDL_KEYUP) { my $key = $event->key_sym(); $xplus = 0 if $key eq SDLK_RIGHT; $xminus = 0 if $key eq SDLK_LEFT; $yminus = 0 if $key eq SDLK_UP; $yplus = 0 if $key eq SDLK_DOWN; $fire = 0 if $key eq SDLK_SPACE; } } #delete if movement if($xplus or $xminus or $yplus or $yminus) { &erase_image($images{'ship'}->width, $images{'ship'}->height,$x,$y); } #change values if key is pressed my $step = 10; $x+=$step if $xplus and $x < ($app->width - $images{'ship'}->width); $x-=$step if $xminus and $x >= $step; $y+=$step if $yplus and $y < ($app->height - $images{'ship'}->height); $y-=$step if $yminus and $y >= $step ; #draw gunshots, possibly add new one &handle_fire($fire, $x, $y); #draw ship &put_image($images{'ship'},$x,$y); $app->update(@update_rects); @update_rects=(); &SDL::DisplayFormat($images{'ship'}->{-surface}); &SDL::Delay(20); } __END__
Thanks!

Replies are listed 'Best First'.
Re: Sprite animation with SDL_perl
by belg4mit (Prior) on May 05, 2002 at 16:11 UTC
    Well this won't affect speed much but it removed duplicate code and increases readability.

    UPDATE: my is expensive. Move it out of (important) tight loops.

    my($key, $d); while(1) { #Does this really need to be called each round? #(no dox), if so move into until $event->pump(); until ($event->poll != 0) { select(undef,undef,undef, 0.01); } #micr +osleep $key = $event->key_sym(); $d = 0; $d = 1 if $event->type == SDL_KEYDOWN; $xplus = $d if $key eq SDLK_RIGHT; $xminus = $d if $key eq SDLK_LEFT; $yminus = $d if $key eq SDLK_UP; $yplus = $d if $key eq SDLK_DOWN; $fire = $d if $key eq SDLK_SPACE; exit if $key eq SDLK_ESCAPE; }

    --
    perl -pew "s/\b;([mnst])/'$1/g"

      There's a little mistake in your code, somehow(don't know really what poll does) you have to keep the if statement, or else repeated movements don't work.
        Well I beg to differ about it being a bug in my code ;-) It's more an issue of the library. Though there really oughtn't be any difference between how the two run. It might actually be the pump, did you try moving it into the until loop as the comment suggested might be required?

        --
        perl -pew "s/\b;([mnst])/'$1/g"

Re: Sprite animation with SDL_perl
by CharlesClarkson (Curate) on May 05, 2002 at 17:17 UTC

    There is more to address here than algorithm efficiency. You need to learn more of perls' features. One glaring example is the lack of return values from subroutines. In perl subroutines can also be functons. Another example is the global variables defined at the beginning of the program. Globals are variables that are used anywhere in the program. They are usually frowned on. Let's take a look at one global: @fire_items. It is used exclusively in the subroutine: handle_fire().

    handle_fire() is called each pass through the while block. The reason for making @fire_items global is because we don't want to lose the items between passes. The problem is if we want to use @fire_items in the future we need to take into account that at least one subroutine is changing it in an inobvious way. The use of many global variables makes it difficult to optimize and debug a program. There are too many dependencies.

    When designing a subroutine, pretend a manager or teacher has assigned you the task. She has given the inputs, the outputs, and a few details. The rest of the program is not written yet or is not shown to you. Changing a variable in the program is not an option. Everything you need must be passed in and everthing you change must be passed back out. You can break the rules as you become a better programmer or as the situation dictates. While your learning, try to find a way to stick to them.

    Here is the handle_fire() subroutine:

    sub handle_fire { my ($new, $x, $y) = @_; my $skip = 0; if($new){ push(@fire_items, $x.' '.$y); } my $i = 0; while($i < @fire_items) { my ($xc, $yc) = split(' ',$fire_items[$i]); splice(@fire_items,$i,1) and $skip = 1 unless $xc < $app->widt +h + $images{'beam1'}->width; if($skip){ $skip = 0; next; } &erase_image($images{'beam1'}->width, $images{'beam1'}->height +,$xc-10,$yc); &put_image($images{'beam1'}, $xc, $yc); $fire_items[$i]=join(' ',$xc + 10, $yc); $i++; } }

    Three items initially caught my eye:

    1. The use of an index to step through an array.
    2. The use of the & prefix to subroutines. Re-read perlsub. & doesn't always mean what you think.
    3. The use of @fire_items, %images, and $app which have not been passed to the subroutine.

    This is how handle_fire() is called:

    #draw gunshots, possibly add new one handle_fire( $fire, $x, $y );

    Let's change handle_fire( $fire, $x, $y ) to accept and return a reference to @fire_items.

    #draw gunshots, possibly add new one $fire_items = handle_fire( $fire, $x, $y, $fire_items );

    Let's keep $fire_items defined outside the while block to maintain it's value between passes. Remember $fire_items is a reference to @fire_items.

    my $fire_items; . . . while (1) { . . . #draw gunshots, possibly add new one $fire_items = handle_fire( $fire, $x, $y, $fire_items ); . . . }

    Here's our sub again (with some whitespace added).

    sub handle_fire { my ( $new, $x, $y ) = @_; my $skip = 0; if ( $new ){ push(@fire_items, $x.' '.$y); } my $i = 0; while( $i < @fire_items ) { my ( $xc, $yc ) = split(' ', $fire_items[$i] ); splice( @fire_items, $i, 1 ) and $skip = 1 unless $xc < $app-> +width + $images{beam1}->width; if( $skip ){ $skip = 0; next; } erase_image( $images{beam1}->width, $images{beam1}->height, $x +c-10, $yc ); put_image( $images{beam1}, $xc, $yc ); $fire_items[$i] = join(' ', $xc + 10, $yc ); $i++; } }

    In order to use $fire_items reference we'll need to de-reference it. We'll also need to add a return and change the line that receives the passed variables.

    sub handle_fire { my ( $new, $x, $y, $fire_items ) = @_; my $skip = 0; if ( $new ){ push(@$fire_items, $x.' '.$y); } my $i = 0; while( $i < @$fire_items ) { my ( $xc, $yc ) = split(' ', $$fire_items[$i] ); splice( @$fire_items, $i, 1 ) and $skip = 1 unless $xc < $app- +>width + $images{beam1}->width; if( $skip ){ $skip = 0; next; } erase_image( $images{beam1}->width, $images{beam1}->height, $x +c-10, $yc ); put_image( $images{beam1}, $xc, $yc ); $$fire_items[$i] = join(' ', $xc + 10, $yc ); $i++; } return $fire_items; }

    Now let's look at the data structure. By using an Array of Arrays, we can eliminate the joining and splitting of spaces from $x and $y.

    if ( $new ){ push(@$fire_items, $x.' '.$y); }
    Becomes:
    if ( $new ){ push @$fire_items, [$x, $y]; }
    Or just:
    push @$fire_items, [$x, $y] if $new;

    and:

    my ( $xc, $yc ) = split(' ', $$fire_items[$i] );
    Becomes:
    my ( $xc, $yc ) = @{ $fire_items[$i] };

    and:

    $$fire_items[$i] = join(' ', $xc + 10, $yc );
    Becomes:
    $$fire_items[$i] = [$xc + 10, $yc];

    The splice is removing something from the array and going to the next item. Since $fire_items is a reference, changing it changes an external variable. Something we normally want to avoid if possible. Also $skip is only used to get to the next index. First, we'll get rid of $skip. As far as I can tell splice always returns a value:

    unless ( $xc < $app->width + $images{beam1}->width ) { splice( @$fire_items, $i, 1 ); next; }

    Now we'll add a temporary array to hold the values we're keeping and to avoid changing the $fire_items array. This is what I ended up with:

    sub handle_fire { my ( $new, $x, $y, $fire_items ) = @_; my $i = 0; my @temp; while ( $i < @$fire_items ) { my ( $xc, $yc ) = @{ $fire_items[$i] }; next unless $xc < $app->width + $images{beam1}->width; erase_image( $images{beam1}->width, $images{beam1}->height, $x +c-10, $yc ); put_image( $images{beam1}, $xc, $yc ); push @temp, [$xc + 10, $yc]; $i++; } push @temp, [$x, $y] if $new; return \@temp; }

    Since we are no longer using splice, we no longer need to track the index:

    sub handle_fire { my ( $new, $x, $y, $fire_items ) = @_; my @temp; foreach my $item ( @$fire_items ) { my ( $xc, $yc ) = @$item; next unless $xc < $app->width + $images{beam1}->width; erase_image( $images{beam1}->width, $images{beam1}->height, $x +c-10, $yc ); put_image( $images{beam1}, $xc, $yc ); push @temp, [$xc + 10, $yc]; } push @temp, [$x, $y] if $new; return \@temp; }

    For speed you could forgo not changing $fire_items, but pushing is probably faster than splicing.

    HTH,
    Charles K. Clarkson
    Clarkson Energy Homes, Inc.
      Thanks, some aspects quite interesting though not all was new, I just let some things open, because I planned to expand a bit more. However, I still don't understand why it runs that slow as sdl is rather fast and my cpu also is not one of the slowest. Is it a matter with perl or still with optimizing every little bit of code(since that can't be nessecary for this little efford I don't really think so)?
      Your changes are good for programming issues, but in speed nothing has increased.
Re: Sprite animation with SDL_perl
by IlyaM (Parson) on May 05, 2002 at 14:29 UTC
      Well thanks, however, the module is able to tell me which parts of code take what time, but if you look at the code you may notice that besides from using oop, I also don't use threads, which may give some speed improvements. Mainly I wonder how these games were made in the old days, where cumputers were slow, and threads were unknown.
        How where they made in the old days?
        • Assembler
        • C
        • Look up tables for almost anything other than an addition or subtraction
        • Tweaked graphic modes so you code write to all 4 planes of a VGA card so you could write 4 pixels at a time for scaling and filling.
        • Everthing based on a a pow of 2.
        • Virtually all multiplications and divisions could then be bit-shifted instead of an actual multiplication.
        • If you needed more than int precision you used fixedpoint math.
        • 2D games usually used bit-masks so you could do cheap collision tests using bitwise operators.
        • Self modifying code to cut down on the codesize (usually less than 64K to work with)


        -Lee

        "To be civilized is to deny one's nature."
        Threads cannot make program run faster unless your computer has more than one CPU because on systems with single CPU no matter if you use threads or not your program cannot force CPU to run faster. Carefuly written event based program can actually beat threads based program because they avoid system's overhead for using threads.

        Try to search google for 'Threads vs Events'. You may find some interesting reading on this topic.

        --
        Ilya Martynov (http://martynov.org/)

        I haven't played with it yet myself, but Event might just be of some use to you.

        jeffa

        L-LL-L--L-LL-L--L-LL-L--
        -R--R-RR-R--R-RR-R--R-RR
        B--B--B--B--B--B--B--B--
        H---H---H---H---H---H---
        (the triplet paradiddle with high-hat)
        

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://164118]
Approved by ybiC
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others learning in the Monastery: (5)
As of 2024-04-23 20:18 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found