Daniel Ruoso

Últimos Posts

Categorias

Arquivo

Nuvem de Tags

07.02.2010  Writing games in Perl - Part 2 - Controlling the Ball

Following the first post on the subject of writing games in Perl, where we created a bouncing ball (I know, it is a rectangle, but I trust your imagination), this post is going to add something very important when dealing with games: input.

Silveira Neto suggested that I should include more specific instructions on how to start the game (and maybe a video), so I recalled that I didn't mention that all the sources for this posts (including the text) is currently hosted at a github repository (if you plan to contribute, please just ask me for commit permissions instead of forking the repo).

So if you want to run the codes posted here, you first need to:

$ git clone http://github.com/ruoso/games-perl.git

You can check for updates by calling

$ git pull origin master

from inside the games-perl directory. Each directory inside games-perl starts with the number of the post. The first post is inside the 1-bouncing-ball directory and the second is in 2-controlling. To run the the first code just get inside the first directory and call:

$ perl ball.pl

The second example code is based on the first, so the script name is the same, so just get into the other directory and run the same line. If you get an error like:

Can't locate SDL/Video.pm in @INC (@INC contains: /etc/perl
/usr/local/lib/perl/5.10.0 /usr/local/share/perl/5.10.0 /usr/lib/perl5
/usr/share/perl5 /usr/lib/perl/5.10 /usr/share/perl/5.10
/usr/local/lib/site_perl .) at ball.pl line 8.

It means you probably don't have the newest SDL, take a look at the first post to see how to get the newest redesigned SDL.

Controlling the Ball

Enough for the introduction, let's get to the actual code. The first thing we need is understanding SDL Events. If you ever programmed GUI applications or even if you wrote some javascript you are aware of how an event framework looks like. SDL is no exception, you need to wait (or poll) for the events, and each event will contain the information you need to figure out what happened.

In our case, we want to apply additional acceleration to the ball whenever the arrow keys are pressed. But if we have an event-based system, the way to figure out which of those four keys is currently pressed is keeping a state mask and update it when you receive keydown and keyup events.

So what we're going to do is to manipulate the acc_h and acc_v ball attributes depending on the keydown and keyup events. It might look complicated, but the only change we need is (this is inside ball.pl main loop):

  while (SDL::Events::poll_event($event)) {
    if ($event->type == SDL_QUIT) {
      exit;

    } elsif ($type == SDL_KEYDOWN &&
             $sevent->key_sym() == SDLK_LEFT) {
      $ball->acc_h(-1);

    } elsif ($type == SDL_KEYUP &&
             $sevent->key_sym() == SDLK_LEFT) {
      $ball->acc_h(0);

    } elsif ($type == SDL_KEYDOWN &&
             $sevent->key_sym() == SDLK_RIGHT) {
      $ball->acc_h(1);

    } elsif ($type == SDL_KEYUP &&
             $sevent->key_sym() == SDLK_RIGHT) {
      $ball->acc_h(0);

    } elsif ($type == SDL_KEYDOWN &&
             $sevent->key_sym() == SDLK_UP) {
      $ball->acc_v(1);

    } elsif ($type == SDL_KEYUP &&
             $sevent->key_sym() == SDLK_UP) {
      $ball->acc_v(0);

    } elsif ($type == SDL_KEYDOWN &&
             $sevent->key_sym() == SDLK_DOWN) {
      $ball->acc_v(-1);

    } elsif ($type == SDL_KEYUP &&
             $sevent->key_sym() == SDLK_DOWN) {
      $ball->acc_v(0);

    }
  }

So, this is it. Follows a small video of the game.

Postado por autor: ruoso em Perl.   Tags  sdlPerlsnippetperl5.

01.02.2010  Writing games in Perl - part 1 - Bouncing Ball

I've been saying I will write a lengthy description of my experiments with SDL in Perl for a while, today kthakore uploaded a SDL experimental version with all the code involved in this experiments. So I decided to start, but I decided to make it a serial post. I'm still unsure about how the format will look like for the rest of the posts, but I certainly would love any comment with ideas of where I should go next.

