⚠️ Beta State

PyBevy is in an early and experimental stage. The API is incomplete, subject to breaking changes without notice, and you should expect bugs. Many features are still under development.

3D Skinned Animation

Play animations on a 3D model with a skeleton.

Introduction

Many 3D models, especially characters, have an internal skeleton (or "armature") that allows them to be animated. This is called "skinned animation". pybevy can play these animations directly from a loaded glTF file.

from pybevy.prelude import *
from pybevy.animation import AnimationPlayer, AnimationClip
from math import pi

Setup

We load a glTF file that contains a model with a skeleton and at least one animation. The Fox model is a great example.

def setup(
    commands: Commands,
    asset_server: AssetServer,
    meshes: ResMut[Assets[Mesh]],
    materials: ResMut[Assets[StandardMaterial]],
):
    # Camera and light
    commands.spawn(
        Camera3d(),
        Transform.from_xyz(100.0, 100.0, 150.0).looking_at(Vec3(0.0, 20.0, 0.0), Vec3.Y),
    )
    commands.spawn(DirectionalLight(shadows_enabled=True))
    commands.spawn(
        Mesh3d(meshes.add(Plane3d(Vec3.Y, Vec2(5000.0, 5000.0)))),
        MeshMaterial3d(materials.add(Color.srgb(0.3, 0.5, 0.3))),
    )
 
    # Spawn the Fox model
    commands.spawn(
        SceneRoot(asset_server.load_scene("models/animated/Fox.glb#Scene0"))
    )

The Animation System

When pybevy loads a scene with animations, it automatically adds an AnimationPlayer component to the appropriate entity. Our job is to find that player and tell it which animation to play.

This system waits for the AnimationPlayer to be added, then starts the first animation clip found in the model and sets it to repeat.

def start_animation_system(
    animations: Res[Assets[AnimationClip]],
    query: Query[Mut[AnimationPlayer], Added[AnimationPlayer]],
):
    for player in query:
        # The `play` method returns a handle to the playing animation.
        # We can use this handle to control the animation (e.g., pause, repeat).
        # Here, we just tell it to repeat indefinitely.
        player.play(animations.keys()[2]).repeat()

Running the App

When the app runs, the Fox model will load, and the start_animation_system will trigger, starting the "Run" animation.

@entrypoint
def main(app: App) -> App:
    return (
        app
        .add_plugins(DefaultPlugins)
        .add_systems(Startup, setup)
        .add_systems(Update, start_animation_system)
    )
 
if __name__ == "__main__":
    main().run()