workshop 20

Top  Zurück  Weiter

Workshop 20: Sprites. Animierte Sprites.

Warum brauchen wir diese Sprites eigentlich? Wollen wir ein praktisches Beispiel besprechen?

w20_11

Das ist ein Screenschot aus einem Level, das Jonas Noell während eines Schulpraktikums bei Conitec erstellt hat und das, wie ich meine, recht gut aussieht. Sie finden es auf Gamestudios Download-Seite. Und nun schauen Sie sich das Bild unten genau an:

w20_12

Nun, es ist derselbe Shot, allerdings ist das gewisse Etwas daraus verschwunden! Der Zaun ist weg, der Lichthof um die Laternen verschwunden und selbst die Inschriften am linken Bildrand sind jetzt nicht mehr da! Und jawohl, Sie haben recht, all diese Level-Elemente waren Sprites. Sprites lassen sich mit einem beliebigen Paintprogramm erstellen. Ich benutze Paint Shop Pro weil es günstig ist und seine Sache gut macht. Aber natürlich können Sie auch das teurere Photoshop oder im Gegenteil sogar das kostenlose Gimp nehmen. Malen Sie einfach Ihre 2D-Bitmap, speichern Sie sie als .pcx-, .bmp-, .tga- oder .dds-Datei und fügen Sie sie dann in Ihr Level ein.

Den Künstler in mir drängt es nach Taten. Also fahren Sie Ihr Malprogramm hoch, kreieren wir doch gleich jetzt ein Sprite:

- Beginnen Sie mit einem Bild von der Größe 256 x 512 Pixel und verwenden Sie einen roten Hintergrund dafür:

w20_13

- Zeichnen Sie 2 schwarze Linien (RGB = 000), genauso wie in der Abbildung unten:

w20_14

- Nun füllen sie den Innenraum mit einem metallischen Verlauf:

w20_15

- Füllen Sie dann die roten Teile mit Schwarz(RGB = 000):

w20_16

Jetzt werden wir noch einen Alphakanal hinzufügen. Das ist ein spezieller Layer, der die Transparenz unseres Sprites steuert. Selektieren Sie dazu das gesamte Bild, gehen Sie dann Selections -> Save to alpha channel und dann speichern Sie Ihr Sprite als 32 bit-TGA-Datei.

Ich füge diesen Sprite anstelle eines der Original-Lichthof-Sprites ins Level ein. Wenn es so aussieht...

w20_17t

... haben Sie die Bitmap vermutlich im PCX-, BMP- oder in einem 24- anstatt 32-bit-TGA-Format abgespeichert - dadurch verliert sie ihre Transparenz! Ausschließlich 32-bit-TGA-Bilder haben diesen magischen Alphakanal, der uns durch den Lichtstrahl schauen läßt. Noch ein letzter Schliff: ich will in WED den Flag BRIGHT prüfen (Ihr WED-Dialog könnte ein wenig anders aussehen):

w20_19

w20_18

Der BRIGHT-Flag sorgt dafür, dass die Entity ihre Helligkeit auf den Hintergrund aufaddiert und daher aussieht, als wäre sie von einer Lichtquelle erleuchtet und das ist genau das was wir hier brauchen. Sie können nicht recht glauben, dass ein einziger Flag unseren Sprite so sehr verändert? Vielleicht sollte ich meinen Programmierjob an den Nagel hängen und meinen Lebensunterhalt stattdessen mit dem Erstellen von Sprites verdienen! :)

Nicht zu vergessen, Sie können auch ausgerichtete Sprites und Plakatwände erstellen. Lesen Sie mehr darüber im 9. Workshop.

Sprite-Animation

Nun ist es an der Zeit, uns mit einigen animierten Sprites zu vergnügen. Starten Sie Lite-C, öffnen und starten Sie script20.c:

w20_01

Dieser verschwommene Text "Acknex rules!" ist ein animierter Sprite aus 29 Frames. Wenn Sie wissen wollen wie er aussieht, werfen Sie einen Blick auf die Datei logo+29.pcx, die Sie im Ordner workshop20 finden. Drücken wir nun auf die Taste [E]:

w20_02

Wir haben eine Explosion ausgelöst! In Wahrheit haben wir die Animation für den Explosions-Sprite abgespielt. Wenn Sie eine weitere Explosion auslösen wollen, drücken Sie nochmals auf [E]. Es gibt zwei Sorten von Sprite-Animationen: Animations-Schleifen (looped) und einmal ablaufende (one-shot) Animationen.