So at the first post, I'll try to make a very simple application: "bouncing ball". It is simply a ball bouncing in the screen, where the left and right cursors will accelerate them to each side.

Starting the environment

Well, at first you need version 2.3_5 or greater of the SDL module available here. Building the SDL perl binding will require the headers for most of the sdl related libraries. If you're in a Debian machine (or most debian-based distros) you can simply install the regular version of the SDL perl bindings. You can also do:

# apt-get build-dep libsdl-perl

This line requires you to have a deb-src line in your /etc/apt/sources.list. Mine looks like:

deb-src http://ftp.br.debian.org/debian lenny main

In the case you change your sources.list file, remember you need to call apt-get update for it to get the new info.

After that you can simply download SDL 2.3_5 from CPAN and follow the README instructions, which basically means (note that this version is experimental so you might notice some failing tests):

# perl Build.PL
# ./Build
# ./Build test
# ./Build install

You might setup a local::lib if you still want to run other applications that require the older and stable version of SDL (yes, there are a lot of API changes). I'm not a Windows user, but it has been reported that it works like charm when using Strawberry Perl.

If everything is fine, you should be able to get "2.35" when you do:

$ perl -MSDL -E 'say $SDL::VERSION'

You might always get into #sdl@irc.perl.org if you run into trouble.

The way I write games

Alright, before getting into the technicalities of how to write a game with SDL in Perl, let's think a bit on the mechanics of how a game works.

The first thing to realize is that a game needs to simulate some universe, and that this universe needs to have some universal rules like the physics you apply. For instance, it is very important that you decide, from the beggining, if you're going to have gravity in your game.

The second thing to realize is that, even if we have the impression that the time is a continuum, time is actually a sequence of events, like the shutter of a camera in burst mode (okay, for the not interested in photography, think in stop motion). That basically means: in frame A the ball was at position 10,10 and in frame B it was at 20,30. There's no in-between, you don't have to worry about it. You might be wondering if a collision might be lost in that move, but the point is, if the machine can't evaluate enough frames per second as to avoid that, it probably wouldn't be able to evaluate a more ellaborated calculation of the tragetories to see if they would have collided.

This provides an important simplification on how to think your game model. In this first example I'm going to overlook the modelling for interaction with different objects, since we have just a bouncing ball.

One last thing before we go on. You might be tempted to use pixels as your measuring unit, there's one important aspect to keep in mind. A Pixel is an integer value, which means that you'll need to do roundings for each frame. And if you need to store it as a integer value, you're going to accumulate imprecision, which might lead to weird effects, specially when the fps rate changes dramatically. My suggestion is to stick with the good old international measuring system and just use meters, a simple calculation can covert from/to pixels.

package Util;
use strict;
use warnings;
our $DPI = 96; # I think there's a sane way to fetch this value
sub m2px { int(((shift) * ($DPI / 0.0254)) + 0.5) }
sub px2m { (shift) / ($DPI / .0254) }

Modelling the ball

I think there probably isn't a better fit for Object Orientation than games, since you're actually dealing with simulated objects, and the most obvious choice is to use object orientation to work with it. So that's the attributes I can think right now to our object.

package Ball;
use Moose;

use constant g => 1.5;
use Util;
use SDL::Rect;

# how much space does it take
has radius => (is => 'rw', isa => 'Num', default => 0.005);

# Position - vertical and horizontal
has pos_v => (is => 'rw', isa => 'Num', default => 0.1);
has pos_h => (is => 'rw', isa => 'Num', default => 0.04);

# Velocty - vertical and horizontal
has vel_v => (is => 'rw', isa => 'Num', default => 0);
has vel_h => (is => 'rw', isa => 'Num', default => 0);

# Current acceleration - vertical and horizontal
# gravity is added later
has acc_v => (is => 'rw', isa => 'Num', default => 0);
has acc_h => (is => 'rw', isa => 'Num', default => 0);

