Vom Himmel hoch (Linux-Magazin, Oktober 2007)

Abbildung 1: Der Perlmeister springt vom Himmel. Das zugehörige Video gibt's auf [2].

Programmierer von Computerspielen rechnen mit physikalischen Formeln und setzen spezielle Tricks ein, um Grafiken realitätsnah zu animieren. Der als Perl-Wrapper verfügbare Simple DirectMedia Layer (SDL) bietet ein leistungsfähiges Framework, um mit wenigen Zeilen einfache 2D-Welten zu erschaffen.

Als ich neulich das Video meines Tandemfallschirmsprungs von einer ollen VHS-Cassette auf Youtube überspielte und den Link [2] einigen Arbeitskollegen schickte, entbrannte eine heftige Diskussion über die bei einem Sprung relevanten physikalischen Gesetze.

In einem vereinfachten Modell ohne Seitenwindeinfluss startet der Springer mit einer vertikalen Geschwindigkeit vy=0 und beschleunigt sofort aufgrund der Erdanziehung nach unten. Der nach unten wirkenden Erdanziehungskraft wirkt der Luftwiderstand entgegen, der sich mit steigender Fallgeschwindigkeit stetig vergrößert. Abhängig vom Gewicht und Ausmaßen des Springers stellt sich dann bei 180/h ein Kräftegleichgewicht ein und der Springer fällt mit konstanter Geschwindigkeit nach unten. Für den Springer fühlt sich das an, als würde er im Weltall schweben und dieser Zustand hält solange an, bis der Fallschirm geöffnet wird, was sich anfühlt, als zöge einen jemand an einem Seil ruckartig nach oben.

Freier Fall

Das Skript skydive in Listing 1 simuliert das Fallexperiment. Es stellt den frei fallenden Springer in einem Icon dar, das erst langsam, dann immer schneller nach unten fällt, bis die konstante Endgeschwindigkeit von 50 m/s (180 km/h) erreicht ist. Der User löst den Fallschirm mit einem Tastendruck auf die Taste ``Cursor-UP'' aus, worauf das Icon sich in einen Springer mit offenem Fallschirm verwandelt, der zunächst stark abbremst und dann langsam der Erde entgegensegelt.

Abbildung 2: Der Springer beschleunigt nach dem Absprung und erreicht nach einigen Sekunden die konstante Fallgeschwindigkeit.

Abbildung 3: Der Springer fällt mit 40.96 m/s und ist nicht mehr weit vom Boden entfernt -- allerhöchste Zeit, mit der Taste "Cursor-Up" den Fallschirm zu öffnen!

Abbildung 4: Der Fallschirm geht auf und bremst den Springer ab. Bei einer sicheren Landung fällt der Springer weniger als 3.1 m/s.

Das Skript zählt die Sekunden vom Absprung bis zur sicheren Landung. Das Ziel ist es, die Reißleine möglichst spät zu ziehen, aber darauf zu achten, dass die Landegeschwindigkeit bei offenem Fallschirm nicht mehr als 3 m/s (etwa 11 km/h) beträgt, damit der Springer sich nicht verletzt. Das Display zeigt links die verstrichene Zeit in Sekunden und rechts die aktuelle Fallgeschwindigkeit in m/s an. Erreicht der Spieler eine neue Bestzeit, wird diese unterhalb des aktuellen Zählerfeldes eingeblendet und bleibt dort, bis ein neuer Versuch die Bestzeit unterschreitet. Landet der Spieler aber zu schnell, verwandelt sich das Fallschirm-Icon am Boden wieder in einen Springer ohne Fallschirm, um einen missglückten Versuch anzuzeigen. Die Bestzeit bleibt in diesen Fällen unberührt.

Abbildung 5: Sichere Landung mit 3.0 m/s und neuer Rekord mit 17.60 Sekunden!

Abbildung 6: Gute Zeit, aber ungültiger Versuch, da die Landegeschwindigkeit mit 45.33 m/s viel zu hoch war.

Fallphysik

