Writing games in Perl - part 1 - Bouncing Ball

| 3 Comments | No TrackBacks

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));
}
}

No TrackBacks

TrackBack URL: http://daniel.ruoso.com/cgi-bin/mt/mt-tb.cgi/165

3 Comments

With this simple case I think you can do with single-step Euler method for kinematics, but if you have more complicated physics (like in Gravity Golf) you would need to use more sophisticated numerical methods, like leap-frog method.

Very good post!!!

Leave a comment

About this Entry

This page contains a single entry by Daniel Ruoso published on February 1, 2010 1:54 PM.

My quest to SDL in Perl was the previous entry in this blog.

Writing games in Perl - Part 2 - Controlling the Ball is the next entry in this blog.

Find recent content on the main index or look in the archives to find all content.