StatefulAnimation (Zustandsbehaftete Animation)1
Die Klasse StatefulAnimation ist im
Paket pi.actor enthalten und kann über die Anweisung
import pi.actor.StatefulAnimation;
importiert werden.
Mit der Figur StatefulAnimation lassen sich komplexe Spielfiguren mit wenig Aufwand umsetzen.
Nehmen wir dieses Beispiel:2
| Zustand | Animiertes GIF |
|---|---|
| Idle | |
| Jumping | |
| Midair | |
| Falling | |
| Landing | |
| Walking | |
| Running |
Zustandsübergangsdiagramm für die Figur
Bevor mit der Umsetzung begonnen wird, ist es sinnvoll, die Zustände und deren Übergänge zu modellieren. Hier ist ein mögliches Zustandsübergangsdiagramm für die Figur.

Zustandsübergangsdiagramm für die Figur
Die Zustände als Aufzählungstyp (Enumeration)
Die Zustände einer Figur werden in der Engine stets als Aufzählungstyp (enum) implementiert. Dieser Aufzählungstyp definiert die Spielerzustände und speichert gleichzeitig die Dateipfade der zugehörigen GIF-Dateien.
Ist beispielsweise das GIF des Zustandes
JUMPING gefragt, so ist es jederzeit mit JUMPING.gifFileLocation()
erreichbar. Dies macht den Code deutlich wartbarer.
Die Klasse der Spielfigur
Mit den definierten Zuständen in PlayerState kann nun die Implementierung der
eigentlichen Spielfigur beginnen:
In setupPlayerStates() werden alle in PlayerState definierten
Zustände der Spielfigur eingepflegt, inklusive des Einladens der animierten
GIFs.
Zwei der Zustände bestehen nur aus einen Animationszyklus. Danach sollen sie in
einen anderen Zustand übergehen: MIDAIR geht über zu FALLING
und LANDING geht über zu IDLE. Diese Übergänge können direkt
über die Methode stateTransition() umgesetzt werden.
Schließlich wird in setupPhysics() die Figur über die Engine-Physik
noch dynamisch gesetzt und bereit gemacht, sich als Platformer-Figur der
Schwerkraft auszusetzen. Der hohe Reibungswert friction(30) sorgt
dafür, dass die Figur später schnell auf dem Boden abbremsen kann, sobald sie
sich nicht mehr bewegt. Ein Verhalten, dass bei den meisten Platformern
erwünscht ist.
Einbetten in eine Szene
Damit die Figur getestet werden kann, schreiben wir ein schnelles Testbett für sie. In einer Scene bekommt sie einen Boden zum Laufen:

Der Zwischenstand: Noch passiert nicht viel.
36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 | |
Die Figur bleibt im IDLE-Zustand hängen. Nun gilt es, die übrigen Zustandsübergänge zu implementieren.
Implementieren der Zustände & Übergänge
Springen

Wir fokussieren uns nun auf die Übergänge zum Springen.
Auf Tastendruck (Leertaste) soll die Spielfigur
springen, wenn sie auf festem Boden steht. Die Spielfigur implementiert nun
zusätzlich den KeyStrokeListener und führt auf Leertastendruck die Sprungroutine aus:

Die Figur kann springen, aber nicht landen.
Quellcode: demos/stateful_animation/StatefulPlayerCharacter.java#L92-L104
Zum Java-Code: demos/tutorials/stateful_animation/StatefulPlayerCharacter.java
private void attemptJump()
{
PlayerState state = getCurrentState();
if (state == PlayerState.IDLE || state == PlayerState.WALKING
|| state == PlayerState.RUNNING)
{
if (isGrounded())
{
applyImpulse(new Vector(0, JUMP_IMPULSE));
setState(PlayerState.JUMPING);
}
}
}
Fallen und Landen