With our virtual ball defined, we need to implement the simulation of time. And again, we need to think that time is not a continuum, so what we do is providing a "time_lapse" method that will recalculate the attributes of our object according to the ammount of time past.

sub time_lapse {
  my ($self, $old_time, $new_time, $height, $width) = @_;
  my $elapsed = ($new_time - $old_time)/1000; # convert to seconds...

  # now simple mechanics...
  $self->vel_h( $self->vel_h + $self->acc_h * $elapsed );
  # and add gravity for vertical velocity.
  $self->vel_v( $self->vel_v + ($self->acc_v - g) * $elapsed );

  # finally get the new position
  $self->pos_v( $self->pos_v + $self->vel_v * $elapsed );
  $self->pos_h( $self->pos_h + $self->vel_h * $elapsed );

  # this ball is supposed to bounce, so let's check $width and $height
  # if we're out of bounds, we assume a 100% efficient bounce.
  if ($self->pos_v pos_v($self->pos_v * -1);
    $self->vel_v($self->vel_v * -1);
  } elsif ($self->pos_v > $height) {
    $self->pos_v($height - ($self->pos_v - $height));
    $self->vel_v($self->vel_v * -1);
  }

  if ($self->pos_h pos_h($self->pos_h * -1);
    $self->vel_h($self->vel_h * -1);
  } elsif ($self->pos_h > $width) {
    $self->pos_h($width - ($self->pos_h - $width));
    $self->vel_h($self->vel_h * -1);
  }


}

From the simulated universe to pixels

You probably noticed that we haven't talked much about the game development per se, so let's start thinking about it

The first difference from the simulated universe and the actual game is the coordinate system. In our universe the vertical position 0 is near the floor, but in the screen, the vertical position 0 is at the top of the screen.

The other difference, which I already mentioned, is that our measures are in meters, and the screen is measured in pixels. The SDL library provides an important class to interact with this issue, the SDL::Rect.

That way, we're going to have a method to return a rect - but we need to remember to only return a rect that is inside the expected bounds, otherwise SDL will throw an exception.

sub get_rect {
  my ($self, $height, $width) = @_;

  my $inverted_v = $height - $self->pos_v;

  my $x = Util::m2px( $self->pos_h - $self->radius );
  my $y = Util::m2px( $inverted_v - $self->radius );
  my $h = Util::m2px( $self->radius * 2 );
  my $w = Util::m2px( $self->radius * 2 );

  my $screen_w = Util::m2px( $width );
  my $screen_h = Util::m2px( $height );

  if ($x  $screen_w) {
    $w -= ($x + $w) - $screen_w;
  }

  if ($y  $screen_h) {
    $h -= ($y + $h) - $screen_h;
  }

  return SDL::Rect->new( $x, $y, $w, $h );
}

Painting

The last part is actually drawing the ball, which involves painting the ball in the correct place. In our first version, our ball will be a square, since it's the most primitive drawing we have and I don't want to get into that specifics.

my $color;
sub draw {
  my ($self, $surface, $height, $width) = @_;
  unless ($color) {
    $color = SDL::Video::map_RGB
      ( $surface->format(),
        0, 0, 255 ); # blue
  }
  SDL::Video::fill_rect
      ( $surface,
        $self->get_rect($height, $width),
        $color );
}

Yeah, I know, it's an ugly ball, but it bounces... ;)

Putting the pieces together

Now we just need the actual application to use our Ball. Note that the cycle is basically:

  • Listen for events
  • Evaluete the time_lapse
  • Draw the background
  • Draw the ball
  • Update the parts of the screen that where changed
  • Ask for a delay to keep the desired fps rate

Note that I could have asked for it to update the entire screen instead of only the parts that were changed, but that particular operation is actually the most expensive thing you can have in your app, so we try to update as few parts of the screen as possible.

#!/usr/bin/perl

use 5.10.0;
use strict;
use warnings;

use SDL;
use SDL::Video;
use SDL::App;
use SDL::Events;
use SDL::Event;
use SDL::Time;

use lib 'lib';
use Util;
use Ball;