Die Geschwindigkeit eines aus dem Ruhezustand konstant beschleunigten Körpers beträgt v=a*t. Im Falle eines aus einem Flugzeug springenden Körpers ist die Beschleunigung a gleich der Erdbeschleunigung (9.82 m/s**2) und die Zeit t tickt in Sekunden und startet beim Absprung.

Die der Gravitationskraft entgegen wirkende Luftreibung lässt sich als negative Beschleunigung auslegen, die für vy=0 gleich Null und für vy=vterm gleich der Erdbeschleunigung 9.81m/s**2 ist.

Der Luftwiderstand berechnet sich aus Masse und Geschwindigkeit des Springers sowie dem Reibungskoeffizienten des Springers in Luft. Nach [5] beschleunigt ein 80kg schwerer Erwachsener nach dem Absprung binnen 14 Sekunden auf etwa 190 km/h, wobei er 548 Meter zurücklegt. Anschließend fällt er 3000 Meter pro Minute mit konstanter Geschwindigkeit, bis der Fallschirm sich öffnet.

Diese Endgeschwindigkeit hängt allerdings auch noch von der Fluglage ab. Mit dem Kopf voran ist der Luftwiderstand geringer und es lassen sich leicht über 200km/h erreichen. Details zur Berechnung des Luftwiderstands stehen unter [6]. Bewegt sich der Körper schließlich mit konstanter Geschwindigkeit fort, weil Gravitiations- und Luftreibungskraft sich gegenseitig aufheben, legt er mit der Geschwindigkeit v in der Zeit t die Strecke s=v*t zurück.

Spielphysik

In einem animierten Spiel, das 50 Frames pro Sekunde zeichnet, muss man keine Multiplikation ausführen, um die gleichmäßige Fortbewegung einer Figur zwischen zwei Frames auszurechnen. Man dividiert einfach die Geschwindigkeit in m/s durch 50 und addiert das Ergebnis zur aktuellen Position. Wiederholt man das 50 Mal für jeden Frame, ist man am Ende einer Sekunde genau bei s=v*1s, was der physikalischen Formel für die gleichmäßige Fortbewegung entspricht.

Wegen der relativ kleinen Zeitabstände zwischen den einzelnen Frames funktioniert das sogar für die gleichmäßig beschleunigte Bewegung eines zur Erde fallenden Körpers. Zusätzlich zur Position ändert sich allerdings in jedem Frame die Geschwindigkeit des Körpers. Um diese zu berechnen, addiert man einfach jedesmal 1/50 der Erdbeschleunigung zur aktuellen Geschwindigkeit hinzu. Macht man das 50 mal hintereinander, ist man genau bei v=a*1s.

Lug und Trug

Die Beschleunigung ist allerdings auch nicht konstant. Fällt der Körper aus dem Flugzeug, ist die Beschleunigung 9.81 m/s**2, wenn man Effekte wie Seitenwind und Auftrieb einmal vernachlässigt. Mit steigender Geschwindigkeit hemmt die Luftreibung den Fall, und die tatsächliche Beschleunigung ist kleiner als die durch die Gravitationskraft erzeugte. Erreicht der Körper die maximale Fallgeschwindigkeit vterm, ist die Beschleunigung gleich Null und der Springer fällt mit konstanter Geschwindigkeit. Das Spiel löst diese Berechnung mit einem vereinfachten Verfahren, indem es mit der Funktion deceleration einen Wert berechnet, der von der aktuellen Beschleunigung abgezogen wird. Er ermittelt sich aus dem Verhältnis von aktueller und maximaler Geschwindigkeit, nimmt also einen linearen Zusammenhang an.

Wird der Fallschirm geöffnet, ist die Beschleunigung sogar negativ. Allerdings bremst der Fallschirm nicht beliebig stark, das Spiel limitiert die maximal herrschende Gegenkraft auf 2g. Was deceleration treibt, ist physikalisch gesehen zwar Lug und Trug, aber für das Spiel reicht die Genauigkeit.

Blit mal das Image

Bewegt sich ein Icon durch das Spielfeld, wie ein vom Himmel fallender Fallschirmspringer, löscht SDL zunächst den alten Eintrag und zeichnet dann das Bild an der neuen Position. Das Icon ist schon als Image im Speicher und wird mit der Methode blit() von einer Speicherposition an eine andere kopiert. Mit diesem Trick entstehen Änderungen an der Spieloberfläche mit beeindruckender Geschwindigkeit und der Benutzer schwebt in der Illusion einer realen Welt.