Die nächsten Übergänge, die wir umsetzen, sind für das Fallen und Landen.
Als nächstes sorgen wir dafür, dass die Figur landen kann und schließlich zurück
in den IDLE-Zustand kommt. Dafür ist die Geschwindigkeit der Figur in
Y-Richtung wichtig. Im Zustandsübergangsdiagramm haben wir dafür v_y < 0 als
Fallen definiert und v_y = 0 als Stehen. Das ist im Modell in Ordnung,
allerdings ist die Physik mit Fließkomma-Zahlen nicht ideal für „harte“
Schwellwerte. Stattdessen definieren wir einen Grenzwert, innerhalb dessen wir
auf 0 runden. (private static final float THRESHOLD = 0.01;).
Unsere Spielfigur soll in jedem Einzelbild ihre eigene Y-Geschwidingkeit
überprüfen. Dazu implementiert sie nun zusätzlich FrameUpdateListener und
prüft in jedem Frame entsprechend unseres Zustandsübergangsdiagrammes:

Die Figur hat jetzt einen vollen Sprungzyklus
Player Movement
Die letzten zu implementierenden Zustände sind die Bewegung des Spielers. Durch die Physik-Engine gibt es viele Möglichkeiten, Bewegung im Spiel zu simulieren. Ein physikalisch korrekte Implementierung ist die kontinuierliche Anwendung einer Bewegungskraft:

Player Movement
Die (je nach Tastendruck gerichtete) Kraft beschleunigt die Spielfigur, bis die
Reibung die wirkende Kraft ausgleicht. In der Methode setupPhysics() wurden
bereits folgende Reibung für die Figur aktiviert:
- Luftreibung (gesetzt mit
setLinearDamping(.3)) - Kontaktreibung, z. B. mit Plattformen (gesetzt mit
setFriction(30))
Die Maximalgeschwindigkeit sowie die konstant wirkende Kraft setzen wir als Konstanten in der Klasse der Figur, um diese Werte schnell ändern zu können:
Zum Java-Code: demos/subprojects/demos/src/main/java/demos/docs/main_classes/actor/stateful_animation/StatefulPlayerCharacter.javaUm die Kraft und die Geschwindigkeit frameweise zu implementieren, wird die
Methode onFrameUpdate(double pastTime) erweitert:

Die Figur kann sich bewegen, jedoch resultiert dies noch nicht in Zustandsänderung.
In der Methode onFrameUpdate():
Die Übergänge IDLE - WALKING - RUNNING

Die letzten zu implementierenden Zustandsübergänge hängen von der Spielerbewegung ab.
Die Figur kann jetzt voll gesteuert werden. Die Zustände WALKING und RUNNING
können nun eingebracht werden. Ist die Figur in einem der drei „bodenständigen“
Zustände (IDLE, WALKING, RUNNING), so hängt der Übergang zwischen diesen
Zuständen nur vom Betrag ihrer Geschindigkeit ab:
- Bewegt sich die Figur „langsam“, so ist sie
WALKING. - Bewegt sich die Figur „schnell“, so ist sie
RUNNING. - Bewegt sich die Figur „gar nicht“, so ist sie
IDLE.
Um die Begriffe „langsam“ und „schnell“ greifbar zu machen, ist einen Grenzwert nötig. Dazu definieren wir Konstanten in der Figur:
Sobald sich die Figur mindestens 1 Meter pro Sekunde bewegt, zählt sie als WALKING,
sobald sie sich mindestens 10 Meter pro Sekunde bewegt (die Hälfte der maximalen
Geschwindigkeit), so zählt sie als RUNNING.
Auf diese Grenzwerte wird jeden Frame in der onFrameUpdate(...) der Spielfigur
geprüft, genauso wie zuvor die Y-Geschwindigkeit implementiert wurde. Damit ist
die neue onFrameUpdate(...):

Die Figur ist mit ihren Zuständen und Übergängen vollständig implementiert.
Die letzte Überprüfung der X-Geschwindigkeit dient dazu, die Bewegungsrichtung
festzustellen. Mit dieser Info kann zum richtigen Zeitpunkt über
setFlipHorizontal(boolean flip) die Blickrichtung der Figur angepasst werden.
-
Der Abschnitt stammt aus dem Engine-Alpha-Wiki: engine-alpha.org/wiki/v4.x/Stateful_Animation ↩
-
Die Spielfigur stammt aus dem Open Pixel Project, aus dem Ordner ./sprites/humans/traveler/ der Zip-Datei opp-assets.zip. ↩