Cave: Erste Schritte Anleitung
Charaktere und Animation
Lesson 13 of 19 • 25 XP
Keep your place in this quest
Log in or sign up for free to subscribe, follow lesson progress, and access more learning content.
Lassen Sie uns nun verstehen, wie Charaktere und Animationen in Cave funktionieren. Ein Charakter ist nicht nur ein 3D-Modell, das durch den Level läuft. In einem echten Spielsetup kombiniert er normalerweise Physik, Bewegungslogik, ein sichtbares Mesh, ein Rig, Animationen sowie Code oder Logic Bricks, die entscheiden, welche Animation abgespielt werden soll.
Um das praktisch zu machen, werden wir die Standard-Player-Vorlage aus den Starterprojekten von Cave als Hauptreferenz verwenden. Wenn Sie ein Third Person Game oder Top Down Game erstellen, generiert Cave einen Player, der sich bereits bewegt, eine sichtbare animierte Figur hat und grundlegende Lokomotionsanimationen abspielt.

Diese Lektion zeigt Ihnen, wie dieses Setup organisiert ist und wie Sie Animationen aus Python heraus steuern können.
Sie lernen:
- Wie die Starter-
Player-Vorlage aufgebaut ist. - Warum das sichtbare Mesh ein Kind-Entity ist und nicht direkt im Root-Player-Entity liegt.
- Was die Animation Component macht.
- Wie man den Animator aus Python bekommt (ähnliche Logik gilt für Logic Bricks).
- Wie man Animationen per Namen abspielt.
- Wie Blending, Layer und Bone-Filter funktionieren.
- Wie Animations-Callbacks und Sockets in das System passen.
Die Player-Template-Struktur
Im Starterprojekt ist der Player eine Entity Template. Das bedeutet, dass das Player-Setup wiederverwendbar ist und in mehreren Szenen platziert werden kann.
Die Struktur ist ungefähr so:

Die genauen Kind-Entities können je nach ausgewählten Projekteinstellungen variieren, aber die wichtige Idee ist, dass das Root-Player-Entity das Gameplay-Setup besitzt, während das Kind-Entity Mesh die sichtbare animierte Figur enthält.
Der Starter-Gegner verwendet eine sehr ähnliche Idee. Das Root-Entity des Gegners hat Charakterphysik und Verhalten, und das Kind-Entity Mesh enthält das sichtbare Mesh und die Animation Component.
Das Root Player Entity
Das Root-Player-Entity repräsentiert den Gameplay-Charakter. Es enthält üblicherweise:
- Eine
Transform Component. - Eine
Character Component. - Python-Komponenten für Spielbewegung, UI, Animationshelfer und andere Gameplay-Logiken.
- Eigenschaften wie Gesundheit oder optionale Verhaltensparameter.
- Kinder für Kamera, UI, sichtbares Mesh und Hilfsobjekte.
Die Character Component ist besonders wichtig, weil sie charaktertypische Physik behandelt: Gehen, Springen, Kollision, Steigungen und Bewegung innerhalb der Welt. Das bedeutet, dass das Root-Player hauptsächlich für Gameplay und Physik zuständig ist.
Das Mesh Child Entity
Innerhalb der Player-Vorlage gibt es ein Kind-Entity namens Mesh. Dieses Kind enthält üblicherweise:
- Eine
Transform Component. - Eine oder mehrere
Mesh Components. - Eine
Animation Component.
Die Mesh Component gibt dem Charakter sein sichtbares 3D-Modell und Material. Wenn der Charakter mehrere Materialien verwendet, kann Cave das mit mehreren Mesh Components darstellen, gemäß der Multimaterialregel, die in der Import-Lektion erklärt wurde.
Die Animation Component ist die Komponente, die das Rig auswertet und Animationen abspielt.
Kurz gesagt:
| Entity | Hauptverantwortung |
|---|---|
Player |
Physik, Bewegung, Gameplay-Logik, Eigenschaften. |
Player -> Mesh |
Sichtbares Modell, Material, Rig-Animation. |
Diese Trennung ist beabsichtigt.
Warum das Mesh ein Child Entity ist
Vielleicht fragen Sie sich, warum das Mesh nicht direkt auf dem Root-Player-Entity liegt.
Der Grund ist, dass die Physik-Charakterkomponente und die sichtbare animierte Figur oft unterschiedliche Transformationswerte benötigen.
Die Root-Player-Transform repräsentiert den Gameplay-Körper. Es ist das Objekt, das sich durch die Welt bewegt, mit Wänden kollidiert und die Character Component trägt. Die Transform des Kind-Entity Mesh repräsentiert, wie der Charakter aussieht.
Diese Trennung ist in vielen Situationen nützlich:
- Das Mesh benötigt eventuell eine andere Skalierung als die Physikkapsel.
- Das Mesh braucht eventuell eine kleine Verschiebung, um zur Charakterkollision auszurichten.
- Das Mesh muss sich unabhängig vom Bewegungsobjekt drehen können.
- Das Mesh soll sich in Bewegungsrichtung drehen, während der Root eine Gameplay-Ausrichtung behält.
- Das Mesh soll nach vorne schauen, während der Spieler seitwärts läuft.
Zum Beispiel, wenn der Spieler nach links läuft. Wenn Sie eine korrekte "links gehen"-Animation haben, möchten Sie vielleicht, dass das Charakter-Mesh nach vorne schaut, während die Animation die seitliche Bewegung steuert. Haben Sie keine seitliche Animation, können Sie das Mesh zur linken Seite drehen, damit der Charakter visuell in diese Richtung läuft.
Beide Fälle sind gültig.
Dafür sind separate Transformationen erforderlich:
| Transform | Steuert |
|---|---|
Root-Player-Transform |
Physik-Position, Gameplay-Körper, Bewegung in der Welt. |
Child-Mesh-Transform |
Visuelle Ausrichtung, Skalierung, Versatz, Präsentation der Animation. |
Deshalb halten der Standard-Player und Gegner das animierte Mesh als Child Entity.
Was die Animation Component macht
Die Animation Component ist die Hauptkomponente, die verwendet wird, um Skelettanimationen auf einem Entity abzuspielen.
Sie benötigt:
- Ein
Armature(Rig). - Eine Standard-
Animation. - Eine gültige Mesh Component auf demselben Entity (oder mehrere).
Das Rig definiert das Skelett. Die Animation beschreibt, wie das Skelett sich im Laufe der Zeit bewegt. Die Animation Component wertet die finale animierte Pose aus und wendet sie auf den sichtbaren Charakter an.
Im Standard-Starter-Player nutzt das Mesh-Kind:
Proto MeshProto MatProto Armaturep-idleals Standardanimation (Stillstand)
Diese Starter-Assets sind vorhanden, damit Sie sofort eine funktionierende animierte Figur inspizieren können.
Armatures, Animationen und Retargeting
- Ein
Armatureist das Skelett des Charakters. Es enthält die Knochen, denen das Mesh folgt. - Eine
Animationspeichert Bewegungen über Zeit für die Knochen des Armatures.
Die Animation Component verbindet diese Elemente:
| Teil | Bedeutung |
|---|---|
| Mesh | Das sichtbare Charaktermodell. |
| Armature | Das Skelett innerhalb des Charakters. |
| Animation | Die Bewegung, z.B. idle, walk, run oder fall. |
| Animation Component | Die Komponente, die die Animation am Entity abspielt. |
Cave kann Animationen auch retargeten, wenn möglich. Wenn eine abgespielte Animation zu einem anderen kompatiblen Rig gehört, kann die Animation Component Retargeting verwenden, um diese Bewegung auf das aktuelle Rig anzuwenden.
Retargeting ist nützlich, wenn Sie Animationen zwischen kompatiblen Charakteren wiederverwenden möchten, hängt aber von Qualität und Kompatibilität der importierten Rigs ab. Wenn eine Animation verzerrt oder verschoben aussieht, überprüfen Sie das Quellrig, das importierte Rig und die Retargeting-Einstellungen.
Den Animator aus Python holen
Kommen wir nun dazu, wie wir Charaktere mit Logik animieren können.
In Cave-Projekten ist es übliche Praxis, die Gameplay-Logik des Players im Root-Player-Entity zu halten und dann das Mesh-Kind von diesem Code aus zuzugreifen. Die Standard-Starter-Skripte folgen diesem Muster.
Hier ist das Grundmuster:
import cave
class PlayerAnimationExample(cave.Component):
def start(self, scene: cave.Scene):
self.mesh : cave.Entity = self.entity.getChild("Mesh")
self.animator : cave.AnimationComponent = self.mesh.get("Animation")
def update(self):
# Beispiel: Eine Idle-Animation abspielen.
self.animator.playByName("p-idle", blend=0.2, loop=True)
In diesem Beispiel:
self.entityist das Root-Player-Entity.getChild("Mesh")findet das visuelle Kind-Entity.self.mesh.get("Animation")holt die Animation Component.- Die Variable heißt
animator, was die übliche Benennung in Cave-Skripten ist. playByName(...)spielt eine Animation per Namen ab.
Das ist die grundlegende Verbindung, die Sie benötigen, bevor Sie Animationen über Code steuern.
Animationen per Namen abspielen
Die häufigste Methode, die Sie verwenden, ist playByName.
Die Grundnutzung sieht so aus:
self.animator.playByName("p-walk", blend=0.2, loop=True)
Wichtige Parameter sind:
| Parameter | Bedeutung |
|---|---|
anim |
Der Name der abzuspielenden Animations-Resource. |
blend |
Wie viele Sekunden Cave in die neue Animation überblenden soll. |
loop |
Ob die Animation wiederholt werden soll. |
layer |
Welche Animationsschicht die Animation abspielt. |
Zum Beispiel:
self.animator.playByName("p-idle", blend=0.2, loop=True)
self.animator.playByName("p-walk", blend=0.2, loop=True)
self.animator.playByName("p-run", blend=0.2, loop=True)
self.animator.playByName("p-fall", blend=0.2, loop=True)
Diese Aufrufe sind die gleichen, die auch die Standard-Player- und Gegner-Skripte verwenden.
Ein einfaches Lokomotionsbeispiel
Ein typisches Player-Animationssetup ist:
- Idle spielen, wenn der Charakter steht.
- Walk spielen, wenn der Charakter sich bewegt.
- Run spielen, wenn der Charakter sich beim Rennen bewegt.
- Fall spielen, wenn der Charakter nicht auf dem Boden ist.
Hier ein vereinfachtes Beispiel:
import cave
class SimplePlayerAnimator(cave.Component):
def start(self, scene: cave.Scene):
self.character : cave.CharacterComponent = self.entity.get("Character")
self.mesh : cave.Entity = self.entity.getChild("Mesh")
self.meshTransform : cave.TransformComponent = self.mesh.getTransform() if self.mesh else None
self.animator : cave.AnimationComponent = self.mesh.get("Animation") if self.mesh else None
def update(self):
if self.animator is None or self.character is None:
return
direction = self.character.getWalkDirection()
isMoving = direction.length() > 0
isRunning = False # Ersetzen Sie dies durch Ihre eigenen Eingabe- oder Gameplay-Bedingungen.
if self.character.onGround():
if isMoving:
if isRunning:
self.animator.playByName("p-run", blend=0.2, loop=True)
else:
self.animator.playByName("p-walk", blend=0.2, loop=True)
else:
self.animator.playByName("p-idle", blend=0.2, loop=True)
else:
self.animator.playByName("p-fall", blend=0.2, loop=True)
Dieses Beispiel ist absichtlich einfach gehalten. Der echte Starter-Controller behandelt auch Eingaben, Bewegungsrichtung, Springen, optionales Point-and-Click-Verhalten und Mesh-Rotation, aber das Animationsprinzip ist dasselbe.
Drehung des Meshs in Bewegungsrichtung
Da die visuelle Mesh-Entität ihre eigene Transformation hat, kann sie separat vom Root-Player rotiert werden.
Der Starter-Spieler macht dies, wenn sich der Charakter bewegt:
if direction.length() > 0 and self.meshTransform:
self.meshTransform.lookAtSmooth(
self.entity.getTransform().transformDirection(-direction),
6.0 * cave.getDeltaTime()
)
Die wichtige Idee ist nicht die genaue Mathematik, sondern dass der Gameplay-Körper und das visuelle Mesh separat gesteuert werden können.
Dadurch kann entschieden werden, wie der Charakter ausgerichtet sein soll:
- In Bewegungsrichtung schauen.
- Während des Seitwärtsgehens nach vorne schauen.
- Sanft in die gewünschte Richtung rotieren.
- Verschiedene visuelle Ausrichtungsregeln für unterschiedliche Spielmodi verwenden.
Dies ist einer der Hauptgründe, warum der Starter-Charakter ein Kind-Mesh hat.
Animationsübergänge (Blending)
Animation Blending sorgt für sanftere Übergänge.
Ohne Blending wechseln Animationen sofort und abruppt. Mit Blending kann Cave sanft von einer Animation zur nächsten wechseln – sofern beide auf derselben Ebene abgespielt werden.
Zum Beispiel:
# Von:
self.animator.playByName("p-idle", blend=0.2, loop=True)
# Zu:
self.animator.playByName("p-walk", blend=0.2, loop=True)
Hier bedeutet blend=0.2, dass Cave für 0,2 Sekunden in die neue Animation überblendet.
Das ist besonders wichtig für Charaktere, da Spieler abrupte Animationswechsel auch in Prototypen leicht bemerken.
Gute Anfängerwerte für Blending sind oft klein:
0.1für sehr schnelle Übergänge.0.2für normale Bewegungswechsel.0.4oder mehr für langsamere, schwerere Übergänge.
Hier sollte man nach Gefühl abstimmen.
Animationsschichten (Layers)
Cave unterstützt mehrere Animationsschichten. Jede Schicht kann ihre eigene Animation abspielen, und höhere Schichten können oben auf unteren Schichten liegen. Animationen können in jeder beliebigen Schicht abgespielt werden.
Die Standard-Locomotion-Animation läuft meist in Schicht 0.
Zum Beispiel:
self.animator.playByName("p-walk", blend=0.2, layer=0, loop=True)
Falls es eine Aktion für den Oberkörper gibt, z.B. Angriff, Zielen oder Nachladen, kann diese in einer anderen Schicht laufen:
self.animator.playByName("Attack", blend=0.1, layer=1, loop=False)
Die Idee:
| Schicht | Üblicher Zweck |
|---|---|
| Schicht 0 | Ganzkörper-Locomotion: Stillstand, Gehen, Laufen, Springen, Fallen. |
| Schicht 1 | Oberkörper-Aktionen: Angriff, Zielen, Nachladen, Waffe halten. |
Das ermöglicht, dass der Charakter weiter läuft, während eine andere Schicht eine Aktion ergänzt.
Schicht-Gewichte
Jede Schicht hat ein Gewicht, das steuert, wie stark sie einfließt.
Man kann es in Python ändern:
self.animator.setLayerWeight(1, 1.0)
Oder auslesen:
weight = self.animator.getLayerWeight(1)
Ein Gewicht von 1.0 bedeutet volle Aktivität, 0.0 keine sichtbare Wirkung.
Gewichte helfen, wenn man eine Aktionsschicht ein- oder ausblenden möchte, z.B. das langsame Anheben einer Waffe oder das Einnehmen einer speziellen Haltung.
Zu erwähnen ist, dass das blend-Parameter nur bei Übergängen zwischen Animationen derselben Schicht wirkt. Wenn plötzlich eine Animation in Schicht 1 gespielt wird während vorher nur Schicht 0 aktiv war, erfolgt kein Blending zwischen diesen, egal wie hoch der blend-Wert ist. Blending zwischen Schichten erfordert eigene Logik, die Schichtgewichte nutzt.
Knochenfilter
Knochenfilter steuern, welche Knochen von einer Schicht beeinflusst werden und wie stark jeder Knochen wirkt.
Dies ermöglicht z.B. Oberkörper-Animationen.
Beispiel:
- Schicht 0 steuert den ganzen Körper.
- Schicht 1 steuert nur Wirbelsäule, Arme, Hände und Waffenknochen.
In Python kann man einen Filter anlegen und Einfluss auf einen Knochen und dessen Kinder setzen:
def setupUpperBodyLayer(self):
armature = self.animator.armature.get()
spine = armature.getBone("mixamorig:Spine")
upperBody = self.animator.createLayerFilter(1)
upperBody.defaultBlend = 0.0
upperBody.setToBone(spine, 1.0, recursive=True)
Hier:
defaultBlend = 0.0heißt, die Schicht beeinflusst standardmäßig keine Knochen.setToBone(spine, 1.0, recursive=True)setzt volle Beeinflussung auf Wirbelsäule und Kinder.- Schicht
1ist nun für Oberkörper-Animationen bereit.
Die Knochen-Namen hängen von der importierten Armatur ab. Immer die Armatur inspizieren und korrekte Knochen-Namen verwenden.
Praktisches Layer-Beispiel
Stell dir einen Third-Person-Charakter vor, der gleichzeitig laufen und angreifen kann.
Mögliche Schicht-Zuweisung:
| Schicht | Zuständigkeit |
|---|---|
| Schicht 0 | Stillstand, Gehen, Laufen, Fallen. |
| Schicht 1 | Oberkörper-Angriff. |
Beispiel-Code:
import cave
class CombatAnimator(cave.Component):
def start(self, scene: cave.Scene):
self.mesh = self.entity.getChild("Mesh")
self.animator : cave.AnimationComponent = self.mesh.get("Animation") if self.mesh else None
if self.animator:
armature = self.animator.armature.get()
spine = armature.getBone("mixamorig:Spine")
upperBody = self.animator.createLayerFilter(1)
upperBody.defaultBlend = 0.0
upperBody.setToBone(spine, 1.0, recursive=True)
self.animator.setLayerWeight(1, 1.0)
def playWalk(self):
self.animator.playByName("p-walk", blend=0.2, layer=0, loop=True)
def playAttack(self):
self.animator.playByName("Attack", blend=0.1, layer=1, loop=False)
Das Beispiel geht davon aus, dass eine Animations-Asset Attack existiert. Falls dein Projekt einen anderen Namen nutzt, verwende diesen.
Wichtig ist, dass die Locomotion auf Schicht 0 verbleibt, während der Angriff auf Schicht 1 abgespielt wird und nur die gefilterten Knochen beeinflusst.
Animations-Callbacks
Animationen können zu bestimmten Zeitpunkten Python-Code ausführen.
Cave unterstützt Callback-Ereignisse wie:
- Beginn der Animation.
- Ende der Animation.
- Ein bestimmter Animationsframe.
Callbacks sind nützlich, wenn Animationen Gameplay oder Effekte auslösen sollen.
Beispiele:
- Abspielen eines Schrittsounds.
- Aufwirbeln von Staub beim Schritt.
- Schaden verursachen bei einem Angriffsframe.
- Start einer Waffen-Spur.
- Auslösen eines Partikeleffekts.
- Benachrichtigung, dass eine Animation abgeschlossen ist.
Oft kennt die Animation den genauen Zeitpunkt besser als der Code. Wenn ein Schwert nur Schaden anrichten soll, wenn der Schwung das Ziel erreicht, kann ein Animations-Callback diesen Moment genau im Frame setzen.
Beispiel Fußschritt-Callback
Starter-Projekte enthalten oft Fußschritt-Callbacks in Lauf- und Geh-Animationen. Die Betrachtung dieser zeigt praktische Callback-Anwendung.
Die Idee:
- Die Animation weiß, wann der Fuß den Boden berührt.
- Der Callback wird in diesem Frame gesetzt.
- Der Callback spielt einen Sound oder erzeugt einen kleinen Effekt.
Innerhalb eines Animations-Callbacks stellt Cave nützliche Variablen zur Verfügung:
entity, die Besitzende Entität.animator, die Animation Component.handle, der Animationslayer/-handler.
Ein simpler Fußschritt-Callback könnte so aussehen:
cave.playSound("Footstep Grass", volume=0.5)
Diese Idee lässt sich erweitern, um je nach Bodenmaterial, Charakterspeed oder Zustand verschiedene Sounds abzuspielen.
Wiederverwendbare Animations-Callbacks
Callbacks gehören zum Animations-Asset.
Das bedeutet, wenn mehrere Entitäten dieselbe Animation abspielen, läuft der Callback für jede Entität, die sie nutzt.
So sind Callbacks wiederverwendbar. Die gleiche Geh-Animation kann für jeden Charakter Fußschritt-Sounds auslösen, sofern der Callback den Kontext der Besitz-Entität berücksichtigt.
Die Animation speichert das Timing, die Entität liefert den Kontext.
Fortgeschrittene Pose-Callbacks
Cave erlaubt außerdem Post-Evaluation-Callbacks per Python. Diese laufen, nachdem die Animation Component die Armatur-Pose berechnet hat.
Diese Funktion ist fortgeschritten, aber einsetzbar für:
- Inverse Kinematik (IK).
- Kopf-Ausrichtung.
- Waffen-Ausrichtung.
- Fuß-Auflage.
- Finale Posen-Anpassungen.
Das Starter-Player Toolkit enthält ein Foot IK Beispiel, das dieses Konzept nutzt. Es holt das Mesh-Kind, die Animation Komponente und registriert eine Methode mit:
self.animator.addPostEvaluationCallback(self.postEvaluation)
Du musst nicht sofort IK-Systeme schreiben, aber es ist gut zu wissen, dass Cave Python erlaubt, die finale Pose nach der normalen Animation anzupassen.
Animation Socket Component
Die Animation Socket Component erlaubt einer Kind-Entität, einem Knochen einer übergeordneten animierten Entität zu folgen.
Die Kind-Entität muss sich unter einem Elternobjekt mit Animation Component befinden. Dann kann der Socket einen Knochen wählen und Position, Rotation und optional Skalierung von diesem Knochen kopieren, mit Offsets falls nötig.
Sockets eignen sich gut, um Dinge an animierte Charaktere anzudocken:
- Schwert in der Hand.
- Waffe in der Hand.
- Schild am Arm.
- Helm am Kopf.
- Rucksack am Wirbelknochen der Wirbelsäule.
- Requisite an einer Hand befestigt.
Zum Beispiel: Wenn ein Charakter ein Schwert hält, kann das Schwert eine untergeordnete Entity mit einem Animation Socket Component sein, das dem Handknochen folgt. Während der Charakter angreift, läuft oder inaktiv ist, bleibt das Schwert an der korrekten animierten Position befestigt.
Was Sie sich merken sollten
- Der Starter-Player ist eine Vorlage mit einer Root-
Player-Entity und einer untergeordnetenMesh-Entity. - Die Root-
Player-Entity steuert üblicherweise Physik, Bewegung, Eigenschaften und Gameplay-Logik. - Die untergeordnete
Mesh-Entity steuert typischerweise das sichtbare Modell, Material, Armatur und die Animation Component. - Diese Trennung ermöglicht physikalisch-körperlichen und visuellen Charaktern unterschiedliche Transformationswerte.
- Die Animation Component spielt Skelettanimationen unter Verwendung einer Armatur und Animationsressourcen ab.
- In Python wird die Animation Component oft
animatorgenannt. playByNamespielt eine Animationsressource nach Namen ab und unterstützt Blending, Schleifen und Ebenen.- Ebenen erlauben, mehrere Animationen übereinander zu stapeln.
- Knochensfilter lassen eine Ebene nur bestimmte Teile des Skeletts beeinflussen.
- Animations-Callbacks verbinden den Animationszeitpunkt mit Gameplay-Ereignissen.
- Animation Sockets befestigen Requisiten an animierten Knochen.
Wenn Sie die Starter-Player- oder Enemy-Vorlage untersuchen, verwenden Sie diese Struktur als Karte: Root-Entity für Gameplay, untergeordnete Mesh-Entity für Visuals und Animation.