Um zum Beispiel das Logo des Spiels, eine mit Gimp erstellte PNG-Grafik, an den oberen Rand des Spielfelds zu zeichnen, lädt das Skript die Datei logo.png in Zeile 21 mit dem Konstruktor der Klasse SDL::Surface von der Platte in den Speicher. Zeile 39 definiert dann ein Rechteck der Klasse SDL::Rect mit Länge, Breite, sowie der gewünschten Position der Grafik im Bild. Die X-Koordinaten laufen von links nach rechts, die Y-Koordinaten von oben nach unten. Die Methode blit() der Grafik in $logo in Zeile 43 kopiert die Daten blitzschnell auf die Spieloberfläche $app.

SDL frischt die Oberfläche allerdings nicht sofort auf, sondern wartet aus Performance-Gründen, bis der Programmierer mit der Methode update() den Befehl dazu gibt. So kann SDL viele Rechtecke blitzschnell auf einmal auffrischen, sodass der Eindruck einer ruhig laufenden Animation entsteht.

Listing 1: skydive

    001 #!/usr/bin/perl -w
    002 use strict;
    003 use SDL;
    004 use SDLMove;
    005 use SDL::TTFont;
    006 
    007 local *main::TEXT_SHADED = \&SDL::Event::TEXT_SHADED;
    008 
    009 my $SPEED_MS    = 20;
    010 my $FRAMES_PSEC = 1000.0/$SPEED_MS;
    011 my $VTERM_FREE  = 50; # Terminal speed
    012 my $VTERM_PARA  =  3; # ... with parachute
    013 my $WIDTH       = 158;
    014 my $HEIGHT      = 500;
    015 my $G           = 9.81;
    016 my $MAX_LAND    = 3.1;
    017 
    018 my $bg_color = SDL::Color->new(
    019   -r => 0, -g => 0, -b => 0 );
    020 my $fg_color = SDL::Color->new(
    021   -r => 0xff, -g => 0x0, -b => 0x0 );
    022 
    023 my $logo = SDL::Surface->new(
    024               -name => "logo.png");
    025   # Load player icons
    026 my $diver = SDL::Surface->new(
    027               -name => "dive.png");
    028 my $para = SDL::Surface->new(
    029               -name => "para.png");
    030 
    031 my $app = SDL::App->new(
    032   -title => "Skydive 1.0", -depth => 16,
    033   -width => $WIDTH, -height => $HEIGHT);
    034 
    035 my $fontpath = "/usr/X11R6/lib/X11/fonts/TTF/VeraMono.ttf";
    036 $fontpath = "/usr/share/fonts/truetype/ttf-bitstream-vera/VeraMono.ttf"
    037   if ! -f $fontpath;
    038 
    039 my $font = SDL::TTFont->new(
    040   -name => $fontpath,
    041   -size=>15,
    042   -bg => $bg_color, -fg => $fg_color);
    043 
    044 my $lrect = SDL::Rect->new(
    045   -width  => $logo->width, 
    046   -height => $logo->height,
    047   -x => 0, -y => 0);
    048 $logo->blit(0, $app, $lrect);
    049 $app->update($lrect);
    050 
    051 my $event = new SDL::Event->new();
    052 $event->set_key_repeat(200, 10);
    053 
    054 my $record_time;
    055 my $gtime;
    056 
    057   # Next game ...
    058 GAME: while(1) {
    059 
    060   my $obj = SDLMove->new(
    061     app      => $app,
    062     bg_color => $bg_color,
    063     x  => $WIDTH/2 - $diver->width()/2, 
    064     y  => $logo->height,
    065     image => $diver,  # Start with diver
    066   );
    067 
    068   my $v     = 0;
    069   my $vterm = $VTERM_FREE;
    070   my $start = $app->ticks();
    071   
    072   while(1) {   # Frame loop
    073     my $synchro_ticks = $app->ticks;
    074   
    075       # Accelerate
    076     $v += ($G - deceleration($v, $vterm))
    077           / $FRAMES_PSEC;
    078       # Move player downwards 
    079     $obj->move("s", $v/$FRAMES_PSEC);
    080     
    081     if($obj->hit_bottom()) {
    082       if($v <= $MAX_LAND) { # soft enough?
    083         if(! defined $record_time or 
    084            $gtime < $record_time) {
    085             $record_time = $gtime;
    086         }
    087         nput($app, 0, $lrect->height + 20, 
    088              $record_time);
    089       } else {
    090           $obj->wipe();
    091           $obj->image($diver);
    092           $obj->move("s", # indicate crash
    093            $para->height - $diver->height);
    094       }
    095       sleep 5;
    096       $obj->wipe();
    097       next GAME;
    098     }
    099       # Process all queued events
    100     while ($event->poll != 0) {
    101       my $type = $event->type();
    102       exit if $type == SDL::Event::SDL_QUIT();
    103    
    104       if($type == SDL::Event::SDL_KEYDOWN()) {
    105         my $keypressed = $event->key_name;
    106   
    107         if($keypressed eq "left") {
    108             $obj->move("w", 0.1);
    109         } elsif($keypressed eq "right") {
    110             $obj->move("e", 0.1);
    111         } elsif($keypressed eq "up") {
    112           # deploy parachute
    113           $vterm = $VTERM_PARA;
    114           $obj->image($para);
    115         } elsif($keypressed eq "r") {
    116           $obj->wipe();
    117           next GAME;
    118         } elsif($keypressed eq "q") {
    119           exit 0; # quit
    120         }
    121       }
    122     }
    123     $gtime = ($app->ticks - $start)/1000.0;
    124 
    125     nput($app, 0, $lrect->height, $gtime);
    126     nput($app, 110, $lrect->height, $v);
    127   
    128     my $wait = $SPEED_MS - 
    129            ($app->ticks - $synchro_ticks);
    130     select undef, undef, 
    131           undef, $wait/1000.0 if $wait > 0;
    132   }
    133 }
    134 
    135 ###########################################
    136 sub deceleration {
    137 ###########################################
    138     my($v, $vterm) = @_;
    139 
    140     my $d = $v/$vterm*9.81;
    141 
    142     $d = 0 if $d < 0;
    143     $d = 2*$G if $d > 2*$G;
    144 
    145     return $d;
    146 }
    147 
    148 ###########################################
    149 sub nput {
    150 ###########################################
    151   my($app, $x, $y, $number) = @_;
    152 
    153   my $rect = SDL::Rect->new(
    154     "-height" => $font->height,
    155     "-width"  => $font->width($number),
    156     "-x"      => $x,
    157     "-y"      => $y);
    158 
    159   $app->fill($rect, $bg_color);
    160   my $string = sprintf "%-5.2f", $number;
    161   $font->print($app, $x, $y, $string);
    162   $app->sync();
    163 }
    164 

