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 piSetup
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()