Keep your place in this quest

Log in or sign up for free to subscribe, follow lesson progress, and access more learning content.

Maintenant, comprenons comment les personnages et les animations fonctionnent dans Cave. Un personnage n'est pas simplement un modèle 3D se déplaçant dans le niveau. Dans un véritable configuration de jeu, il combine généralement la physique, la logique de mouvement, un maillage visible, une armature, des animations et du code ou des Logic Bricks qui décident quelle animation doit être jouée.

Pour rendre cela pratique, nous utiliserons le modèle Player par défaut des projets de démarrage de Cave comme référence principale. Si vous créez un Third Person Game ou un Top Down Game, Cave génère un joueur qui se déplace déjà, possède un personnage animé visible et joue des animations de locomotion de base.

image.png

Cette leçon vous montrera comment cette configuration est organisée et comment vous pouvez contrôler les animations depuis Python.

Vous apprendrez :

  • Comment le modèle Player de démarrage est construit.
  • Pourquoi le maillage visuel est une entité enfant au lieu d'être sur l'entité principale du joueur.
  • Ce que fait le Component d'Animation.
  • Comment obtenir l'animateur depuis Python (une logique similaire s'applique aux Logic Bricks).
  • Comment jouer des animations par nom.
  • Comment fonctionnent le mélange, les couches et les filtres d'os.
  • Comment les rappels d'animation et les sockets s'intègrent dans le système.

La structure du modèle Player

Dans le projet de démarrage, le Player est un modèle d'Entité. Cela signifie que la configuration du joueur est réutilisable et peut être placée dans plusieurs scènes.

La structure est à peu près comme ceci :

image.png

Les enfants exacts peuvent varier en fonction des options de projet que vous avez sélectionnées, mais l'idée principale est que l'entité racine Player possède la configuration de gameplay, tandis que l'entité enfant Mesh possède le personnage animé visible.

L'ennemi de démarrage utilise une idée très similaire. L'entité racine de l'ennemi possède la physique et le comportement du personnage, et son entité enfant Mesh contient le maillage visible et le Component d'Animation.

L'entité racine Player

L'entité racine Player représente le personnage de gameplay. Elle contient généralement :

  • Un Transform Component.
  • Un Character Component.
  • Des Components Python pour le mouvement du joueur, l'UI, les aides d'animation et d'autres logiques de gameplay.
  • Des propriétés comme la santé ou des réglages de comportement optionnels.
  • Des enfants utilisés pour la caméra, l'UI, le maillage visuel et des objets d'aide.

Le Character Component est particulièrement important car il gère la physique de type personnage : marche, saut, collision, pentes et mouvement par rapport au monde. Cela signifie que le Player racine est principalement responsable du gameplay et de la physique.

L'entité enfant Mesh

À l'intérieur du modèle du joueur, il y a une entité enfant appelée Mesh. Cet enfant contient généralement :

  • Un Transform Component.
  • Un ou plusieurs Mesh Components.
  • Un Animation Component.

Le Mesh Component donne au personnage son modèle 3D visible et son matériau. Si le personnage utilise plusieurs matériaux, Cave peut représenter cela avec plusieurs Mesh Components, suivant la même règle de multimatériau expliquée dans la leçon d'importation.

Le Animation Component est ce qui évalue l'armature et joue les animations.

Donc, de manière simple :

Entité Responsabilité Principale
Player Physique, mouvement, logique de gameplay, propriétés.
Player -> Mesh Modèle visible, matériau, animation de l'armature.

Cette séparation est intentionnelle.

Pourquoi le maillage est une entité enfant

Vous vous demandez peut-être pourquoi le maillage n'est pas placé directement sur l'entité racine Player.

La raison est que le personnage physique et le personnage animé visible ont souvent besoin de transformations différentes.

La transformation racine Player représente le corps de gameplay. C'est ce qui se déplace à travers le monde, entre en collision avec les murs et porte le Character Component. La transformation enfant Mesh représente l'apparence du personnage.

