Path: blob/main/examples/animation/animated_mesh_events.rs
6849 views
//! Plays animations from a skinned glTF.12use std::{f32::consts::PI, time::Duration};34use bevy::{5animation::{AnimationEvent, AnimationTargetId},6color::palettes::css::WHITE,7light::CascadeShadowConfigBuilder,8prelude::*,9};10use rand::{Rng, SeedableRng};11use rand_chacha::ChaCha8Rng;1213const FOX_PATH: &str = "models/animated/Fox.glb";1415fn main() {16App::new()17.insert_resource(AmbientLight {18color: Color::WHITE,19brightness: 2000.,20..default()21})22.add_plugins(DefaultPlugins)23.init_resource::<ParticleAssets>()24.init_resource::<FoxFeetTargets>()25.add_systems(Startup, setup)26.add_systems(Update, setup_scene_once_loaded)27.add_systems(Update, simulate_particles)28.add_observer(observe_on_step)29.run();30}3132#[derive(Resource)]33struct SeededRng(ChaCha8Rng);3435#[derive(Resource)]36struct Animations {37index: AnimationNodeIndex,38graph_handle: Handle<AnimationGraph>,39}4041#[derive(AnimationEvent, Reflect, Clone)]42struct Step;4344fn observe_on_step(45step: On<Step>,46particle: Res<ParticleAssets>,47mut commands: Commands,48transforms: Query<&GlobalTransform>,49mut seeded_rng: ResMut<SeededRng>,50) -> Result {51let translation = transforms52.get(step.trigger().animation_player)?53.translation();54// Spawn a bunch of particles.55for _ in 0..14 {56let horizontal = seeded_rng.0.random::<Dir2>() * seeded_rng.0.random_range(8.0..12.0);57let vertical = seeded_rng.0.random_range(0.0..4.0);58let size = seeded_rng.0.random_range(0.2..1.0);5960commands.spawn((61Particle {62lifetime_timer: Timer::from_seconds(63seeded_rng.0.random_range(0.2..0.6),64TimerMode::Once,65),66size,67velocity: Vec3::new(horizontal.x, vertical, horizontal.y) * 10.0,68},69Mesh3d(particle.mesh.clone()),70MeshMaterial3d(particle.material.clone()),71Transform {72translation,73scale: Vec3::splat(size),74..Default::default()75},76));77}78Ok(())79}8081fn setup(82mut commands: Commands,83asset_server: Res<AssetServer>,84mut meshes: ResMut<Assets<Mesh>>,85mut materials: ResMut<Assets<StandardMaterial>>,86mut graphs: ResMut<Assets<AnimationGraph>>,87) {88// Build the animation graph89let (graph, index) = AnimationGraph::from_clip(90// We specifically want the "run" animation, which is the third one.91asset_server.load(GltfAssetLabel::Animation(2).from_asset(FOX_PATH)),92);9394// Insert a resource with the current scene information95let graph_handle = graphs.add(graph);96commands.insert_resource(Animations {97index,98graph_handle,99});100101// Camera102commands.spawn((103Camera3d::default(),104Transform::from_xyz(100.0, 100.0, 150.0).looking_at(Vec3::new(0.0, 20.0, 0.0), Vec3::Y),105));106107// Plane108commands.spawn((109Mesh3d(meshes.add(Plane3d::default().mesh().size(500000.0, 500000.0))),110MeshMaterial3d(materials.add(Color::srgb(0.3, 0.5, 0.3))),111));112113// Light114commands.spawn((115Transform::from_rotation(Quat::from_euler(EulerRot::ZYX, 0.0, 1.0, -PI / 4.)),116DirectionalLight {117shadows_enabled: true,118..default()119},120CascadeShadowConfigBuilder {121first_cascade_far_bound: 200.0,122maximum_distance: 400.0,123..default()124}125.build(),126));127128// Fox129commands.spawn(SceneRoot(130asset_server.load(GltfAssetLabel::Scene(0).from_asset(FOX_PATH)),131));132133// We're seeding the PRNG here to make this example deterministic for testing purposes.134// This isn't strictly required in practical use unless you need your app to be deterministic.135let seeded_rng = ChaCha8Rng::seed_from_u64(19878367467712);136commands.insert_resource(SeededRng(seeded_rng));137}138139// An `AnimationPlayer` is automatically added to the scene when it's ready.140// When the player is added, start the animation.141fn setup_scene_once_loaded(142mut commands: Commands,143animations: Res<Animations>,144feet: Res<FoxFeetTargets>,145graphs: Res<Assets<AnimationGraph>>,146mut clips: ResMut<Assets<AnimationClip>>,147mut players: Query<(Entity, &mut AnimationPlayer), Added<AnimationPlayer>>,148) {149fn get_clip<'a>(150node: AnimationNodeIndex,151graph: &AnimationGraph,152clips: &'a mut Assets<AnimationClip>,153) -> &'a mut AnimationClip {154let node = graph.get(node).unwrap();155let clip = match &node.node_type {156AnimationNodeType::Clip(handle) => clips.get_mut(handle),157_ => unreachable!(),158};159clip.unwrap()160}161162for (entity, mut player) in &mut players {163// Send `OnStep` events once the fox feet hits the ground in the running animation.164165let graph = graphs.get(&animations.graph_handle).unwrap();166let running_animation = get_clip(animations.index, graph, &mut clips);167168// You can determine the time an event should trigger if you know witch frame it occurs and169// the frame rate of the animation. Let's say we want to trigger an event at frame 15,170// and the animation has a frame rate of 24 fps, then time = 15 / 24 = 0.625.171running_animation.add_event_to_target(feet.front_left, 0.625, Step);172running_animation.add_event_to_target(feet.front_right, 0.5, Step);173running_animation.add_event_to_target(feet.back_left, 0.0, Step);174running_animation.add_event_to_target(feet.back_right, 0.125, Step);175176// Start the animation177178let mut transitions = AnimationTransitions::new();179180// Make sure to start the animation via the `AnimationTransitions`181// component. The `AnimationTransitions` component wants to manage all182// the animations and will get confused if the animations are started183// directly via the `AnimationPlayer`.184transitions185.play(&mut player, animations.index, Duration::ZERO)186.repeat();187188commands189.entity(entity)190.insert(AnimationGraphHandle(animations.graph_handle.clone()))191.insert(transitions);192}193}194195fn simulate_particles(196mut commands: Commands,197mut query: Query<(Entity, &mut Transform, &mut Particle)>,198time: Res<Time>,199) {200for (entity, mut transform, mut particle) in &mut query {201if particle.lifetime_timer.tick(time.delta()).just_finished() {202commands.entity(entity).despawn();203return;204}205206transform.translation += particle.velocity * time.delta_secs();207transform.scale = Vec3::splat(particle.size.lerp(0.0, particle.lifetime_timer.fraction()));208particle209.velocity210.smooth_nudge(&Vec3::ZERO, 4.0, time.delta_secs());211}212}213214#[derive(Component)]215struct Particle {216lifetime_timer: Timer,217size: f32,218velocity: Vec3,219}220221#[derive(Resource)]222struct ParticleAssets {223mesh: Handle<Mesh>,224material: Handle<StandardMaterial>,225}226227impl FromWorld for ParticleAssets {228fn from_world(world: &mut World) -> Self {229Self {230mesh: world.add_asset::<Mesh>(Sphere::new(10.0)),231material: world.add_asset::<StandardMaterial>(StandardMaterial {232base_color: WHITE.into(),233..Default::default()234}),235}236}237}238239/// Stores the `AnimationTargetId`s of the fox's feet240#[derive(Resource)]241struct FoxFeetTargets {242front_right: AnimationTargetId,243front_left: AnimationTargetId,244back_left: AnimationTargetId,245back_right: AnimationTargetId,246}247248impl Default for FoxFeetTargets {249fn default() -> Self {250let hip_node = ["root", "_rootJoint", "b_Root_00", "b_Hip_01"];251let front_left_foot = hip_node.iter().chain(252[253"b_Spine01_02",254"b_Spine02_03",255"b_LeftUpperArm_09",256"b_LeftForeArm_010",257"b_LeftHand_011",258]259.iter(),260);261let front_right_foot = hip_node.iter().chain(262[263"b_Spine01_02",264"b_Spine02_03",265"b_RightUpperArm_06",266"b_RightForeArm_07",267"b_RightHand_08",268]269.iter(),270);271let back_left_foot = hip_node.iter().chain(272[273"b_LeftLeg01_015",274"b_LeftLeg02_016",275"b_LeftFoot01_017",276"b_LeftFoot02_018",277]278.iter(),279);280let back_right_foot = hip_node.iter().chain(281[282"b_RightLeg01_019",283"b_RightLeg02_020",284"b_RightFoot01_021",285"b_RightFoot02_022",286]287.iter(),288);289Self {290front_left: AnimationTargetId::from_iter(front_left_foot),291front_right: AnimationTargetId::from_iter(front_right_foot),292back_left: AnimationTargetId::from_iter(back_left_foot),293back_right: AnimationTargetId::from_iter(back_right_foot),294}295}296}297298299