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