Eine Animation läuft dann in einer Schleife, wenn ihre Frames öfter als einmal abgespielt werden. Sie werden Animationsschleifen dann brauchen, wenn Sie einen Feuereffekt haben wollen oder für die Schilder, die über den Läden in Ihrer Stadt hängen; für einen Monitor, der alle möglichen seltsame oder fremdartigen Symbole in einer Endlosschleife darstellt usw. Eine einmal ablaufende Animation hingegen werden Sie für jeden Sprite brauchen, der seine Animationssprites nur einmal abspielt und dann aufhört: eine Explosion, einen Teleporteffekt etc. Natürlich können Sie ein- und dieselbe one-shot-Animation verschiedene Male ablaufen lassen (z. B. für diverse Explosionen), eine Schleifen-Animation wird aber einfach nur ständig ihre Frames wiederholen - wieder und wieder ohne aufzuhören.

Ich weiß, ich weiß.. Ich habe über diese Animations-Frames gesprochen und nun fragen Sie sich was, zum Teufel das ist? Lassen Sie mich Ihnen zeigen, wie eine Bitmap für einen animierten Sprite aussieht:

w20_03

Diese Bitmap hat 5 Quadrate = 5 Animations-Frames (-Einzelbilder). Die Engine stellt diese Bitmaps eine nach der anderen dar und fängt dabei mit derjenigen am linken Bildschirmrand an, dann arbeitet sie sich bis zur letzten nach rechts durch. Ist die Animationsgeschwindigkeit hoch genug, wird der Player nicht bemerken, dass er einer Slide-Schow aus Bitmaps zuschaut, er denkt, er sähe eine "wirkliche" Animation.

Wie erstellen Sie nun einen animierten Sprite? Nun, Sie könnten sich einige Gratis-Sprites von Acknex Unlimited, der Webseite, die kostenlose Resourcen für für Gamestudio- und Lite-C-Entwickler anbietet, holen oder aber, Sie kreieren sie sich selbst in irgendeinem beliebigen Malprogramm. Entscheiden Sie sich für die einem Frame angemessene Größe und dann erstellen Sie einige Frames und fügen sie in einer einzigen Bitmap zusammen. Wenn Ihr Frame beispielsweise 128 x 32 Pixel groß ist und Sie 4 Animationsframes haben, muss die endgültige Bitmap 512 x 32 Pixel groß sein weil 512 = 128 x 4, haben Sie´s? Oh, und verzichten Sie auf diese gelben Rahmen, die in obigem Bild erscheinen; die habe ich gemalt, weil ich wollte, dass Sie sehen können wo ein Animationsframe anfängt und wo er aufhört.

Sie können so viele Frames wie sie wollen für Ihre animierten Sprites verwenden. Bitte beachten Sie, dass mehr Frames besser aussehende Effekte ergeben, allerdings auch mehr Speicherplatz verbrauchen. Ein .pcx-Sprite von der Größe 256 x 128 Pixel braucht 256 x 128 x 2 Bytes = 65,536 bytes = 64 Kb. Das bedeutet, dass Ihr Sprite, wenn er 16 Frames hat, etwa 64 Kb x 16 = 1 Mb Videospeicher belegt.

Denken Sie daran, dass Sie ein- und denselben Sprite oder dasselbe Modell problemlos wieder und wieder verwenden können (und sollten), denn der dafür gebrauchte Speicher wird nur einmal zugewiesen. Es ist also besser, 10 Mal denselben Sprite in Ihrem Level zu verwenden, als 2 verschiedene Sprites zu nehmen. Sie wollen eine richtig große Explosion? Setzen Sie 10 identische Explosions-Sprites dicht nebeneinander und lassen sie gleichzeitig ablaufen.

Nun müssen wir uns noch die Namenskonvention für Sprites merken. Sehen Sie, Lite-C kein Gedankenlesegerät und daher kann sie nicht wissen, was Sie mit einem bestimmten Sprite vorhaben! Es wäre also nett von Ihnen, Ihr etwas in der Art von "liebes Lite-C, ich hätte gerne, dass Sie, falls möglich, diesen Sprite als animierten Sprite behandeln! Würden Sie das für mich tun?" mitzuteilen. Wie Sie das machen? Indem Sie einen passenden Namen für Ihr animiertes Sprite wählen. Hier ein paar Beispiele:

fire+5.dds
lightning+32.tga
my_explosion+12.bmp

Sehen Sie was ich sehe? Sie können Ihrem Sprite so gut wie jeden Namen geben aber Sie müssen ein "+" und die Anzahl der Frames für diesen Sprite hinzufügen. Was passiert, wenn ich vergesse, meinem animierten Sprite einen korrekten Namen zu geben? Er wird wie ein regulärer, nicht animierter Sprite behandelt! Sehen Sie selbst:

w20_04

w20_05

Das erste Bild (in WED´s Seitenansicht) zeigt dieselbe Bitmap, die unter zwei verschiedenen Namen im Level plaziert wurde. Noch einmal: Ich verwende dieselbe Bitmap, aber die Engine hat herausgefunden, dass "muzzle+5.pcx" ein Sprite mit 5 Animationsframes ist und hat die Bitmap in 5 Teile (5 Frames) unterteilt. Das zweite Bild zeigt wie diese Sprites in der Engine aussehen. Sie müssen mir einfach glauben, dass sich "muzzle+5.pcx" wie ein animierter Sprite verhält während "muzzle.pcx " all seine Frames auf einmal darstellt. Weil ich es Ihnen etwas einfacher machen wollte, habe ich im zweiten Bild die violetten Rahmen hinzugefügt. Wenn sie nicht in der Bitmap enthalten sind, tauchen sie auch nicht auf.

Wie Sie wissen, können wir .pcx-, bmp-, tga- oder .dds-Bilder für unsere Sprites verwenden. In Ihrem Malprogramm sehen sie alle gleich aus, was also ist der Unterschied zwischen ihnen? Nun, die Engine benutzt das Bildformat zur Entscheidung ob die Bilder in niederer Qualität (16 Bits = 2 Bytes) oder in hoher Qualität (24 Bits = 3 Bytes oder 32 Bits = 4 Bytes) gespeichert werden. .PCX- und .BMP-Bilder werden in 16 Bit-Qualität gespeichert. Das spart wertvollen Videospeicher, ist für coole Transparent-Effekte wie Explosionen oder Spezialeffekte aber nicht so empfehlenswert - dafür nimmt man besser .tga-Dateien. DDS-Bilder sind eine besondere Spezies von Bitmaps und erfordern zum Speichern in einem Malprogramm normalerweise ein Plug In oder einen Konverter. Sie sind diese Mühe aber wert, denn Sie sind von besserer Qualität als PCX / BMP-Bilder und brauchen weniger Speicherplatz.

Nun möchte ich Ihnen den entsprechenden Teil der Datei script20 zeigen:

///////////////////////////////
#include 
#include 

///////////////////////////////

action acknex_loop()
{
	while (1)
	{
		my.frame += 0.8 * time_step;
		if (my.frame >= 30) my.frame -= 29;	
		wait (1);
	}
}

action explosion()
{
	my.ambient = 100;	
	my.flags |= BRIGHT;
	while(1) 
	{
		while (!key_e) wait (1);
		for (my.frame=0; my.frame<12; my.frame += 0.7 * time_step) wait (1);
	}
}

function main()
{
  vec_set(sky_color,vector(1,1,1));	// fast schwarzer Sky
  level_load(""); // lade leeres Level
  ent_create("logo+29.pcx", vector(300,100,0), acknex_loop);
  ent_create("explo+11.tga", vector(300,-100,0), explosion);
}

Ich habe hier 2 animierte Sprites benutzt: einen sich in einer Schleife wiederholenden "Acknex rules!"-.pcx-Sprite und eine .tga-Datei für eine einmal ablaufende one-shot-Explosion.

w20_07

w20_08

Wie wird ein Sprite in einer Schleife wiederholbar? Für den Programmierer gibt es keinerlei Beschränkungen, aber Sie sollten sicher gehen, dass das letzte und das erste Frame ähnlich (nicht identisch) sind. Ansonsten sähe Ihre Animationsschleife nicht allzu gut aus.

Werfen wir zuerst einen Blick auf die Aktion, die dem "Acknex rules"-Sprite zugewiesen ist:

action acknex_loop()
{
  
while (1)
  
{
    
my.frame += 0.8 * time_step;
    
if (my.frame >= 30) my.frame -= 29;
    
wait (1);
  
}
}

"Acknex rules!" spielt seine Animations-Frames in einem while (1)-Loop ab und das bedeutet, daß die Animation zu laufen anfängt sobald wir die Engine starten und nur dann aufhört, wenn wir die Engine herunterfahren. Wir erhöhen den Animationsframe indem wir dauernd "0.8 * time_step" auf den gegenwärtigen Frame addieren. Spielen Sie mit diesem 0.8, wenn Sie die Animationsgeschwindigkeit verändern wollen.

Selbst wenn my.frame auf über 100 anwächst, können wir nicht mehr als 29 Frames sehen, denn unsere Bitmap hat nun mal nicht mehr Frames, richtig? Und daher hat auch die nächste Zeile ihre Berechtigung: sie begrenzt my.frame in seinem Bereich auf 1... 29 und ermöglicht dem Sprite so, seine Frames wieder und wieder durchlaufen zu lassen.