my $ball = Ball->new;
my $width = 0.2;
my $height = 0.15;
my $fps = 60;

my $app = SDL::App->new
  ( -title => 'Bouncing Ball',
    -width => Util::m2px($width),
    -height => Util::m2px($height));

my $black = SDL::Video::map_RGB
  ( $app->format(),
    0, 0, 0 ); # black

my $event = SDL::Event->new();
my $time = SDL::get_ticks;
my $app_rect = SDL::Rect->new(0, 0, $app->w, $app->h);
my $ball_rect = $ball->get_rect($height, $width);

while (1) {
  my $oldtime = $time;
  my $now = SDL::get_ticks;

  while (SDL::Events::poll_event($event)) {
    exit if $event->type == SDL_QUIT;
  }

  my $old_ball_rect = $ball_rect;

  $ball->time_lapse($oldtime, $now, $height, $width);

  $ball_rect = $ball->get_rect($height, $width);

  SDL::Video::fill_rect
      ( $app,
        $app_rect,
        $black );

  $ball->draw($app, $height, $width);

  SDL::Video::update_rects
      ( $app,
        $old_ball_rect, $ball_rect );

  $time = SDL::get_ticks;
  if (($time - $oldtime) < (1000/$fps)) {
    SDL::delay((1000/$fps) - ($time - $oldtime));
  }
}

Postado por autor: ruoso em Perl.   Tags  sdlPerlsnippetperl5.

08.01.2010  My quest to SDL in Perl

I think this thing started when Silveira Neto posted his code snippet on Pygame animating the tile set he has been creating for a long time. I knew for a long time that Perl supported SDL and that Frozen Bubble is written in Perl, but I have never tried it myself.

Working with graphics wasn't new to me, back in the days I played with BASIC, I wrote a blackjack program with animated cards, some time later - already in Linux, I played a bit with vgalib. I've been also thinking for a while on the concept of how to evaluate the progress of time in a game environment.

If you join this with the fact that I now have a 1yo kid, it kinda urged me to writing something in SDL for him. The idea was quite simple, as he doesn't coordinate mouse and keyboard movement yet, the game would simply present random images and sounds as there was any event at all.

So I started by using a simple event loop for the SDL events, and a timer to evaluate the time lapse and draw the game. To my surprise, using the timer caused segfaults, because the timer callback runs in a different thread in SDL, but the binding didn't provide any support for it and all I got was kaboom.

I worked around this issue by using Event, a simple event library I already used some other places, to implement the timer. It worked like charm, as there was events in SDL, it was creating rects of random sizes and colors that would last for a random amount of time, fading to black.

Then I thought: Ok, now let's proceed to audio. To my surprise, the callbacks for procedural audio generation were simply missing - Frozen Bubble only plays pre-produced audio files - and my idea was to produce random audio (not noise, but sounds in random frequencies). This was the push I needed to get to the redesign branch in github and start porting the app to it.

Lots of research later, with the help of kthakore++ and the tips from Nicholas++, we managed to have a working version of SDL based on threads and threads::shared. This was a very great moment. I must confess I always kept some envy from python people because of the - so I thought - better support for threads in Python. At some point in this path I found out that Python doesn't support threads at all, because they have a Global Interpreter Lock, which basically means that POE implements the same kind of scalability than Python (and it also explained why the people at ce.gov.br have so much trouble with zope scalability). So now not only we have a thread-enabled SDL binding, but it is *really* threaded, which means that the audio callback will really run at the same time as the timer callback , which will also run together with the event loop. And now I'm happy to know that Perl does have a better threading support than Python - yeah, I know envy is a bad thing, but... whatever...

After I finished Tecla, the app for my 1yo kid, I decided to play with Game::PerlInvaders, which I saw some time ago and I knew some things I could improve. The result is in the branch refact_ruoso at github.

I should make another post, with the details on how tecla and the rewrite of Game::PerlInvaders work, but that's it for now.

Postado por autor: ruoso em Perl.   Tags  sdlPerlsnippetperl5.