Cette séparation est utile dans de nombreuses situations :

  • Le maillage peut avoir une échelle différente de celle de la capsule physique.
  • Le maillage peut nécessiter un petit décalage pour s'aligner avec la collision du personnage.
  • Le maillage peut devoir tourner indépendamment du corps de mouvement.
  • Le maillage peut devoir faire face à la direction du mouvement tandis que la racine conserve une orientation de gameplay.
  • Le maillage peut devoir rester face à l'avant tandis que le joueur se déplace latéralement.

Par exemple, imaginez que le joueur se déplace à gauche. Si vous avez une animation correcte de "marcher à gauche", vous voudrez peut-être que le maillage du personnage reste face à l'avant pendant que l'animation gère le mouvement latéral. Mais si vous n'avez pas d'animation latérale, vous voudrez peut-être que le maillage tourne vers la gauche pour que le personnage marche visuellement dans cette direction.

Les deux cas sont valables.

Ils nécessitent des transformations séparées :

Transformation Contrôles
Transformation racine Player Position physique, corps de gameplay, mouvement à travers le monde.
Transformation enfant Mesh Orientation visuelle, échelle, décalages, présentation de l'animation.

C'est pourquoi le joueur et l'ennemi de démarrage gardent le maillage animé comme une entité enfant.

Ce que fait le Animation Component

Le Animation Component est le principal composant utilisé pour jouer des animations squelettiques sur une entité.

Il a besoin :

  • D'une Armature.
  • D'une Animation par défaut.
  • D'un Mesh Component valide sur la même entité (ou plusieurs).

L'armature définit le squelette. L'animation définit comment ce squelette bouge au fil du temps. Le Animation Component évalue la pose animée finale et l'applique au personnage visible.

Dans le modèle du joueur par défaut, l'enfant Mesh utilise :

  • Proto Mesh
  • Proto Mat
  • Proto Armature
  • p-idle comme animation par défaut (animation d'attente)

Ces ressources de démarrage sont là pour que vous puissiez immédiatement inspecter un personnage animé fonctionnel.

Armatures, Animations et Retargeting

  • Une Armature est le squelette du personnage. Il contient les os que le maillage suit.
  • Une Animation stocke le mouvement au fil du temps pour les os dans une armature.

Le Animation Component connecte ces pièces ensemble :

Pièce Signification
Mesh Le modèle visible du personnage.
Armature Le squelette à l'intérieur du personnage.
Animation Le mouvement, tel qu'attendre, marcher, courir ou tomber.
Animation Component Le composant qui joue l'animation sur l'entité.

Cave peut également re-cibler les animations lorsque cela est possible. Si l'animation jouée appartient à une armature compatible différente, le Animation Component peut utiliser le re-ciblage pour appliquer ce mouvement à l'armature actuelle.

Le re-ciblage est utile lorsque vous souhaitez réutiliser des animations sur des personnages compatibles, mais cela dépend toujours de la qualité et de la compatibilité des rigs importés. Si une animation semble tordue, décalée ou étrange, vérifiez le rig source, l'armature importée et la configuration de re-ciblage.


Obtenir l'animateur depuis Python

Maintenant, commençons à explorer comment nous pouvons animer les personnages par la logique.

Dans les projets Cave, il est courant de garder la logique de gameplay du joueur sur l'entité racine Player, puis d'accéder à l'enfant Mesh depuis ce code. Les scripts de démarrage par défaut suivent cette idée.

Voici le schéma de base :

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):
        # Exemple : jouer une animation d'attente.
        self.animator.playByName("p-idle", blend=0.2, loop=True)

Dans cet exemple :

  • self.entity est l'entité racine Player.
  • getChild("Mesh") trouve l'entité enfant visuelle.
  • self.mesh.get("Animation") obtient le Animation Component.
  • La variable est appelée animator, ce qui est la pratique de nommage courante dans les scripts Cave.
  • playByName(...) joue une ressource d'animation par son nom.

C'est la connexion de base dont vous avez besoin avant de contrôler les animations par le code.

Jouer des animations par nom

