docs/bevy/examples/animation/animated_mesh_control.rs 7.8 K · 247 lines · raw · history

1 //! Plays animations from a skinned glTF.
2
3 use std::{f32::consts::PI, time::Duration};
4
5 use bevy::{
6 animation::RepeatAnimation, light::CascadeShadowConfigBuilder, prelude::*,
7 world_serialization::WorldInstanceReady,
8 };
9
10 const FOX_PATH: &str = "models/animated/Fox.glb";
11
12 fn 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)]
33 struct Animations {
34 animations: Vec<AnimationNodeIndex>,
35 graph_handle: Handle<AnimationGraph>,
36 }
37
38 #[derive(Resource)]
39 struct Fox(Handle<Gltf>);
40
41 fn 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
97 fn 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`.
142 fn 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
165 fn 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 }