Tutorials

Andorra 2D Tutorials
Teil 3 - Die Spriteengine


Einleitung
Einleitung der Einleitung
Endlich ist es soweit: Das Tutorial (auf jeden Fall das erste), wie man die Spriteengine verwendet ist fertig. In diesem Tutorial bauen wir die Demo aus dem letzten Tutorial um.

Sprites - Was sind das?
Sprite kommt aus dem Englischen und bedeutet eigentlich soviel wie "Elfe" oder "Kobold". In der Computergrafik wird damit jedoch ein Objekt bezeichnet, welches sich (einfach ausgedrückt) unabhängig von anderen Objekten bewegen kann. (Nähere Informationen gibt es im Wikipediaartikel)
Bei Andorra 2D handelt es sich bei einem Sprite um ein abstraktes Objekt, welches sich zeichenen, bewegen und kollidieren kann. Diese Handlungen werden von einem übergeordnetem Objekt, der SpriteEngine (bei der es sich eigentlich selbst um ein Sprite Handelt) kontrolliert.
Die Sprites müssen auch nicht Zwangsläufig ein bewegtes Objekt in der Spielwelt darstellen, die Sprites werden auch durchaus dazu verwendet um z.B. den Hintergrund zu zeichnen.
Und nicht nur so viel - Man kann ganze Spiele basierend auf der SpriteEngine schreiben.

Erzeugen der SpriteEngine
Bisher haben wir 4 Objekte der Andorra Bibliothek benutzt:

  • TAdDraw - Das Andorra 2D "Herzstück"
  • TAdPerformanceCounter - Rechnet die FPS und das "TimeGap" aus
  • TAdImageList - Verwaltet Bilder in einer Liste
  • TAdImage - Kümmert sich um das Zeichnen und Verwalten von Bildern


In diesem Tutorial werden Sie ein nun auch das Objekt "TSpriteEngine" kennen lernen. Dazu müssen Sie zunächst die Unit "AdSprites" einbinden.
Deklarieren Sie also eine neue Variable "AdSpriteEngine" des Typs "TSpriteEngine".
Mit den folgenden zwei Zeilen, die Sie am Besten nach der Erzeugung der ImageList einfügen, wird die SpriteEngine erzeugt:

Delphi-Code:
AdSpriteEngine := TSpriteEngine.Create(AdDraw);

Natürlich sollten Sie nicht vergessen, das Objekt am Ende wieder frei zu geben.

Die Familie der Sprites
Achtung: Sollten Sie sich nicht mit objektorientierter Programmierung auskennen, dann empfehle ich Ihnen erstmal sich damit ein wenig außeinander zu setzen (z.B. hier: http://www.dsdt.info/tutorials/crashkurs/?page=8), sonst kann es passieren, dass Sie einige Dinge nicht richtig verstehen.

Wie schon oben geschrieben muss ein Sprite nicht unbedingt ein Bild zeichnen. Es kann auch (die Spriteengine selbst ist das beste Beispiel dafür) etwas ganz abstraktes verwalten. Daher stehen verschiedene Spriteklassen zur Verfügung. Sie alle haben jedoch eins gemeinsam: Sie sind von TSprite, der Basisspriteklasse, abgeleitet.

  • TSprite
    • TImageSprite
      • TImageSpriteEx

    • TBackgroundSprite
    • TLightSprite
    • TParticleSprite


Bei der Basisklasse handelt es sich um ein "nacktes" Sprite, das noch nichts kann, außer sich in die Verwaltungsstruktur der Spriteengine einzufügen. TImageSprite kann schon das, was wir von einem Sprite im Allgemeinen erwarten: Es kümmert sich außerdem um das Zeichnen und Animieren eines Bildes. TImageSpriteEx kann außerdem Transparent und Gedreht gezeichnet werden. TBackgroundSprite - wie der Name schon sagt - kümmert sich um das Zeichnen eines gekachelten Hintergrundes. Zu TLight und TParticleSprite kommen wir später ;-).

Lauf, Sprite, Lauf
Nun möchten wir unseren Kerl aus dem letzten Tutorial als Sprite über den Bildschirm rennen lassen. Kramen Sie also am Besten das Ergebnis des letzten Tutorials noch einmal hervor.
Wir ergänzen die Initialisierungsroutine nun am das Erzeugen der SpriteEngine und das Erzeugen eines TImageSprites mit dem namen "Figur":

Delphi-Code:
.
.
AdImageList.Restore;

//SpriteEngine erzeugen
AdSpriteEngine := TSpriteEngine.Create(nil);
AdSpriteEngine.Surface := AdDraw1;

//Eine Instanz von TImageSprite erstellen
Figur := TImageSprite.Create(AdSpriteEngine);
with Figur do
begin
  //Hier gehts gleich weiter
end;

Bevor Sie jetzt - ordentlich wie Sie sind - "Figur" im Destruktor freigeben möchten, lassen Sie es lieber gleich. Die Spriteengine kümmert sich ganz automatisch darum, dass alle Sprites freigegeben werden. Sollten Sie dennoch (dazu kommen wir später noch) ein Sprite vorzeitig freigeben wollen, so tun Sie dies über die Methode "Dead". Rufen Sie dennoch "Free" auf, so werden Sie mit Fehlermeldungen überhäuft.