Hauptschleife des Lebens

Zeile 7 setzt die Geschwindigkeit der Animation auf 20 Millisekunden pro Frame. Das entspricht 50 Frames pro Sekunde, was die Variable $FRAMES_PSEC in Zeile 8 widerspiegelt. Die Endlosschleife ab Zeile 67 bringt die Frames auf den Bildschirm. Um die Taktrate genau zu halten, fragt das Skript am Anfang der Schleife mit $app->ticks() die seit Programmbeginn verstrichenen Anzahl von Millisekunden ab und speichert sie in der Variablen $synchro_ticks ab.

Eine weitere Messprobe am Ende der Schleife bestimmt dann die von Schleifenanfang bis Schleifenende verstrichenen Millisekunden. Ist diese kleiner als 20, muss das Skript solange pausieren, bis die anberaumten 20 Millisekunden pro Frame abgelaufen sind. Mit select() kann man Pausen mit Millisekunden-Auflösung einlegen, so läuft die Animation ruckelfrei. Ist die Differenz zwischen anberaumter zu verstrichener Zeit gar negativ, hat die Berechnung innerhalb der Schleife länger als 20 ms gedauert und das Skript muss umgeschrieben oder die Framerate gesenkt werden.

Auch während skydive emsig die Hauptschleife durchläuft, kommen Events wie Tastendrücke, Mausbewegungen oder Klicks auf das Schließ-Icon des Applikationsfensters bei der Applikation an. Das in Zeile 46 definierte Objekt der Klasse SDL::Event stellt die Methode poll() bereit, die anzeigt, ob überhaupt Events vorliegen. Den Typ des Events liefert event_type(). Der Eventtyp SDL_QUIT liegt zum Beispiel an, wenn der User das Applikationsfenster mit der Maus schließt. In diesem Fall bricht das Skript in Zeile 97 einfach mit exit ab.

