Path: blob/main/examples/tools/scene_viewer/animation_plugin.rs
6598 views
//! Control animations of entities in the loaded scene.1use std::collections::HashMap;23use bevy::{animation::AnimationTarget, 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<(Entity, &AnimationTarget)>,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<_, _> = targets67.iter()68.map(|(entity, target)| (target.id, entity))69.collect();7071// Build up a list of all animation clips that belong to each player. A clip72// is considered to belong to an animation player if all targets of the clip73// refer to entities whose nearest ancestor player is that animation player.7475let mut player_to_graph: EntityHashMap<(AnimationGraph, Vec<AnimationNodeIndex>)> =76EntityHashMap::default();7778for (clip_id, clip) in clips.iter() {79let mut ancestor_player = None;80for target_id in clip.curves().keys() {81// If the animation clip refers to entities that aren't present in82// the scene, bail.83let Some(&target) = animation_target_id_to_entity.get(target_id) else {84continue;85};8687// Find the nearest ancestor animation player.88let mut current = Some(target);89while let Some(entity) = current {90if players.contains(entity) {91match ancestor_player {92None => {93// If we haven't found a player yet, record the one94// we found.95ancestor_player = Some(entity);96}97Some(ancestor) => {98// If we have found a player, then make sure it's99// the same player we located before.100if ancestor != entity {101// It's a different player. Bail.102ancestor_player = None;103break;104}105}106}107}108109// Go to the next parent.110current = children.get(entity).ok().map(ChildOf::parent);111}112}113114let Some(ancestor_player) = ancestor_player else {115warn!(116"Unexpected animation hierarchy for animation clip {}; ignoring.",117clip_id118);119continue;120};121122let Some(clip_handle) = assets.get_id_handle(clip_id) else {123warn!("Clip {} wasn't loaded.", clip_id);124continue;125};126127let &mut (ref mut graph, ref mut clip_indices) =128player_to_graph.entry(ancestor_player).or_default();129let node_index = graph.add_clip(clip_handle, 1.0, graph.root);130clip_indices.push(node_index);131}132133// Now that we've built up a list of all clips that belong to each player,134// package them up into a `Clips` component, play the first such animation,135// and add that component to the player.136for (player_entity, (graph, clips)) in player_to_graph {137let Ok(mut player) = players.get_mut(player_entity) else {138warn!("Animation targets referenced a nonexistent player. This shouldn't happen.");139continue;140};141let graph = graphs.add(graph);142let animations = Clips::new(clips);143player.play(animations.current()).repeat();144commands145.entity(player_entity)146.insert(animations)147.insert(AnimationGraphHandle(graph));148}149}150151fn handle_inputs(152keyboard_input: Res<ButtonInput<KeyCode>>,153mut animation_player: Query<(&mut AnimationPlayer, &mut Clips, Entity, Option<&Name>)>,154) {155for (mut player, mut clips, entity, name) in &mut animation_player {156let display_entity_name = match name {157Some(name) => name.to_string(),158None => format!("entity {entity}"),159};160if keyboard_input.just_pressed(KeyCode::Space) {161if player.all_paused() {162info!("resuming animations for {display_entity_name}");163player.resume_all();164} else {165info!("pausing animation for {display_entity_name}");166player.pause_all();167}168}169if clips.nodes.len() <= 1 {170continue;171}172173if keyboard_input.just_pressed(KeyCode::Enter) {174info!("switching to new animation for {display_entity_name}");175176let resume = !player.all_paused();177// set the current animation to its start and pause it to reset to its starting state178player.rewind_all().pause_all();179180clips.advance_to_next();181let current_clip = clips.current();182player.play(current_clip).repeat();183if resume {184player.resume_all();185}186}187}188}189190pub struct AnimationManipulationPlugin;191impl Plugin for AnimationManipulationPlugin {192fn build(&self, app: &mut App) {193app.add_systems(Update, (handle_inputs, assign_clips));194}195}196197198