La méthode la plus courante que vous utiliserez est playByName.

Son utilisation de base ressemble à ceci :

self.animator.playByName("p-walk", blend=0.2, loop=True)

Les paramètres importants sont :

Paramètre Signification
anim Le nom de la ressource d'animation à jouer.
blend Combien de temps, en secondes, Cave doit se fondre dans la nouvelle animation.
loop Si l'animation doit se répéter.
layer Quelle couche d'animation doit jouer l'animation.

Par exemple :

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)

Ce sont les mêmes appels utilisés par le joueur et les scripts ennemis par défaut.

Un exemple simple de locomotion

Une configuration d'animation de joueur courante est :

  • Jouer l'attente lorsque le personnage est debout.
  • Jouer la marche lorsque le personnage se déplace.
  • Jouer la course lorsque le personnage se déplace en courant.
  • Jouer la chute lorsque le personnage n'est pas au sol.

Voici un exemple simplifié :

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 # Remplacez ceci par votre propre entrée ou condition de gameplay.

```python
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)

Cet exemple est intentionnellement simple. Le vrai contrôleur de départ gère également l'entrée, la direction du mouvement, le saut, un comportement optionnel de point et clic, et la rotation de la maquette, mais l'idée d'animation est la même.

Rotation de la maquette vers le mouvement

Parce que l'entité visuelle Mesh a sa propre transformation, vous pouvez la faire pivoter séparément de la racine Player.

Le joueur de départ le fait lorsque le personnage se déplace :

if direction.length() > 0 and self.meshTransform:
    self.meshTransform.lookAtSmooth(
        self.entity.getTransform().transformDirection(-direction),
        6.0 * cave.getDeltaTime()
    )

L'idée importante n'est pas le calcul exact. L'idée importante est que le corps de jeu et la maquette visuelle peuvent être contrôlés séparément.

Cela vous permet de choisir comment le personnage doit faire face :

  • Faire face à la direction du mouvement.
  • Continuer à faire face vers l'avant en déplaçant latéralement.
  • Tourner en douceur vers la direction désirée.
  • Utiliser différentes règles d'orientation visuelle pour différents modes de jeu.

C'est l'une des principales raisons pour lesquelles le personnage de départ possède une entité enfant Mesh.

Mélange d'animation

Le mélange d'animation rend les transitions plus fluides.

Sans mélange, passer d'une animation à une autre peut se faire instantanément. Avec le mélange, Cave peut passer en douceur d'une animation à l'autre sur la même couche.

Par exemple :

# De ceci :
self.animator.playByName("p-idle", blend=0.2, loop=True)

# À cela :
self.animator.playByName("p-walk", blend=0.2, loop=True)

Ici, blend=0.2 signifie que Cave mélangera dans la nouvelle animation sur 0,2 secondes.

C'est particulièrement important pour les personnages. Un joueur peut remarquer des sauts d'animation très facilement, même dans un prototype.

De bonnes valeurs de mélange pour les débutants sont généralement petites :

  • 0.1 pour des transitions très rapides.
  • 0.2 pour des transitions de locomotion normales.
  • 0.4 ou plus pour des transitions plus lentes et plus lourdes.

Vous devriez régler cela en fonction de votre ressenti.

Couches d'animation

Cave prend en charge plusieurs couches d'animation. Chaque couche peut jouer sa propre animation, et des couches supérieures peuvent être superposées à des couches inférieures. Vous pouvez exécuter des animations dans n'importe quelle couche que vous souhaitez.

L'animation de locomotion par défaut s'exécute généralement sur la couche 0.

Par exemple :

self.animator.playByName("p-walk", blend=0.2, layer=0, loop=True)

Si vous avez une animation d'action du haut du corps, comme une attaque, viser ou recharger, vous pouvez la jouer sur une autre couche :

self.animator.playByName("Attack", blend=0.1, layer=1, loop=False)

L'idée est :

Couche But commun
Couche 0 Locomotion du corps entier, comme rester immobile, marcher, courir, sauter, tomber.
Couche 1 Actions du haut du corps, comme attaquer, viser, recharger, tenir une arme.

Cela permet au personnage de continuer à marcher pendant qu'une autre couche ajoute une action par-dessus.

Poids des couches

Chaque couche a un poids, qui contrôle combien d'influence cette couche a.

Vous pouvez le changer depuis Python :

self.animator.setLayerWeight(1, 1.0)

Vous pouvez également le lire :

weight = self.animator.getLayerWeight(1)

Un poids de 1.0 signifie que la couche est totalement active. Un poids de 0.0 signifie qu'elle n'a aucune influence visible.

Les poids des couches sont utiles lorsque vous souhaitez faire un fondu d'une couche d'action, comme lever lentement une arme, viser ou fondre dans une posture spéciale.

Il convient de mentionner que le paramètre de mélange ne mélange que les animations entre les mêmes couches. Et nous ne mélangerons pas les animations jouées à des couches différentes. Donc, si vous avez une animation jouée à la couche 0, et ensuite tout à coup vous jouez une animation à la couche 1, peu importe la valeur de mélange, elle ne se mélangera pas entre elles. Elle ne se mélangera que s'il y avait déjà une autre animation en cours d'exécution à la couche 1. Si vous souhaitez mélanger entre les couches, vous devez créer une logique personnalisée pour utiliser le poids de la couche afin de le faire.

Filtres d'os

Les filtres d'os contrôlent quels os sont affectés par une couche et combien d'influence chaque os reçoit.

C'est ce qui rend l'animation du haut du corps possible.

Par exemple, vous pouvez vouloir :

  • Que la couche 0 contrôle tout le corps.
  • Que la couche 1 contrôle uniquement la colonne vertébrale, les bras, les mains et les os de l'arme.

En Python, vous pouvez créer un filtre pour une couche et assigner une influence à un os et à ses enfants :

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)

Dans cet exemple :

  • defaultBlend = 0.0 signifie que la couche n'affecte aucun os par défaut.
  • setToBone(spine, 1.0, recursive=True) signifie que la colonne vertébrale et ses enfants sont affectés entièrement.
  • La couche 1 peut maintenant être utilisée pour les animations du haut du corps.

Noms de vos os dépendent de l'armature que vous avez importée. Inspectez toujours votre armature et utilisez les bons noms d'os pour votre personnage.

Exemple pratique de couche

Imaginez un personnage à la troisième personne qui peut marcher et attaquer en même temps.

Une configuration possible est :

Couche Ce qu'elle gère
Couche 0 Rester immobile, marcher, courir, tomber.
Couche 1 Attaque du haut du corps.

Le code pourrait ressembler à ceci :

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)

Cet exemple suppose que vous avez un actif d'animation appelé Attack. Si votre projet utilise un nom d'animation différent, utilisez ce nom à la place.

L'idée importante est que la locomotion reste à la couche 0, tandis que l'attaque est jouée à la couche 1 et n'affecte que les os filtrés.

Rappels d'animation

Les animations peuvent exécuter du code Python à des moments spécifiques.

Cave prend en charge des rappels tels que :

  • Au début de l'animation.
  • À la fin de l'animation.
  • À une image spécifique de l'animation.

Les rappels sont utiles lorsque l'animation doit déclencher un gameplay ou des effets.

Exemples :

  • Jouer un son de pas.
  • Faire apparaître de la poussière sur un pas.
  • Appliquer des dégâts sur une image d'attaque.
  • Commencer un sillage d'arme.
  • Déclencher un effet de particules.
  • Notifier la logique qu'une animation est terminée.

Les animations connaissent souvent le chronométrage exact mieux que le code. Si une épée doit infliger des dégâts uniquement lorsque le mouvement atteint la zone cible, un rappel d'animation peut placer ce moment de gameplay sur la bonne image.

Exemple de rappel de pas

Les projets de départ peuvent inclure des rappels de pas dans les animations de marche et de course. Inspecter ceux-ci est un bon moyen de voir un rappel d'animation pratique en action.

L'idée est simple :

  1. L'animation sait quand le pied touche le sol.
  2. Le rappel est placé sur cette image.
  3. Le rappel joue un son ou fait apparaître un petit effet.

À l'intérieur d'un rappel d'animation, Cave fournit des variables utiles telles que :

  • entity, l'entité propriétaire.
  • animator, le composant d'animation.
  • handle, la couche/gestionnaire d'animation en cours de lecture.

Un très petit rappel de pas pourrait ressembler à ceci :

cave.playSound("Footstep Grass", volume=0.5)

Vous pouvez ensuite étendre cette idée pour choisir différents sons en fonction du matériel du sol, de la vitesse du personnage ou de l'état actuel.

Rappels d'animation réutilisables

Les rappels appartiennent à l'actif d'animation.

Cela signifie que si plusieurs entités jouent la même animation, le rappel peut s'exécuter pour chaque entité qui l'utilise.

Cela rend les rappels réutilisables. Par exemple, la même animation de marche peut déclencher des sons de pas pour chaque personnage qui la joue, tant que le code du rappel utilise correctement l'entité propriétaire.

L'animation stocke le chronométrage. L'entité fournit le contexte.

Rappels de pose avancés

Cave prend également en charge des rappels après évaluation via Python. Ceux-ci se produisent après que le composant d'animation évalue la pose de l'armature.

C'est une fonctionnalité avancée, mais elle peut être utilisée pour :

  • Cinématique inverse.
  • Visée de la tête.
  • Visée de l'arme.
  • Placement des pieds.
  • Ajustements finaux de pose.

Le Player Toolkit de départ inclut un exemple de IK de pied qui utilise ce concept. Il obtient l'enfant Mesh, obtient le composant Animation, puis enregistre une méthode avec :

self.animator.addPostEvaluationCallback(self.postEvaluation)

Vous n'avez pas besoin d'écrire des systèmes IK immédiatement, mais il est utile de savoir que Cave permet à Python d'ajuster la pose finale après la lecture normale de l'animation.

Composant de socket d'animation

Le Composant de socket d'animation permet à une entité enfant de suivre un os d'une entité parent animée.

L'entité enfant doit être sous un parent qui a un composant d'animation. Ensuite, le socket peut choisir un os et copier la position, la rotation, et éventuellement l'échelle de cet os, avec des décalages si besoin.

Les sockets sont utiles pour attacher des choses aux personnages animés :

  • Épée dans une main.
  • Pistolet dans une main.
  • Bouclier sur un bras.
  • Casque sur une tête.
  • Sac à dos sur un os de colonne vertébrale.
  • Accessoire attaché à une main.

Par exemple, si un personnage tient une épée, l'épée peut être une entité enfant avec un composant Animation Socket qui suit l'os de la main. Lorsque le personnage attaque, court ou reste immobile, l'épée reste attachée à la bonne position animée.

Ce Que Vous Devriez Retenir

  • Le joueur de départ est un modèle avec une entité racine Player et une entité enfant Mesh.
  • L'entité racine Player gère généralement la physique, le mouvement, les propriétés et la logique du gameplay.
  • L'entité enfant Mesh gère généralement le modèle visible, le matériau, l'armature et le composant d'animation.
  • Cette séparation donne au corps physique et au personnage visuel des transformations distinctes.
  • Le composant d'animation joue des animations squelettiques à l'aide d'une armature et d'actifs d'animation.
  • En Python, il est courant d'appeler le composant d'animation animator.
  • playByName joue un actif d'animation par nom et prend en charge le mélange, les répétitions et les couches.
  • Les couches permettent à plusieurs animations de se superposer.
  • Les filtres d'os permettent à une couche d'affecter uniquement une partie du squelette.
  • Les rappels d'animation connectent le timing de l'animation aux événements de gameplay.
  • Les sockets d'animation attachent des accessoires à des os animés.

Lorsque vous inspectez le modèle de départ Player ou Enemy, utilisez cette structure comme votre carte : entité racine pour le gameplay, entité enfant Mesh pour les visuels et l'animation.