Events vom Typ SDL_KEYDOWN signalisieren, dass der User eine Taste des Keyboards gedrückt hat. Die Methode key_name() in Zeile 100 findet heraus, welche Taste es war. Praktischerweise übersetzt SDL die Tastaturcodes gleich in handliche Strings und so kommt bei gedrückter rechter Cursortaste der String "right" an und "q", wenn jemand auf ``q'' gedrückt hat. Die Methode set_key_repeat() hilft, länger gedrückte Tasten als wiederholende Eingaben zu verarbeiten. Sie nimmt zwei Parameter entgegen. Der erste bestimmt, nach wievielen Millisekunden SDL eine konstant gedrückte Taste auf Dauerfeuer stellt. Der zweite Parameter bestimmt den zeitlichen Abstand der dann ausgelösten Salven, ebenfalls in Millisekunden. Für das vorgestellte Spiel ist das zwar irrelevant, aber wer den Springer mit den nach links und rechts zeigenden Cursortasten manövrieren will, wird diese Einstellung zu schätzen wissen.

Drückt der User auf die Cursortaste mit dem Pfeil nach oben, möchte er den Fallschirm auslösen und die elsif-Bedingung ab Zeile 107 leitet zwei Aktionen ein: Die Endgeschwindigkeit $VTERM wird von $VTERM_FREE auf $VTERM_PARA gesetzt und die Methode image() des Spieler-Objektes $obj setzt das Spieler-Icon auf $para, also das Fallschirm-Icon. Weitere Tastendrücke sind ``r'' für Restart (also einen Spielabbruch und -neugbeginn), sowie ``q'', um die Applikation abzubrechen. Für Erweiterungen sind auch noch die nach links und rechts zeigenden Cursortasten definiert, die das Spielerobjekt im Fallen nach links und rechts transportieren. Dies ist zwar in der vorliegenden Version nicht notwendig, könnte aber bei Erweiterungen nützlich sein.

Die Variable $gtime gibt die Spielzeit eines gerade beendeten Durchlaufs an und $record_time wird aufgefrischt, falls eine neue Rekordzeit erzielt wurde und die Landung nicht allzu hart verlaufen ist.

Die Applikation selbst liegt im Objekt $app vom Typ SDL::App, einer von SDL::Surface abgeleiteten Klasse. Zeichenvorgänge im Applikationsfenster oder ein Auffrischen der veränderten Rechtecke laufen grundsätzlich über $app ab.

Damit der ganze Zirkus um das Verschieben des Spieler-Icons einfacher vonstatten geht, definiert das Modul SDLMove.pm in Listing 2 einige Hilfsfunktionen. Die Methode image() setzt das Spieler-Icon auf das spezifizierte Objekt vom Typ SDL::Surface.

Da SDLMove die Dimensionen der Applikation kennt, kann es mit hit_bottom() mitteilen, ob die Spielfigur das untere Ende des Spielfensters erreicht hat und die aktuelle Runde abgeschlossen ist. Die Methode wipe() wischt die Spielerfigur mit einem Schlag vom Feld, zum Beispiel um einen gescheiterten Fallschirmspringer am Boden zurück in einen freifallenden zu verwandeln, um anzuzeigen, dass der aktuelle Versuch wegen zu hoher Aufprallgeschwindigkeit gescheitert ist. Die Methode move() bewegt die Spielfigur um die angegebene Anzahl von Pixeln in eine per Himmelsrichtung (n=North s=South w=West e=East) angegebene Richtung. Die Pixels dürfen ruhig auch Bruchteile enthalten, die dann zwar keine Auswirkung auf die aktuelle Bewegung haben, aber für zukünftige Aktionen aufkumuliert werden. Bevor die Spielfigur weiterwandert, löscht SDLMove die alte Repräsentation, damit eine ordentliche Bewegung auf dem Bildschirm entsteht.

