Skip to main content

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