Nun haben wir ein "nacktes" TImageSprite erstellt - ohne Bild und ohne Animation. Das ändert sich im nächsten Schritt:

Delphi-Code:
//Hier geht es jetzt weiter

//Dem ImageSprite ein Bild zuweisen
Image := AdImageList.Find('figur')
//Die Animation aktiv schalten
AnimActive := true
//Die Animation soll sich ständig wiederholen
AnimLoop := true
//Die Animation soll mit 15 FPS abgespielt werden
AnimSpeed := 15;

Von unseren vorher definierten Variablen (Pattern,StartPt,EndPt,Y,X,XSpeed) brauchen wir eigentlich nur noch XSpeed, da alle anderen Variablen schon ein Teil von TImageSprite sind.

Die Renderprozedur schreiben wir wiefolgt um:

Delphi-Code:
if AdDraw.CanDraw then
begin
  AdPerCounter.Calculate
   
  Figur.X := Figur.X + XSpeed*AdPerCounter.TimeGap/1000;
  if ((Figur.X > ClientWidth) and (XSpeed > 0)) or
     ((Figur.X < -96) and (XSpeed < 0)) then
  begin
    SetLine;
  end;


  AdDraw.ClearSurface(clBlack);
  AdDraw.BeginScene;

  //Die Bewegunsroutine aller Sprites in der Engine aufrufen
  AdSpriteEngine.Move(AdPerCounter.TimeGap / 1000);
  //Die Zeichenroutine aller Sprites aufrufen
  AdSpriteEngine.Draw;
  //Alle als mit "Dead" als "tot" makierten Sprites löschen
  AdSpriteEngine.Dead;

  AdDraw1.EndScene;
  AdDraw1.Flip;

  Done := false;
end;

Folgende Dinge haben sich verändert:

  • Der Teil, welcher für die Animationen zuständig war, ist herausgefallen.
  • Anstatt den Wert einer Variable "X" direkt zu verändern wird hier die X-Position von "Figur" verändert
  • Die Zeichenroutine ist herausgeflogen. Stattdessen kümmert sich die SpriteEngine nun um die (bewegte) Darstellung.

Nun müssen wir nur noch die "SetLine" Prozedur ein wenig überarbeiten:

Delphi-Code:
procedure TForm1.SetLine;
begin
  XSpeed := -XSpeed;
  if XSpeed > 0 then
  begin
    Figur.AnimStart := 0;
    Figur.AnimStop := 7;
    Figur.X := -96;
  end
  else
  begin
    Figur.AnimStart := 8;
    Figur.AnimStop := 15;
    Figur.X := ClientWidth+96;
  end;
  Figur.Y := Random(ClientHeight-96);
end;

Die Änderungen, welche hier gemacht wurden, sollten eigentlich klar sein.

Und nun rennt unsere Figur - munter wie eh un jeh - über den Bildschirm.

Unsere eigene Spriteklasse
Klasse!
Wenn wir uns diese Lösung, welche wir bis hier hin erarbeitet haben so anschauen, so müssen wir leider zugeben, dass diese mehr oder weniger "suboptimal" ist. Wäre es nicht schöner eine eigene Spriteklasse zu haben, die sich automatisch um alle Bewegungen kümmert? Dann wäre es auch kein Problem mehr, mehrere Figuren unabhängig von einander über den Bildschirm zu bewegen.

Dies ist natürlich ohne weitere möglich - denn dafür ist die Spriteengine schließlich gedacht.
Also deklarieren wir erstmal eine neue Klasse, die wir von TImageSprite oder von TImageSpriteEx (je nach Geschmack) ableiten. Die Deklaration sollte am besten oberhalb von der Deklaration von TForm1 geschehen (oder in einer eigenen Unit).

Delphi-Code:
type
  TFigur = class(TImageSprite)
    private
    protected
      procedure DoMove(TimeGap:double);override;
    public
      XSpeed:integer;
      constructor Create(AParent:TSprite);override;
      procedure SetLine;
  end;

DoMove, DoDraw...
Schauen wir uns einmal an, was wir bis hier Deklariert haben:
Im "public"-Bereich befindet sich die Variable "XSpeed" - diese ist equivalent zu der "XSpeed"-Variable, die wir vorher global deklariert hatten (übrigens sollten Sie diese globale Deklaration löschen...).
Außerdem scheint nun auch die Prozedur "SetLine" in den Aufgabenbereich des Sprites gewandert zu sein. Dann befindet sich noch ein überschriebener Konstruktor im Repertoire von TFigur.
Nun haben wir so ziemlich alle Dinge, die zur Figur gehören dort Ausgelagert. Doch wohin soll nun der Code, der die Bewegungen steuert und bisher in der Renderschleife stand? Dafür haben wir die ererbte Methode "DoMove" überschrieben. Andorra 2D bietet eine ganze Reihe von "Do"-Methoden, mit welchen Verhalten und Aussehen der Sprites angepasst werden kann:

Delphi-Code:
//Hier kommt all der Code rein, der mit Bewegungen zu tun hat
procedure DoMove(TimeGap:double);
//Hier landet der Code, welcher ausgeführt wird, wenn eine Kollision stattfindet (später ;-))
procedure DoCollision(Sprite:TSprite; var Done:boolean);
//Hier landet der Code, welcher das Sprite zeichnet (noch später...)
procedure DoDraw;

Nun haben wir also das Grundgerüst einer neuen Klasse mit dem Namen "TFigur" erstellt. Wir können also getrost die Varible "Figur" vom Typ TFigur sein lassen.
Es fehlt nur noch die Implementierung:

Delphi-Code:
{ TFigur }

constructor TFigur.Create(AParent: TSprite);
begin
  inherited;
  //Am Punkt (0;0) starten.
  X := 0;
  Y := 0;
 
  //XSpeed auf -150 setzten
  XSpeed := -150;
end;

procedure TFigur.DoMove(TimeGap: double);
begin
  inherited;

  //Der Teil aus der Renderprozedur, der sich um die Bewegungen kümmert
  X := X + XSpeed*TimeGap;
  if ((X > Engine.SurfaceRect.Right) and (XSpeed > 0)) or
     ((X < -96) and (XSpeed < 0)) then
  begin
    SetLine;
  end;
end;

procedure TFigur.SetLine;
begin
  //Bewegt die Figur in eine neue Ebene
  XSpeed := -XSpeed;
  if XSpeed > 0 then
  begin
    AnimStart := 0;
    AnimStop := 7;
    X := -96;
  end
  else
  begin
    AnimStart := 8;
    AnimStop := 15;
    X := Engine.SurfaceRect.Right+96;
  end;
  Y := Random(Engine.SurfaceRect.Right-96);
end;

Achtung: Damit der Code ordnungsgemäß funktioniert ist es zwingend notwendig, dass die AdSprites.pas der Version 0.1.5 durch die neue Version aus dem CVS-Repository ausgetauscht wird: http://andorra.cvs.sourceforge.net/*checkout*/andorra/andorra/src/AdSprites.pas

Wenn Sie nun "Figur" als "TFigur" deklarieren und in FormCreate anstatt

Delphi-Code:
Figur := TImageSprite.Create(AdSpriteEngine);
.
.
Delphi-Code:
Figur := TFigur.Create(AdSpriteEngine);
with Figur do
begin
  Image := AdImageList1.Find('figur');
  AnimActive := true;
  AnimLoop := true;
  AnimSpeed := 15;
  XSpeed := -150;
  SetLine;
end;

schreiben, dann sollte das Ganze eigentlich funktionieren.

Marathon
Nun ist es natürlich ein bisschen wenig, nur eine Figur über den Bildschirm laufen zu lassen. Löschen Sie am Besten die Deklaration von "Figur" uns schreiben sie einfach:

Delphi-Code:
for i := 0 to 5 do
begin
  with TFigur.Create(AdSpriteEngine) do
  begin
    Image := AdImageList1.Find('figur');
    AnimActive := true;
    AnimLoop := true;
    AnimSpeed := 15;
    XSpeed := -(random(100)+50);
    SetLine;
  end;
end;

Schon rennen 6 Männchen mit unterschiedlicher Geschwindigkeit über den Bildschirm.

Den gesammten Sourcecode gibt es hier.

Aufgaben
Bis hier her haben Sie es schon einmal Geschafft. Ich habe jetzt mal noch ein paar Aufgaben für Sie:

  • Es kommt vor, dass einige Figuren andere überdecken, obwohl sie sich eigentlich hinter der anderen Figur befinden sollten. Beheben Sie diesen "Fehler". Tipp: Die Klasse TSprite bietet eine Variable "Z" mit der Sie bestimmen können, wie weit einzelne Sprites im Hintergrund liegen.
  • Die Männchen sehen alle gleich aus. Bauen Sie noch ein paar Grafiken für die Figuren ein.


Wenn Sie Fragen dazu haben, können Sie mir ruhig eine E-Mail schreiben.

Fazit
Nun kennen Sie sich schon ein wenig mit der Spriteengine aus.
Im nächsten Tutorial erwartet Sie dann: Wir werden eine alte DelphiX Demo nachbauen - dort bewegt man sich, als Kugel, durch eine karierte, mit Kugeln bevölkerte, Welt. Rammt man eine andere Kugel, so verschwindet diese. Natürlich "verbessern" wir die Demo anschließend ein wenig.
Ich möchte hier nocheinmal darauf hinweisen, das das Spriteengine-System nicht von mir in dieser Form entwickelt wurde - es ist ein Nachbau des Spriteengine-Systems von DelphiX, wobei es natürlich an einigen Stellen verbessert wurde.

Copyright und Lizenz
(c) by Andreas Stöckel März 2007

Revision 2: Oktober 2007

Der Inhalt dieses Tutorials steht unter der GNU Lizenz für freie Dokumentation


This page was generated with the help of the following PHP-Scripts: GeSHi a free PHP Syntax highlighter and StringParser_BBCode a free BBCode Parser.