Présentation

Dans Bevy, un modèle .glb n’est pas instancié comme une entité unique mais comme une hiérarchie d’entités. Cette structure complique l’accès direct à l’objet à animer. Le moteur résout ce problème en injectant automatiquement le composant AnimationPlayer quelque part dans la hiérarchie, ce qui constitue le point d’entrée pour toute commande d’animation.

Mécanisme d’AnimationPlayer

L'AnimationPlayer est un composant qui expose les méthodes play, pause et current_animation. Contrairement à une API intuitive du type my_model.play_animation(my_animation), le self du player ne référence pas directement le maillage mais l’entité qui porte le composant. Le player recherche alors, à l’exécution, le AnimationGraph attaché à la même entité et utilise l’NodeIndex fourni pour sélectionner la séquence d’animation souhaitée.

my_animation_player.play(my_node_index);

Cette indirection permet de décorréler le stockage des clips (dans le graph) de leur déclenchement, mais impose que le développeur identifie correctement le NodeIndex correspondant à la piste voulue.

Gestion de l’AnimationGraph

Un AnimationGraph représente un conteneur capable de stocker plusieurs clips et de les combiner. Dans l’exemple officiel, le graph est créé à partir d’un clip extrait du fichier .glb :

let (graph, index) = AnimationGraph::from_clip(
    asset_server.load(GltfAssetLabel::Animation(2).from_asset(GLTF_PATH)),
);

Le paramètre entier de GltfAssetLabel::Animation indique l’index du clip dans le fichier. L’appel from_clip renvoie à la fois le graph et l’NodeIndex du clip. Le graph est ensuite ajouté au store d’actifs :

let graph_handle = graphs.add(graph);

Le handle permet de référencer le même graph depuis plusieurs entités, évitant la duplication de données et garantissant la cohérence des animations partagées.

Intégration dans le pipeline Bevy

Le maillage et le graph sont chargés séparément :

let mesh_scene = WorldAssetRoot(
    asset_server.load(GltfAssetLabel::Scene(0).from_asset(GLTF_PATH))
);

Le mesh_scene peut être spawné immédiatement. L’exemple encapsule le handle du graph et l’index dans un composant personnalisé AnimationToPlay, puis crée l’entité :

commands.spawn((animation_to_play, mesh_scene))
    .observe(play_animation_when_ready);

L’observateur play_animation_when_ready reçoit l’entité racine du modèle une fois que la scène est prête, récupère l’AnimationPlayer interne et invoque play avec le NodeIndex. Cette approche garantit que le player est bien attaché à la bonne sous‑entité, même si la hiérarchie du .glb varie d’un asset à l’autre.

Les limites du système résident dans la nécessité de connaître l’index du clip à l’avance et dans le fait que le composant AnimationPlayer n’est pas exposé directement lors du spawn. Le développeur doit donc soit parcourir la hiérarchie pour le localiser, soit s’appuyer sur des conventions de nommage ou des systèmes d’observation comme montré. Malgré ces contraintes, le modèle de Bevy sépare clairement le stockage des données d’animation (graph) du déclencheur (player), ce qui facilite le partage de clips entre plusieurs modèles et la mise en place de systèmes d’animation complexes à l’échelle du moteur.