Konfiguration

In $VTERM_FREE liegt mit 50m/s die Höchstgeschwindigkeit im freien Fall, $VTERM_PARA gibt mit 3m/s die Sinkrate des Fallschirms an, auf die sich diese nach einiger Zeit des Segelns einpendelt. In der Sektion nach Zeile 7 lassen sich diese Werte umändern und auch andere Parameter wie die Höhe und Breite des Animationsfensters verändern.

Schriften der Spielwelt

Um Texte auf der Spieloberfläche darzustellen, jongliert das Modul SDL::TTFont mit True Type Fonts. Es baut Zeichenstrings zu gerenderten Textketten zusammen und hilft, diese in die Spieloberfläche hineinzuschreiben. Der in Zeile 33 aufgerufene Konstruktor lädt den fixed-Font VeraMono, der im Unterverzeichnis TTF des Fontverzeichnisses meines X-Servers liegt. Die Optionen -fg und -bg stellen die Textfarbe Rot auf schwarzem Hintergrund ein. Die Methode print() übernimmt das Rendern und die Anzeige auf der Spieloberfläche an einem bestimmten Punkt an den Koordinaten ($x, $y). Ähnlich wie bei den vorher besprochenen Rechtecken frischt SDL die Anzeige nicht direkt nach einem print-Befehl auf, sondern wartet auf ein nachfolgendes sync() auf das $app-Objekt.

Überschreibt ein weiterer Aufruf allerdings die gleiche Stelle mit neuem Text, bleibt die ursprüngliche Anzeige bestehen und das Nummerfeld sieht nach einigen Sekunden aus wie das Cover ``Ghost in the Machine'' von der 70er-Jahre-Band The Police. Die ab Zeile 144 definierte Funktion nput ermittelt deswegen zunächst die Ausmaße des gerenderten Textstrings und definiert ein Recheck darauf. Dieses füllt sie dann mit schwarzer Farbe, damit die Funktion print anschließend gedankenlos drüberschreiben kann.

Listing 2: SDLMove.pm

    01 package SDLMove;
    02 use strict;
    03 use warnings;
    04 use SDL;
    05 use SDL::App;
    06 
    07 ###########################################
    08 sub new {
    09 ###########################################
    10   my($class, %options) = @_;
    11 
    12   my $self = { %options };
    13   bless $self, $class;
    14 
    15   $self->image($self->{image});
    16   return $self;
    17 }
    18 
    19 ###########################################
    20 sub image {
    21 ###########################################
    22   my($self, $image) = @_;
    23   
    24   $self->{image} = $image;
    25   $self->{drect} = SDL::Rect->new(
    26     -width  => $image->width, 
    27     -height => $image->height,
    28     -x      => $self->{x},
    29     -y      => $self->{y},
    30   );
    31 }
    32 
    33 ###########################################
    34 sub move {
    35 ###########################################
    36   my($self, $direction, $pixels) = @_;
    37 
    38   my $rect = $self->{drect};
    39   my $app  = $self->{app};
    40 
    41   if($direction eq "w") {      # left
    42    $self->{x} -= $pixels if $self->{x} > 0;
    43 
    44   } elsif($direction eq "e") { # right
    45     $self->{x} += $pixels if $self->{x} < 
    46         $app->width - $rect->width;
    47 
    48   } elsif($direction eq "n") { # up
    49    $self->{y} -= $pixels if $self->{y} > 0;
    50 
    51   } elsif($direction eq "s") { # down
    52     $self->{y} += $pixels if $self->{y} < 
    53         $app->height - $rect->height;
    54   }
    55 
    56   $self->{old_rect} = SDL::Rect->new(
    57     -height => $rect->height,
    58     -width  => $rect->width,
    59     -x      => $rect->x,
    60     -y      => $rect->y,
    61   );
    62 
    63   $rect->x( $self->{x} );
    64   $rect->y( $self->{y} );
    65   $app->fill($self->{old_rect}, 
    66              $self->{bg_color});
    67   
    68   $self->{image}->blit(0, $self->{app}, 
    69                        $rect);
    70   $app->update($self->{old_rect}, $rect);
    71 }
    72 
    73 ###########################################
    74 sub wipe {
    75 ###########################################
    76   my($self) = @_;
    77 
    78   $self->{app}->fill($self->{drect}, 
    79              $self->{bg_color});
    80   $self->{app}->update($self->{drect});
    81 }
    82 
    83 ###########################################
    84 sub hit_bottom {
    85 ###########################################
    86   my($self) = @_;
    87 
    88   return $self->{y} >
    89     $self->{app}->height - 
    90     $self->{drect}->height;
    91 }
    92 
    93 1;

