docs/bevy/examples/animation/animated_mesh.rs 4.7 K · 132 lines · raw · history

1 //! Plays an animation on a skinned glTF model of a fox.
2
3 use std::f32::consts::PI;
4
5 use bevy::{
6 light::CascadeShadowConfigBuilder, prelude::*, world_serialization::WorldInstanceReady,
7 };
8
9 // An example asset that contains a mesh and animation.
10 const GLTF_PATH: &str = "models/animated/Fox.glb";
11
12 fn main() {
13 App::new()
14 .insert_resource(GlobalAmbientLight {
15 color: Color::WHITE,
16 brightness: 2000.,
17 ..default()
18 })
19 .add_plugins(DefaultPlugins)
20 .add_systems(Startup, setup_mesh_and_animation)
21 .add_systems(Startup, setup_camera_and_environment)
22 .run();
23 }
24
25 // A component that stores a reference to an animation we want to play. This is
26 // created when we start loading the mesh (see `setup_mesh_and_animation`) and
27 // read when the mesh has spawned (see `play_animation_once_loaded`).
28 #[derive(Component)]
29 struct AnimationToPlay {
30 graph_handle: Handle<AnimationGraph>,
31 index: AnimationNodeIndex,
32 }
33
34 fn setup_mesh_and_animation(
35 mut commands: Commands,
36 asset_server: Res<AssetServer>,
37 mut graphs: ResMut<Assets<AnimationGraph>>,
38 ) {
39 // Create an animation graph containing a single animation. We want the "run"
40 // animation from our example asset, which has an index of two.
41 let (graph, index) = AnimationGraph::from_clip(
42 asset_server.load(GltfAssetLabel::Animation(2).from_asset(GLTF_PATH)),
43 );
44
45 // Store the animation graph as an asset.
46 let graph_handle = graphs.add(graph);
47
48 // Create a component that stores a reference to our animation.
49 let animation_to_play = AnimationToPlay {
50 graph_handle,
51 index,
52 };
53
54 // Start loading the asset as a scene and store a reference to it in a
55 // WorldAssetRoot component. This component will automatically spawn a scene
56 // containing our mesh once it has loaded.
57 let mesh_scene =
58 WorldAssetRoot(asset_server.load(GltfAssetLabel::Scene(0).from_asset(GLTF_PATH)));
59
60 // Spawn an entity with our components, and connect it to an observer that
61 // will trigger when the scene is loaded and spawned.
62 commands
63 .spawn((animation_to_play, mesh_scene))
64 .observe(play_animation_when_ready);
65 }
66
67 fn play_animation_when_ready(
68 scene_ready: On<WorldInstanceReady>,
69 mut commands: Commands,
70 children: Query<&Children>,
71 animations_to_play: Query<&AnimationToPlay>,
72 mut players: Query<&mut AnimationPlayer>,
73 ) {
74 // The entity we spawned in `setup_mesh_and_animation` is the trigger's target.
75 // Start by finding the AnimationToPlay component we added to that entity.
76 if let Ok(animation_to_play) = animations_to_play.get(scene_ready.entity) {
77 // The WorldAssetRoot component will have spawned the scene as a hierarchy
78 // of entities parented to our entity. Since the asset contained a skinned
79 // mesh and animations, it will also have spawned an animation player
80 // component. Search our entity's descendants to find the animation player.
81 for child in children.iter_descendants(scene_ready.entity) {
82 if let Ok(mut player) = players.get_mut(child) {
83 // Tell the animation player to start the animation and keep
84 // repeating it.
85 //
86 // If you want to try stopping and switching animations, see the
87 // `animated_mesh_control.rs` example.
88 player.play(animation_to_play.index).repeat();
89
90 // Add the animation graph. This only needs to be done once to
91 // connect the animation player to the mesh.
92 commands
93 .entity(child)
94 .insert(AnimationGraphHandle(animation_to_play.graph_handle.clone()));
95 }
96 }
97 }
98 }
99
100 // Spawn a camera and a simple environment with a ground plane and light.
101 fn setup_camera_and_environment(
102 mut commands: Commands,
103 mut meshes: ResMut<Assets<Mesh>>,
104 mut materials: ResMut<Assets<StandardMaterial>>,
105 ) {
106 // Camera
107 commands.spawn((
108 Camera3d::default(),
109 Transform::from_xyz(100.0, 100.0, 150.0).looking_at(Vec3::new(0.0, 20.0, 0.0), Vec3::Y),
110 ));
111
112 // Plane
113 commands.spawn((
114 Mesh3d(meshes.add(Plane3d::default().mesh().size(500000.0, 500000.0))),
115 MeshMaterial3d(materials.add(Color::srgb(0.3, 0.5, 0.3))),
116 ));
117
118 // Light
119 commands.spawn((
120 Transform::from_rotation(Quat::from_euler(EulerRot::ZYX, 0.0, 1.0, -PI / 4.)),
121 DirectionalLight {
122 shadow_maps_enabled: true,
123 ..default()
124 },
125 CascadeShadowConfigBuilder {
126 first_cascade_far_bound: 200.0,
127 maximum_distance: 400.0,
128 ..default()
129 }
130 .build(),
131 ));
132 }