Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/examples/tools/scene_viewer/animation_plugin.rs
9353 views
1
//! Control animations of entities in the loaded scene.
2
use std::collections::HashMap;
3
4
use bevy::{animation::AnimationTargetId, ecs::entity::EntityHashMap, gltf::Gltf, prelude::*};
5
6
use crate::scene_viewer_plugin::SceneHandle;
7
8
/// Controls animation clips for a unique entity.
9
#[derive(Component)]
10
struct Clips {
11
nodes: Vec<AnimationNodeIndex>,
12
current: usize,
13
}
14
15
impl Clips {
16
fn new(clips: Vec<AnimationNodeIndex>) -> Self {
17
Clips {
18
nodes: clips,
19
current: 0,
20
}
21
}
22
/// # Panics
23
///
24
/// When no clips are present.
25
fn current(&self) -> AnimationNodeIndex {
26
self.nodes[self.current]
27
}
28
fn advance_to_next(&mut self) {
29
self.current = (self.current + 1) % self.nodes.len();
30
}
31
}
32
33
/// Automatically assign [`AnimationClip`]s to [`AnimationPlayer`] and play
34
/// them, if the clips refer to descendants of the animation player (which is
35
/// the common case).
36
fn assign_clips(
37
mut players: Query<&mut AnimationPlayer>,
38
targets: Query<(&AnimationTargetId, Entity)>,
39
children: Query<&ChildOf>,
40
scene_handle: Res<SceneHandle>,
41
clips: Res<Assets<AnimationClip>>,
42
gltf_assets: Res<Assets<Gltf>>,
43
assets: Res<AssetServer>,
44
mut graphs: ResMut<Assets<AnimationGraph>>,
45
mut commands: Commands,
46
mut setup: Local<bool>,
47
) {
48
if scene_handle.is_loaded && !*setup {
49
*setup = true;
50
} else {
51
return;
52
}
53
54
let gltf = gltf_assets.get(&scene_handle.gltf_handle).unwrap();
55
let animations = &gltf.animations;
56
if animations.is_empty() {
57
return;
58
}
59
60
let count = animations.len();
61
let plural = if count == 1 { "" } else { "s" };
62
info!("Found {} animation{plural}", animations.len());
63
let names: Vec<_> = gltf.named_animations.keys().collect();
64
info!("Animation names: {names:?}");
65
66
// Map animation target IDs to entities.
67
let animation_target_id_to_entity: HashMap<_, _> = targets.iter().collect();
68
69
// Build up a list of all animation clips that belong to each player. A clip
70
// is considered to belong to an animation player if all targets of the clip
71
// refer to entities whose nearest ancestor player is that animation player.
72
73
let mut player_to_graph: EntityHashMap<(AnimationGraph, Vec<AnimationNodeIndex>)> =
74
EntityHashMap::default();
75
76
for (clip_id, clip) in clips.iter() {
77
let mut ancestor_player = None;
78
for target_id in clip.curves().keys() {
79
// If the animation clip refers to entities that aren't present in
80
// the scene, bail.
81
let Some(&target) = animation_target_id_to_entity.get(target_id) else {
82
continue;
83
};
84
85
// Find the nearest ancestor animation player.
86
let mut current = Some(target);
87
while let Some(entity) = current {
88
if players.contains(entity) {
89
match ancestor_player {
90
None => {
91
// If we haven't found a player yet, record the one
92
// we found.
93
ancestor_player = Some(entity);
94
}
95
Some(ancestor) => {
96
// If we have found a player, then make sure it's
97
// the same player we located before.
98
if ancestor != entity {
99
// It's a different player. Bail.
100
ancestor_player = None;
101
break;
102
}
103
}
104
}
105
}
106
107
// Go to the next parent.
108
current = children.get(entity).ok().map(ChildOf::parent);
109
}
110
}
111
112
let Some(ancestor_player) = ancestor_player else {
113
warn!(
114
"Unexpected animation hierarchy for animation clip {}; ignoring.",
115
clip_id
116
);
117
continue;
118
};
119
120
let Some(clip_handle) = assets.get_id_handle(clip_id) else {
121
warn!("Clip {} wasn't loaded.", clip_id);
122
continue;
123
};
124
125
let &mut (ref mut graph, ref mut clip_indices) =
126
player_to_graph.entry(ancestor_player).or_default();
127
let node_index = graph.add_clip(clip_handle, 1.0, graph.root);
128
clip_indices.push(node_index);
129
}
130
131
// Now that we've built up a list of all clips that belong to each player,
132
// package them up into a `Clips` component, play the first such animation,
133
// and add that component to the player.
134
for (player_entity, (graph, clips)) in player_to_graph {
135
let Ok(mut player) = players.get_mut(player_entity) else {
136
warn!("Animation targets referenced a nonexistent player. This shouldn't happen.");
137
continue;
138
};
139
let graph = graphs.add(graph);
140
let animations = Clips::new(clips);
141
player.play(animations.current()).repeat();
142
commands
143
.entity(player_entity)
144
.insert(animations)
145
.insert(AnimationGraphHandle(graph));
146
}
147
}
148
149
fn handle_inputs(
150
keyboard_input: Res<ButtonInput<KeyCode>>,
151
mut animation_player: Query<(&mut AnimationPlayer, &mut Clips, Entity, Option<&Name>)>,
152
) {
153
for (mut player, mut clips, entity, name) in &mut animation_player {
154
let display_entity_name = match name {
155
Some(name) => name.to_string(),
156
None => format!("entity {entity}"),
157
};
158
if keyboard_input.just_pressed(KeyCode::Space) {
159
if player.all_paused() {
160
info!("resuming animations for {display_entity_name}");
161
player.resume_all();
162
} else {
163
info!("pausing animation for {display_entity_name}");
164
player.pause_all();
165
}
166
}
167
if clips.nodes.len() <= 1 {
168
continue;
169
}
170
171
if keyboard_input.just_pressed(KeyCode::Enter) {
172
info!("switching to new animation for {display_entity_name}");
173
174
let paused = player.all_paused();
175
player.stop_all();
176
177
clips.advance_to_next();
178
let current_clip = clips.current();
179
player.play(current_clip).repeat();
180
if paused {
181
player.pause_all();
182
}
183
}
184
}
185
}
186
187
pub struct AnimationManipulationPlugin;
188
impl Plugin for AnimationManipulationPlugin {
189
fn build(&self, app: &mut App) {
190
app.add_systems(Update, (handle_inputs, assign_clips));
191
}
192
}
193
194