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