Zeit, zur one-shot-Animation weiterzugehen:

action explosion
{
 
my.ambient = 100;
  
my.flags |= BRIGHT;
  
while(1)
  
{
    
while (!key_e) wait (1);
    
for (my.frame=1; my.frame<12; my.frame += 0.7 * time_step) wait (1);
  }
}

Der Explosions-Sprite hat einen "Ambient"-Wert von 100 und sein "BRIGHT"-Flag ist gesetzt. Diese Einstellung mussten wir verwenden, denn eine Explosion sollte schließlich hell leuchten. Diesmal habe ich eine .tga-Datei mit einem Alphakanal erstellt und es ist eine Tatsache, dass der Explosionseffekt damit besser aussieht.

Untersuchen wir einmal die folgende Codezeile:

while(1)
{
 
while (!key_e) wait (1);
...

Zwei ineinander verschachtelte while-Schleifen! Der erste Loop sagt der Engine einfach "wiederhole die folgenden Zeilen für ewig". Und der hineingeschachtelte while-Loop ist uns bereits aus dem letzten Workshop bekannt. Er weist die Engine an, solange zu warten, bis die Taste [E] gedrückt ist. Das heißt, die folgende Zeile wartet geduldig, bis wir auf [E] drücken:

for (my.frame=1; my.frame<12; my.frame += 0.7 * time_step) wait (1);

Das ist ein for-Loop, eine gängige Methode in Lite-C (und ebenso in C), diverse Anweisungen zu wiederholen bis ein bestimmtes Limit erreicht ist. Ein for-Loop ist folgendermaßen strukturiert:

for (initialisiere Zähler, vergleiche Zähler, erhöhe oder verringere Zähler)
{
  // tu etwas
}

Zunächst initialisiert der for-Loop den Zähler (setzt die Zähler-Variable auf ihren Eingangswert). Dann führt er einen Vergleich durch (vergleiche Zähler) und prüft, ob der Zähler ein bestimmtes Limit erreicht hat oder nicht. Ist diese Grenze noch nicht erreicht, wird der Code aus dem 'hineingeschachtelten' Loop (tu etwas) ausgeführt. Wie wir gelernt haben, können die geschweiften Klammern weggelassen werden sofern "tu etwas" aus einer einzigen Anweisung besteht. Zum Schluß hat der Zähler wunschgemäß hoch- oder heruntergezählt und die Schleife wird ständig wiederholt und zwar solange, bis der Vergleich "vergleiche Zähler" falsch ergibt (der Zähler sein Limit erreicht hat).

Wir haben für unsere Zähler-Variable my.frame benutzt und das ist (Sie haben es gewußt) die momentane Frame-Nummer der Animation für die my (respektive me)-Entity. Eingangs setzen wir my.frame auf 1, so daß der Sprite seinen ersten Frame darstellt. Dann prüfen wir, ob my.frame immer noch kleiner ist als 12, denn unser Sprite hat lediglich 11 Animation-Sprites.

Die Antwort ist positiv, wir warten ""wait (1);" und addieren dann 0.7 * time_step auf my.frame und sorgen so dafür, daß der Sprite seine Animations-Frames durchläuft. Die Dinge wiederholen sich solange bis my.frame < 12 falsch wird, was soviel bedeutet, daß my.frame größer oder gleich 12 ist. Die Animation wird nur einmal abgespielt und hört dann auf - das ist genau das, was von einer One-Shot-Animation erwartet wird! Allerdings hört die Animation an ihrem Ende nicht magischerweise mit dem Abspielen auf. Der äußere while-Loop läßt sie solange nicht starten, bis wir auf die Taste [E] drücken. Wenn Sie die Animationsgeschwindigkeit verändern wollen, spielen Sie ein wenig mit 0.7.

Übrigens: brauchen wir diese for-Loops wirklich? Kann man die nicht durch while-Loops ersetzen? Doch, man kann. Allerdings bräuchte man dann ein wenig mehr an Code und ich hoffe, Sie erinnern sich daran: Programmierer sind faul und vor allem deshalb wurden die for-Loops erfunden! Ersetzen Sie den obigen for-Loop doch jetzt mal durch einen guten, alten while-Loop.

Lösung (nicht lesen!!)
my.frame = 1;
while (my.frame < 12)
{
 
my.frame += 0.7 * time_step;
  
wait (1);
}

Weiter: Modelle. Animierte Modelle