Installation

SDL ist in gängigen Linux-Distributionen meist schon vorhanden, andernfalls sind die RPMs SDL, SDL-devel, SDL_ttf, SDL_ttf-devel und SDL_mixer zu installieren. Eine CPAN-Shell erledigt anschließend mit install SDL_perl die Installation des Perl-Wrappers mit allen heute verwendeten SDL-Modulen. Es ist darauf zu achten, SDL_perl erst nach den obengenannten Bibliotheken zu installieren, da letzeres sonst Features with True Type Fonts einfach nicht unterstützt. Die drei verwendeten Icons logo.png, dive.png und para.png sind mit den Listings auf dem Download-Server des Linux-Magazins verfügbar. Das Skript sucht die Icons wenn es hochfährt im aktuellen Verzeichnis und beschwert sich, falls sie fehlen.

Erweiterungen

Das Spiel lässt sich mit ein paar Perl-Zeilen leicht erweitern. Wer sich von Experten Ideen holen möchte, ist gut damit beraten, den Sourcecode von Frozen Bubble ([4]) zu studieren. Hierbei handelt es sich um ein mit SDL_Perl geschriebenes Spiel mit professioneller Animation. Einige Ideen für den heute vorgestellten Prototyp: Es wäre denkbar, den Springer realitätsnah aus einem sich mit horizontaler Geschwindigkeit bewegenden Flugzeug abspringen zu lassen. In diesem Fall bewegt er sich zunächst mit konstanter Geschwindigkeit seitwärts, wird aber durch die Luftreibung abgebremst. Ziel des Spiels wäre es dann, nicht nur sanft zu landen, sondern auch eine Markierung am Boden zu treffen oder Wasser oder Strommasten auszuweichen. Nach dem Öffnen des Fallschirms kann der Springer langsam hin- und hersegeln, um Korrekturen vorzunehmen. Aber natürlich nur, solange der zufällig aufbrausende Seitenwind ihm nicht einen Strich durch die Rechnung macht! Und mit SDL_Mixer generierten Soundeffekten wird ein richtiges Spiel daraus.

Anmerkung: Bitte zusätzlich zu den Listings auch noch die drei Icons logo.png, dive.png und para.png zum Download bereitstellen!

Infos

[1]
Listings und Icons zu diesem Artikel: ftp://www.linux-magazin.de/pub/listings/magazin/2007/10/Perl

[2]
Youtube-Video, in dem der Perlmeister vom Himmel springt: http://youtube.com/watch?v=aRxvsSs0sz4

[3]
André LaMothe, Tricks of the Windows Game Programming Gurus, Second Edition

[4]
Frozen Bubble, http://www.frozen-bubble.org

[5]
``Free Fall - Falling Math'', http://www.greenharbor.com/fffolder/math.html

[6]
http://en.wikipedia.org/wiki/Drag_(physics)

[7]
http://arstechnica.com/guides/tweaks/games-perl.ars

Abbildung 8:

Abbildung 9:

Michael Schilli

arbeitet als Software-Engineer bei Yahoo! in Sunnyvale, Kalifornien. Er hat "Goto Perl 5" (deutsch) und "Perl Power" (englisch) für Addison-Wesley geschrieben und ist unter mschilli@perlmeister.com zu erreichen. Seine Homepage: http://perlmeister.com.