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