Skip to main content

animated_mesh_control/
animated_mesh_control.rs

1//! Plays animations from a skinned glTF.
2
3use std::{f32::consts::PI, time::Duration};
4
5use bevy::{
6    animation::RepeatAnimation, light::CascadeShadowConfigBuilder, prelude::*,
7    world_serialization::WorldInstanceReady,
8};
9
10const FOX_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)
21        .add_systems(
22            Update,
23            spawn_fox_asset_when_ready.run_if(not(resource_exists::<Animations>)),
24        )
25        .add_systems(
26            Update,
27            keyboard_control.run_if(resource_exists::<Animations>),
28        )
29        .run();
30}
31
32#[derive(Resource)]
33struct Animations {
34    animations: Vec<AnimationNodeIndex>,
35    graph_handle: Handle<AnimationGraph>,
36}
37
38#[derive(Resource)]
39struct Fox(Handle<Gltf>);
40
41fn setup(
42    mut commands: Commands,
43    asset_server: Res<AssetServer>,
44    mut meshes: ResMut<Assets<Mesh>>,
45    mut materials: ResMut<Assets<StandardMaterial>>,
46) {
47    // trigger a load for the glTF asset
48    // and store the handle in a Resource
49    commands.insert_resource(Fox(asset_server.load(FOX_PATH)));
50
51    // Camera
52    commands.spawn((
53        Camera3d::default(),
54        Transform::from_xyz(100.0, 100.0, 150.0).looking_at(Vec3::new(0.0, 20.0, 0.0), Vec3::Y),
55    ));
56
57    // Plane
58    commands.spawn((
59        Mesh3d(meshes.add(Plane3d::default().mesh().size(500000.0, 500000.0))),
60        MeshMaterial3d(materials.add(Color::srgb(0.3, 0.5, 0.3))),
61    ));
62
63    // Light
64    commands.spawn((
65        Transform::from_rotation(Quat::from_euler(EulerRot::ZYX, 0.0, 1.0, -PI / 4.)),
66        DirectionalLight {
67            shadow_maps_enabled: true,
68            ..default()
69        },
70        CascadeShadowConfigBuilder {
71            first_cascade_far_bound: 200.0,
72            maximum_distance: 400.0,
73            ..default()
74        }
75        .build(),
76    ));
77
78    // Instructions
79    commands.spawn((
80        Text::new(concat!(
81            "space: play / pause\n",
82            "up / down: playback speed\n",
83            "left / right: seek\n",
84            "1-3: play N times\n",
85            "L: loop forever\n",
86            "return: change animation\n",
87        )),
88        Node {
89            position_type: PositionType::Absolute,
90            top: px(12),
91            left: px(12),
92            ..default()
93        },
94    ));
95}
96
97fn spawn_fox_asset_when_ready(
98    mut commands: Commands,
99    fox_handle: Res<Fox>,
100    asset_server: Res<AssetServer>,
101    gltfs: Res<Assets<Gltf>>,
102    mut graphs: ResMut<Assets<AnimationGraph>>,
103) {
104    if !asset_server.is_loaded_with_dependencies(&fox_handle.0) {
105        // fox is not loaded yet
106        return;
107    }
108
109    let fox = gltfs
110        .get(&fox_handle.0)
111        .expect("a loaded asset should exist in the glTF assets collection");
112
113    // Build the animation graph
114    let (graph, node_indices) = AnimationGraph::from_clips([
115        fox.named_animations["Run"].clone(),
116        fox.named_animations["Walk"].clone(),
117        fox.named_animations["Survey"].clone(),
118    ]);
119
120    // Keep our animation graph in a Resource so that it can be inserted onto
121    // the correct entity once the scene actually loads.
122    let graph_handle = graphs.add(graph);
123    commands.insert_resource(Animations {
124        animations: node_indices,
125        graph_handle,
126    });
127
128    // Fox
129    commands
130        .spawn(WorldAssetRoot(
131            fox.default_scene
132                .clone()
133                .expect("a default scene exists in this file"),
134        ))
135        .observe(setup_scene);
136}
137
138// An `AnimationPlayer` is automatically added to the scene when loading the
139// glTF file, so it already exists on the appropriate entity when
140// `WorldInstanceReady` fires. There will be only one player in this example,
141// so we use `Single`.
142fn setup_scene(
143    _ready: On<WorldInstanceReady>,
144    mut commands: Commands,
145    animations: Res<Animations>,
146    player: Single<(Entity, &mut AnimationPlayer)>,
147) {
148    let (entity, mut player) = player.into_inner();
149    let mut transitions = AnimationTransitions::new();
150
151    // Make sure to start the animation via the `AnimationTransitions`
152    // component. The `AnimationTransitions` component wants to manage all
153    // the animations and will get confused if the animations are started
154    // directly via the `AnimationPlayer`.
155    transitions
156        .play(&mut player, animations.animations[0], Duration::ZERO)
157        .repeat();
158
159    commands
160        .entity(entity)
161        .insert(AnimationGraphHandle(animations.graph_handle.clone()))
162        .insert(transitions);
163}
164
165fn keyboard_control(
166    keyboard_input: Res<ButtonInput<KeyCode>>,
167    mut animation_players: Query<(&mut AnimationPlayer, &mut AnimationTransitions)>,
168    animations: Res<Animations>,
169    mut current_animation: Local<usize>,
170) {
171    for (mut player, mut transitions) in &mut animation_players {
172        let Some((&playing_animation_index, _)) = player.playing_animations().next() else {
173            continue;
174        };
175
176        if keyboard_input.just_pressed(KeyCode::Space) {
177            let playing_animation = player.animation_mut(playing_animation_index).unwrap();
178            if playing_animation.is_paused() {
179                playing_animation.resume();
180            } else {
181                playing_animation.pause();
182            }
183        }
184
185        if keyboard_input.just_pressed(KeyCode::ArrowUp) {
186            let playing_animation = player.animation_mut(playing_animation_index).unwrap();
187            let speed = playing_animation.speed();
188            playing_animation.set_speed(speed * 1.2);
189        }
190
191        if keyboard_input.just_pressed(KeyCode::ArrowDown) {
192            let playing_animation = player.animation_mut(playing_animation_index).unwrap();
193            let speed = playing_animation.speed();
194            playing_animation.set_speed(speed * 0.8);
195        }
196
197        if keyboard_input.just_pressed(KeyCode::ArrowLeft) {
198            let playing_animation = player.animation_mut(playing_animation_index).unwrap();
199            let elapsed = playing_animation.seek_time();
200            playing_animation.seek_to(elapsed - 0.1);
201        }
202
203        if keyboard_input.just_pressed(KeyCode::ArrowRight) {
204            let playing_animation = player.animation_mut(playing_animation_index).unwrap();
205            let elapsed = playing_animation.seek_time();
206            playing_animation.seek_to(elapsed + 0.1);
207        }
208
209        if keyboard_input.just_pressed(KeyCode::Enter) {
210            *current_animation = (*current_animation + 1) % animations.animations.len();
211
212            transitions
213                .play(
214                    &mut player,
215                    animations.animations[*current_animation],
216                    Duration::from_millis(250),
217                )
218                .repeat();
219        }
220
221        if keyboard_input.just_pressed(KeyCode::Digit1) {
222            let playing_animation = player.animation_mut(playing_animation_index).unwrap();
223            playing_animation
224                .set_repeat(RepeatAnimation::Count(1))
225                .replay();
226        }
227
228        if keyboard_input.just_pressed(KeyCode::Digit2) {
229            let playing_animation = player.animation_mut(playing_animation_index).unwrap();
230            playing_animation
231                .set_repeat(RepeatAnimation::Count(2))
232                .replay();
233        }
234
235        if keyboard_input.just_pressed(KeyCode::Digit3) {
236            let playing_animation = player.animation_mut(playing_animation_index).unwrap();
237            playing_animation
238                .set_repeat(RepeatAnimation::Count(3))
239                .replay();
240        }
241
242        if keyboard_input.just_pressed(KeyCode::KeyL) {
243            let playing_animation = player.animation_mut(playing_animation_index).unwrap();
244            playing_animation.set_repeat(RepeatAnimation::Forever);
245        }
246    }
247}