animated_mesh/
animated_mesh.rs

1//! Plays an animation on a skinned glTF model of a fox.
2
3use std::f32::consts::PI;
4
5use bevy::{light::CascadeShadowConfigBuilder, prelude::*, scene::SceneInstanceReady};
6
7// An example asset that contains a mesh and animation.
8const GLTF_PATH: &str = "models/animated/Fox.glb";
9
10fn main() {
11    App::new()
12        .insert_resource(AmbientLight {
13            color: Color::WHITE,
14            brightness: 2000.,
15            ..default()
16        })
17        .add_plugins(DefaultPlugins)
18        .add_systems(Startup, setup_mesh_and_animation)
19        .add_systems(Startup, setup_camera_and_environment)
20        .run();
21}
22
23// A component that stores a reference to an animation we want to play. This is
24// created when we start loading the mesh (see `setup_mesh_and_animation`) and
25// read when the mesh has spawned (see `play_animation_once_loaded`).
26#[derive(Component)]
27struct AnimationToPlay {
28    graph_handle: Handle<AnimationGraph>,
29    index: AnimationNodeIndex,
30}
31
32fn setup_mesh_and_animation(
33    mut commands: Commands,
34    asset_server: Res<AssetServer>,
35    mut graphs: ResMut<Assets<AnimationGraph>>,
36) {
37    // Create an animation graph containing a single animation. We want the "run"
38    // animation from our example asset, which has an index of two.
39    let (graph, index) = AnimationGraph::from_clip(
40        asset_server.load(GltfAssetLabel::Animation(2).from_asset(GLTF_PATH)),
41    );
42
43    // Store the animation graph as an asset.
44    let graph_handle = graphs.add(graph);
45
46    // Create a component that stores a reference to our animation.
47    let animation_to_play = AnimationToPlay {
48        graph_handle,
49        index,
50    };
51
52    // Start loading the asset as a scene and store a reference to it in a
53    // SceneRoot component. This component will automatically spawn a scene
54    // containing our mesh once it has loaded.
55    let mesh_scene = SceneRoot(asset_server.load(GltfAssetLabel::Scene(0).from_asset(GLTF_PATH)));
56
57    // Spawn an entity with our components, and connect it to an observer that
58    // will trigger when the scene is loaded and spawned.
59    commands
60        .spawn((animation_to_play, mesh_scene))
61        .observe(play_animation_when_ready);
62}
63
64fn play_animation_when_ready(
65    scene_ready: On<SceneInstanceReady>,
66    mut commands: Commands,
67    children: Query<&Children>,
68    animations_to_play: Query<&AnimationToPlay>,
69    mut players: Query<&mut AnimationPlayer>,
70) {
71    // The entity we spawned in `setup_mesh_and_animation` is the trigger's target.
72    // Start by finding the AnimationToPlay component we added to that entity.
73    if let Ok(animation_to_play) = animations_to_play.get(scene_ready.entity) {
74        // The SceneRoot component will have spawned the scene as a hierarchy
75        // of entities parented to our entity. Since the asset contained a skinned
76        // mesh and animations, it will also have spawned an animation player
77        // component. Search our entity's descendants to find the animation player.
78        for child in children.iter_descendants(scene_ready.entity) {
79            if let Ok(mut player) = players.get_mut(child) {
80                // Tell the animation player to start the animation and keep
81                // repeating it.
82                //
83                // If you want to try stopping and switching animations, see the
84                // `animated_mesh_control.rs` example.
85                player.play(animation_to_play.index).repeat();
86
87                // Add the animation graph. This only needs to be done once to
88                // connect the animation player to the mesh.
89                commands
90                    .entity(child)
91                    .insert(AnimationGraphHandle(animation_to_play.graph_handle.clone()));
92            }
93        }
94    }
95}
96
97// Spawn a camera and a simple environment with a ground plane and light.
98fn setup_camera_and_environment(
99    mut commands: Commands,
100    mut meshes: ResMut<Assets<Mesh>>,
101    mut materials: ResMut<Assets<StandardMaterial>>,
102) {
103    // Camera
104    commands.spawn((
105        Camera3d::default(),
106        Transform::from_xyz(100.0, 100.0, 150.0).looking_at(Vec3::new(0.0, 20.0, 0.0), Vec3::Y),
107    ));
108
109    // Plane
110    commands.spawn((
111        Mesh3d(meshes.add(Plane3d::default().mesh().size(500000.0, 500000.0))),
112        MeshMaterial3d(materials.add(Color::srgb(0.3, 0.5, 0.3))),
113    ));
114
115    // Light
116    commands.spawn((
117        Transform::from_rotation(Quat::from_euler(EulerRot::ZYX, 0.0, 1.0, -PI / 4.)),
118        DirectionalLight {
119            shadows_enabled: true,
120            ..default()
121        },
122        CascadeShadowConfigBuilder {
123            first_cascade_far_bound: 200.0,
124            maximum_distance: 400.0,
125            ..default()
126        }
127        .build(),
128    ));
129}