Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/examples/animation/animated_mesh_events.rs
6849 views
1
//! Plays animations from a skinned glTF.
2
3
use std::{f32::consts::PI, time::Duration};
4
5
use bevy::{
6
animation::{AnimationEvent, AnimationTargetId},
7
color::palettes::css::WHITE,
8
light::CascadeShadowConfigBuilder,
9
prelude::*,
10
};
11
use rand::{Rng, SeedableRng};
12
use rand_chacha::ChaCha8Rng;
13
14
const FOX_PATH: &str = "models/animated/Fox.glb";
15
16
fn main() {
17
App::new()
18
.insert_resource(AmbientLight {
19
color: Color::WHITE,
20
brightness: 2000.,
21
..default()
22
})
23
.add_plugins(DefaultPlugins)
24
.init_resource::<ParticleAssets>()
25
.init_resource::<FoxFeetTargets>()
26
.add_systems(Startup, setup)
27
.add_systems(Update, setup_scene_once_loaded)
28
.add_systems(Update, simulate_particles)
29
.add_observer(observe_on_step)
30
.run();
31
}
32
33
#[derive(Resource)]
34
struct SeededRng(ChaCha8Rng);
35
36
#[derive(Resource)]
37
struct Animations {
38
index: AnimationNodeIndex,
39
graph_handle: Handle<AnimationGraph>,
40
}
41
42
#[derive(AnimationEvent, Reflect, Clone)]
43
struct Step;
44
45
fn observe_on_step(
46
step: On<Step>,
47
particle: Res<ParticleAssets>,
48
mut commands: Commands,
49
transforms: Query<&GlobalTransform>,
50
mut seeded_rng: ResMut<SeededRng>,
51
) -> Result {
52
let translation = transforms
53
.get(step.trigger().animation_player)?
54
.translation();
55
// Spawn a bunch of particles.
56
for _ in 0..14 {
57
let horizontal = seeded_rng.0.random::<Dir2>() * seeded_rng.0.random_range(8.0..12.0);
58
let vertical = seeded_rng.0.random_range(0.0..4.0);
59
let size = seeded_rng.0.random_range(0.2..1.0);
60
61
commands.spawn((
62
Particle {
63
lifetime_timer: Timer::from_seconds(
64
seeded_rng.0.random_range(0.2..0.6),
65
TimerMode::Once,
66
),
67
size,
68
velocity: Vec3::new(horizontal.x, vertical, horizontal.y) * 10.0,
69
},
70
Mesh3d(particle.mesh.clone()),
71
MeshMaterial3d(particle.material.clone()),
72
Transform {
73
translation,
74
scale: Vec3::splat(size),
75
..Default::default()
76
},
77
));
78
}
79
Ok(())
80
}
81
82
fn setup(
83
mut commands: Commands,
84
asset_server: Res<AssetServer>,
85
mut meshes: ResMut<Assets<Mesh>>,
86
mut materials: ResMut<Assets<StandardMaterial>>,
87
mut graphs: ResMut<Assets<AnimationGraph>>,
88
) {
89
// Build the animation graph
90
let (graph, index) = AnimationGraph::from_clip(
91
// We specifically want the "run" animation, which is the third one.
92
asset_server.load(GltfAssetLabel::Animation(2).from_asset(FOX_PATH)),
93
);
94
95
// Insert a resource with the current scene information
96
let graph_handle = graphs.add(graph);
97
commands.insert_resource(Animations {
98
index,
99
graph_handle,
100
});
101
102
// Camera
103
commands.spawn((
104
Camera3d::default(),
105
Transform::from_xyz(100.0, 100.0, 150.0).looking_at(Vec3::new(0.0, 20.0, 0.0), Vec3::Y),
106
));
107
108
// Plane
109
commands.spawn((
110
Mesh3d(meshes.add(Plane3d::default().mesh().size(500000.0, 500000.0))),
111
MeshMaterial3d(materials.add(Color::srgb(0.3, 0.5, 0.3))),
112
));
113
114
// Light
115
commands.spawn((
116
Transform::from_rotation(Quat::from_euler(EulerRot::ZYX, 0.0, 1.0, -PI / 4.)),
117
DirectionalLight {
118
shadows_enabled: true,
119
..default()
120
},
121
CascadeShadowConfigBuilder {
122
first_cascade_far_bound: 200.0,
123
maximum_distance: 400.0,
124
..default()
125
}
126
.build(),
127
));
128
129
// Fox
130
commands.spawn(SceneRoot(
131
asset_server.load(GltfAssetLabel::Scene(0).from_asset(FOX_PATH)),
132
));
133
134
// We're seeding the PRNG here to make this example deterministic for testing purposes.
135
// This isn't strictly required in practical use unless you need your app to be deterministic.
136
let seeded_rng = ChaCha8Rng::seed_from_u64(19878367467712);
137
commands.insert_resource(SeededRng(seeded_rng));
138
}
139
140
// An `AnimationPlayer` is automatically added to the scene when it's ready.
141
// When the player is added, start the animation.
142
fn setup_scene_once_loaded(
143
mut commands: Commands,
144
animations: Res<Animations>,
145
feet: Res<FoxFeetTargets>,
146
graphs: Res<Assets<AnimationGraph>>,
147
mut clips: ResMut<Assets<AnimationClip>>,
148
mut players: Query<(Entity, &mut AnimationPlayer), Added<AnimationPlayer>>,
149
) {
150
fn get_clip<'a>(
151
node: AnimationNodeIndex,
152
graph: &AnimationGraph,
153
clips: &'a mut Assets<AnimationClip>,
154
) -> &'a mut AnimationClip {
155
let node = graph.get(node).unwrap();
156
let clip = match &node.node_type {
157
AnimationNodeType::Clip(handle) => clips.get_mut(handle),
158
_ => unreachable!(),
159
};
160
clip.unwrap()
161
}
162
163
for (entity, mut player) in &mut players {
164
// Send `OnStep` events once the fox feet hits the ground in the running animation.
165
166
let graph = graphs.get(&animations.graph_handle).unwrap();
167
let running_animation = get_clip(animations.index, graph, &mut clips);
168
169
// You can determine the time an event should trigger if you know witch frame it occurs and
170
// the frame rate of the animation. Let's say we want to trigger an event at frame 15,
171
// and the animation has a frame rate of 24 fps, then time = 15 / 24 = 0.625.
172
running_animation.add_event_to_target(feet.front_left, 0.625, Step);
173
running_animation.add_event_to_target(feet.front_right, 0.5, Step);
174
running_animation.add_event_to_target(feet.back_left, 0.0, Step);
175
running_animation.add_event_to_target(feet.back_right, 0.125, Step);
176
177
// Start the animation
178
179
let mut transitions = AnimationTransitions::new();
180
181
// Make sure to start the animation via the `AnimationTransitions`
182
// component. The `AnimationTransitions` component wants to manage all
183
// the animations and will get confused if the animations are started
184
// directly via the `AnimationPlayer`.
185
transitions
186
.play(&mut player, animations.index, Duration::ZERO)
187
.repeat();
188
189
commands
190
.entity(entity)
191
.insert(AnimationGraphHandle(animations.graph_handle.clone()))
192
.insert(transitions);
193
}
194
}
195
196
fn simulate_particles(
197
mut commands: Commands,
198
mut query: Query<(Entity, &mut Transform, &mut Particle)>,
199
time: Res<Time>,
200
) {
201
for (entity, mut transform, mut particle) in &mut query {
202
if particle.lifetime_timer.tick(time.delta()).just_finished() {
203
commands.entity(entity).despawn();
204
return;
205
}
206
207
transform.translation += particle.velocity * time.delta_secs();
208
transform.scale = Vec3::splat(particle.size.lerp(0.0, particle.lifetime_timer.fraction()));
209
particle
210
.velocity
211
.smooth_nudge(&Vec3::ZERO, 4.0, time.delta_secs());
212
}
213
}
214
215
#[derive(Component)]
216
struct Particle {
217
lifetime_timer: Timer,
218
size: f32,
219
velocity: Vec3,
220
}
221
222
#[derive(Resource)]
223
struct ParticleAssets {
224
mesh: Handle<Mesh>,
225
material: Handle<StandardMaterial>,
226
}
227
228
impl FromWorld for ParticleAssets {
229
fn from_world(world: &mut World) -> Self {
230
Self {
231
mesh: world.add_asset::<Mesh>(Sphere::new(10.0)),
232
material: world.add_asset::<StandardMaterial>(StandardMaterial {
233
base_color: WHITE.into(),
234
..Default::default()
235
}),
236
}
237
}
238
}
239
240
/// Stores the `AnimationTargetId`s of the fox's feet
241
#[derive(Resource)]
242
struct FoxFeetTargets {
243
front_right: AnimationTargetId,
244
front_left: AnimationTargetId,
245
back_left: AnimationTargetId,
246
back_right: AnimationTargetId,
247
}
248
249
impl Default for FoxFeetTargets {
250
fn default() -> Self {
251
let hip_node = ["root", "_rootJoint", "b_Root_00", "b_Hip_01"];
252
let front_left_foot = hip_node.iter().chain(
253
[
254
"b_Spine01_02",
255
"b_Spine02_03",
256
"b_LeftUpperArm_09",
257
"b_LeftForeArm_010",
258
"b_LeftHand_011",
259
]
260
.iter(),
261
);
262
let front_right_foot = hip_node.iter().chain(
263
[
264
"b_Spine01_02",
265
"b_Spine02_03",
266
"b_RightUpperArm_06",
267
"b_RightForeArm_07",
268
"b_RightHand_08",
269
]
270
.iter(),
271
);
272
let back_left_foot = hip_node.iter().chain(
273
[
274
"b_LeftLeg01_015",
275
"b_LeftLeg02_016",
276
"b_LeftFoot01_017",
277
"b_LeftFoot02_018",
278
]
279
.iter(),
280
);
281
let back_right_foot = hip_node.iter().chain(
282
[
283
"b_RightLeg01_019",
284
"b_RightLeg02_020",
285
"b_RightFoot01_021",
286
"b_RightFoot02_022",
287
]
288
.iter(),
289
);
290
Self {
291
front_left: AnimationTargetId::from_iter(front_left_foot),
292
front_right: AnimationTargetId::from_iter(front_right_foot),
293
back_left: AnimationTargetId::from_iter(back_left_foot),
294
back_right: AnimationTargetId::from_iter(back_right_foot),
295
}
296
}
297
}
298
299