From 62e2fb654d2522073ab51203631d0385a1f445b2 Mon Sep 17 00:00:00 2001 From: Greeble <166992735+greeble-dev@users.noreply.github.com> Date: Thu, 27 Mar 2025 16:06:57 +0000 Subject: [PATCH] First pass at `test_animation_transitions`. --- Cargo.toml | 8 + tests/animation/test_animation_transitions.rs | 137 ++++++++++++++++++ 2 files changed, 145 insertions(+) create mode 100644 tests/animation/test_animation_transitions.rs diff --git a/Cargo.toml b/Cargo.toml index af2d9d356cb59..73aef78b71d13 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4317,3 +4317,11 @@ name = "`no_std` Compatible Library" description = "Example library compatible with `std` and `no_std` targets" category = "Embedded" wasm = true + +[[example]] +name = "test_animation_transitions" +path = "tests/animation/test_animation_transitions.rs" +doc-scrape-examples = true + +[package.metadata.example.test_animation_transitions] +hidden = true diff --git a/tests/animation/test_animation_transitions.rs b/tests/animation/test_animation_transitions.rs new file mode 100644 index 0000000000000..a777897c390e2 --- /dev/null +++ b/tests/animation/test_animation_transitions.rs @@ -0,0 +1,137 @@ +//! Test animation transitions by spawning several meshes and randomly starting +//! transitions. + +use bevy::{prelude::*, scene::SceneInstanceReady}; +use rand::{seq::SliceRandom, Rng, SeedableRng}; +use rand_chacha::ChaCha8Rng; +use std::time::Duration; + +const GLTF_PATH: &str = "models/animated/Fox.glb"; + +fn main() { + App::new() + .insert_resource(AmbientLight { + color: Color::WHITE, + brightness: 3500.0, + ..default() + }) + .add_plugins(DefaultPlugins) + .add_systems(Startup, setup) + .add_systems(Update, play_transitions) + .run(); +} + +#[derive(Component)] +struct Animations { + node_indices: Vec, + graph_handle: Handle, + time_until_transition: f32, +} + +fn setup( + mut commands: Commands, + asset_server: Res, + mut graphs: ResMut>, +) { + let scene = SceneRoot(asset_server.load(GltfAssetLabel::Scene(0).from_asset(GLTF_PATH))); + + let (graph, node_indices) = AnimationGraph::from_clips([ + asset_server.load(GltfAssetLabel::Animation(0).from_asset(GLTF_PATH)), + asset_server.load(GltfAssetLabel::Animation(1).from_asset(GLTF_PATH)), + asset_server.load(GltfAssetLabel::Animation(2).from_asset(GLTF_PATH)), + ]); + + let graph_handle = graphs.add(graph); + + for x in [-160.0, -80.0, 0.0, 80.0, 160.0] { + commands + .spawn(( + scene.clone(), + Transform::from_xyz(x, 0.0, 0.0), + Animations { + node_indices: node_indices.clone(), + graph_handle: graph_handle.clone(), + time_until_transition: 0.0, + }, + )) + .observe(setup_animations); + } + + commands.spawn(( + Camera3d::default(), + Projection::Perspective(PerspectiveProjection { + fov: 10.0_f32.to_radians(), + ..Default::default() + }), + Transform::from_xyz(900.0, 900.0, 900.0).looking_at(Vec3::new(0.0, 30.0, 0.0), Vec3::Y), + )); +} + +fn setup_animations( + trigger: Trigger, + mut commands: Commands, + query_animations: Query<&Animations>, + children: Query<&Children>, + mut players: Query<&mut AnimationPlayer>, +) { + if let Ok(animations) = query_animations.get(trigger.target()) { + for child in children.iter_descendants(trigger.target()) { + if let Ok(mut player) = players.get_mut(child) { + let mut transitions = AnimationTransitions::new(); + + transitions + .play(&mut player, animations.node_indices[0], Duration::ZERO) + .repeat(); + + commands + .entity(child) + .insert(transitions) + .insert(AnimationGraphHandle(animations.graph_handle.clone())); + } + } + } +} + +fn play_transitions( + query_animations: Query<(Entity, &mut Animations)>, + children: Query<&Children>, + mut players: Query<(&mut AnimationPlayer, &mut AnimationTransitions)>, + mut rng: Local>, + time: Res