From 6f68776eaca0ea7c5d690ca3c95701cd5b3c334b Mon Sep 17 00:00:00 2001 From: Rob Parrett Date: Mon, 6 Jan 2025 11:32:32 -0800 Subject: [PATCH] Split up `animated_fox` example (#17191) # Objective Our `animated_fox` example used to be a bare-bones example of how to spawn an animated gltf and play a single animation. I think that's a valuable example, and the current `animated_fox` example is doing way too much. Users who are trying to understand how our animation system are presented with an enormous amount of information that may not be immediately relevant. Over the past few releases, I've been migrating a simple app of mine where the only animation I need is a single gltf that starts playing a single animation when it is loaded. It has been a slight struggle to wade through changes to the animation system to figure out the minimal amount of things required to accomplish this. Somewhat motivated by this [recent reddit thread](https://www.reddit.com/r/rust/comments/1ht93vl/comment/m5c0nc9/?utm_source=share&utm_medium=mweb3x&utm_name=mweb3xcss&utm_term=1) where Bevy and animation got a mention. ## Solution - Split `animated_fox` into three separate examples - `animated_fox` - Loads and immediately plays a single animation - `animated_fox_control` - Shows how to control animations - `animated_fox_events` - Shows fancy particles when the fox's feet hit the ground - Some minor drive-by tidying of these examples I have created this PR after playing around with the idea and liking how it turned out, but the duplication isn't totally ideal and there's some slight overlap with other examples and inconsistencies: - `animation_events` is simplified and not specific to "loaded animated scenes" and seems valuable on its own - `animation_graph` also uses a fox I am happy to close this if there's no consensus that it's a good idea / step forward for these examples. ## Testing `cargo run --example animated_fox` `cargo run --example animated_fox_control` `cargo run --example animated_fox_events` --- Cargo.toml | 22 ++ examples/README.md | 2 + examples/animation/animated_fox.rs | 331 ++------------------- examples/animation/animated_fox_control.rs | 210 +++++++++++++ examples/animation/animated_fox_events.rs | 292 ++++++++++++++++++ 5 files changed, 543 insertions(+), 314 deletions(-) create mode 100644 examples/animation/animated_fox_control.rs create mode 100644 examples/animation/animated_fox_events.rs diff --git a/Cargo.toml b/Cargo.toml index 6613ec3c4e344..6be54d00eb451 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1291,6 +1291,28 @@ description = "Plays an animation from a skinned glTF" category = "Animation" wasm = true +[[example]] +name = "animated_fox_control" +path = "examples/animation/animated_fox_control.rs" +doc-scrape-examples = true + +[package.metadata.example.animated_fox_control] +name = "Animated Fox Control" +description = "Plays an animation from a skinned glTF with keyboard controls" +category = "Animation" +wasm = true + +[[example]] +name = "animated_fox_events" +path = "examples/animation/animated_fox_events.rs" +doc-scrape-examples = true + +[package.metadata.example.animated_fox_events] +name = "Animated Fox Events" +description = "Plays an animation from a skinned glTF with events" +category = "Animation" +wasm = true + [[example]] name = "animation_graph" path = "examples/animation/animation_graph.rs" diff --git a/examples/README.md b/examples/README.md index 2ff4e504e1672..5b1594b8e016f 100644 --- a/examples/README.md +++ b/examples/README.md @@ -195,6 +195,8 @@ Example | Description Example | Description --- | --- [Animated Fox](../examples/animation/animated_fox.rs) | Plays an animation from a skinned glTF +[Animated Fox Control](../examples/animation/animated_fox_control.rs) | Plays an animation from a skinned glTF with keyboard controls +[Animated Fox Events](../examples/animation/animated_fox_events.rs) | Plays an animation from a skinned glTF with events [Animated Transform](../examples/animation/animated_transform.rs) | Create and play an animation defined by code that operates on the `Transform` component [Animated UI](../examples/animation/animated_ui.rs) | Shows how to use animation clips to animate UI properties [Animation Events](../examples/animation/animation_events.rs) | Demonstrate how to use animation events diff --git a/examples/animation/animated_fox.rs b/examples/animation/animated_fox.rs index 912b2d082b0de..12e8ecc75c263 100644 --- a/examples/animation/animated_fox.rs +++ b/examples/animation/animated_fox.rs @@ -2,14 +2,7 @@ use std::{f32::consts::PI, time::Duration}; -use bevy::{ - animation::{AnimationTargetId, RepeatAnimation}, - color::palettes::css::WHITE, - pbr::CascadeShadowConfigBuilder, - prelude::*, -}; -use rand::{Rng, SeedableRng}; -use rand_chacha::ChaCha8Rng; +use bevy::{pbr::CascadeShadowConfigBuilder, prelude::*}; const FOX_PATH: &str = "models/animated/Fox.glb"; @@ -21,49 +14,15 @@ fn main() { ..default() }) .add_plugins(DefaultPlugins) - .init_resource::() - .init_resource::() .add_systems(Startup, setup) .add_systems(Update, setup_scene_once_loaded) - .add_systems(Update, (keyboard_animation_control, simulate_particles)) - .add_observer(observe_on_step) .run(); } -#[derive(Resource)] -struct SeededRng(ChaCha8Rng); - #[derive(Resource)] struct Animations { - animations: Vec, - graph: Handle, -} - -#[derive(Event, Reflect, Clone)] -struct OnStep; - -fn observe_on_step( - trigger: Trigger, - particle: Res, - mut commands: Commands, - transforms: Query<&GlobalTransform>, - mut seeded_rng: ResMut, -) { - let translation = transforms.get(trigger.target()).unwrap().translation(); - // Spawn a bunch of particles. - for _ in 0..14 { - let horizontal = seeded_rng.0.gen::() * seeded_rng.0.gen_range(8.0..12.0); - let vertical = seeded_rng.0.gen_range(0.0..4.0); - let size = seeded_rng.0.gen_range(0.2..1.0); - commands.queue(spawn_particle( - particle.mesh.clone(), - particle.material.clone(), - translation.reject_from_normalized(Vec3::Y), - seeded_rng.0.gen_range(0.2..0.6), - size, - Vec3::new(horizontal.x, vertical, horizontal.y) * 10.0, - )); - } + graph_handle: Handle, + index: AnimationNodeIndex, } fn setup( @@ -74,17 +33,17 @@ fn setup( mut graphs: ResMut>, ) { // Build the animation graph - let (graph, node_indices) = AnimationGraph::from_clips([ - asset_server.load(GltfAssetLabel::Animation(2).from_asset(FOX_PATH)), - asset_server.load(GltfAssetLabel::Animation(1).from_asset(FOX_PATH)), + let (graph, index) = AnimationGraph::from_clip( + // We specifically want the "walk" animation, which is the first one. asset_server.load(GltfAssetLabel::Animation(0).from_asset(FOX_PATH)), - ]); + ); - // Insert a resource with the current scene information + // Keep our animation graph in a Resource so that it can be inserted onto + // the correct entity once the scene actually loads. let graph_handle = graphs.add(graph); commands.insert_resource(Animations { - animations: node_indices, - graph: graph_handle, + graph_handle: graph_handle.clone(), + index, }); // Camera @@ -115,60 +74,19 @@ fn setup( )); // Fox - commands.spawn(SceneRoot( - asset_server.load(GltfAssetLabel::Scene(0).from_asset(FOX_PATH)), + commands.spawn(( + SceneRoot(asset_server.load(GltfAssetLabel::Scene(0).from_asset(FOX_PATH))), + AnimationGraphHandle(graph_handle), )); - - println!("Animation controls:"); - println!(" - spacebar: play / pause"); - println!(" - arrow up / down: speed up / slow down animation playback"); - println!(" - arrow left / right: seek backward / forward"); - println!(" - digit 1 / 3 / 5: play the animation times"); - println!(" - L: loop the animation forever"); - println!(" - return: change animation"); - - // We're seeding the PRNG here to make this example deterministic for testing purposes. - // This isn't strictly required in practical use unless you need your app to be deterministic. - let seeded_rng = ChaCha8Rng::seed_from_u64(19878367467712); - commands.insert_resource(SeededRng(seeded_rng)); } -// An `AnimationPlayer` is automatically added to the scene when it's ready. -// When the player is added, start the animation. +// Once the scene is loaded, start the animation fn setup_scene_once_loaded( mut commands: Commands, animations: Res, - feet: Res, - graphs: Res>, - mut clips: ResMut>, mut players: Query<(Entity, &mut AnimationPlayer), Added>, ) { - fn get_clip<'a>( - node: AnimationNodeIndex, - graph: &AnimationGraph, - clips: &'a mut Assets, - ) -> &'a mut AnimationClip { - let node = graph.get(node).unwrap(); - let clip = match &node.node_type { - AnimationNodeType::Clip(handle) => clips.get_mut(handle), - _ => unreachable!(), - }; - clip.unwrap() - } - for (entity, mut player) in &mut players { - let graph = graphs.get(&animations.graph).unwrap(); - - // Send `OnStep` events once the fox feet hits the ground in the running animation. - let running_animation = get_clip(animations.animations[0], graph, &mut clips); - // You can determine the time an event should trigger if you know witch frame it occurs and - // the frame rate of the animation. Let's say we want to trigger an event at frame 15, - // and the animation has a frame rate of 24 fps, then time = 15 / 24 = 0.625. - running_animation.add_event_to_target(feet.front_left, 0.625, OnStep); - running_animation.add_event_to_target(feet.front_right, 0.5, OnStep); - running_animation.add_event_to_target(feet.back_left, 0.0, OnStep); - running_animation.add_event_to_target(feet.back_right, 0.125, OnStep); - let mut transitions = AnimationTransitions::new(); // Make sure to start the animation via the `AnimationTransitions` @@ -176,227 +94,12 @@ fn setup_scene_once_loaded( // the animations and will get confused if the animations are started // directly via the `AnimationPlayer`. transitions - .play(&mut player, animations.animations[0], Duration::ZERO) + .play(&mut player, animations.index, Duration::ZERO) .repeat(); commands .entity(entity) - .insert(AnimationGraphHandle(animations.graph.clone())) - .insert(transitions); - } -} - -fn keyboard_animation_control( - keyboard_input: Res>, - mut animation_players: Query<(&mut AnimationPlayer, &mut AnimationTransitions)>, - animations: Res, - mut current_animation: Local, -) { - for (mut player, mut transitions) in &mut animation_players { - let Some((&playing_animation_index, _)) = player.playing_animations().next() else { - continue; - }; - - if keyboard_input.just_pressed(KeyCode::Space) { - let playing_animation = player.animation_mut(playing_animation_index).unwrap(); - if playing_animation.is_paused() { - playing_animation.resume(); - } else { - playing_animation.pause(); - } - } - - if keyboard_input.just_pressed(KeyCode::ArrowUp) { - let playing_animation = player.animation_mut(playing_animation_index).unwrap(); - let speed = playing_animation.speed(); - playing_animation.set_speed(speed * 1.2); - } - - if keyboard_input.just_pressed(KeyCode::ArrowDown) { - let playing_animation = player.animation_mut(playing_animation_index).unwrap(); - let speed = playing_animation.speed(); - playing_animation.set_speed(speed * 0.8); - } - - if keyboard_input.just_pressed(KeyCode::ArrowLeft) { - let playing_animation = player.animation_mut(playing_animation_index).unwrap(); - let elapsed = playing_animation.seek_time(); - playing_animation.seek_to(elapsed - 0.1); - } - - if keyboard_input.just_pressed(KeyCode::ArrowRight) { - let playing_animation = player.animation_mut(playing_animation_index).unwrap(); - let elapsed = playing_animation.seek_time(); - playing_animation.seek_to(elapsed + 0.1); - } - - if keyboard_input.just_pressed(KeyCode::Enter) { - *current_animation = (*current_animation + 1) % animations.animations.len(); - - transitions - .play( - &mut player, - animations.animations[*current_animation], - Duration::from_millis(250), - ) - .repeat(); - } - - if keyboard_input.just_pressed(KeyCode::Digit1) { - let playing_animation = player.animation_mut(playing_animation_index).unwrap(); - playing_animation - .set_repeat(RepeatAnimation::Count(1)) - .replay(); - } - - if keyboard_input.just_pressed(KeyCode::Digit3) { - let playing_animation = player.animation_mut(playing_animation_index).unwrap(); - playing_animation - .set_repeat(RepeatAnimation::Count(3)) - .replay(); - } - - if keyboard_input.just_pressed(KeyCode::Digit5) { - let playing_animation = player.animation_mut(playing_animation_index).unwrap(); - playing_animation - .set_repeat(RepeatAnimation::Count(5)) - .replay(); - } - - if keyboard_input.just_pressed(KeyCode::KeyL) { - let playing_animation = player.animation_mut(playing_animation_index).unwrap(); - playing_animation.set_repeat(RepeatAnimation::Forever); - } - } -} - -fn simulate_particles( - mut commands: Commands, - mut query: Query<(Entity, &mut Transform, &mut Particle)>, - time: Res