Skip to content

Commit

Permalink
Improve the animated_mesh example (#17328)
Browse files Browse the repository at this point in the history
# Objective

Building upon #17191, improve the
`animated_mesh` example by removing code, adding comments, and making
the example more c&p'able.

## Solution

- Split the setup function in two to clarify what the example is
demonstrating.
    - `setup_mesh_and_animation` is the demonstration.
    - `setup_camera_and_environment` just sets up the example app.
- Changed the animation playing to use `AnimationPlayer` directly
instead of creating `AnimationTransitions`.
    - This appears sufficient when only playing a single animation.
- Added a comment pointing users to an example of multiple animations.
- Changed the animation to be the run cycle.
- I think it got accidentally changed to the idle in
[#17191](#17191), so this is
reverting back to the original.
- Note that we can improve it to select the animation by name if
[#16529](#16529) lands.
- Renamed `FOX_PATH` to a more neutral `GLTF_PATH`.
- Updated the example descriptions to mention the fox.
- This adds a little character and hints that the example involves
character animation.
- Removed a seemingly redundant `AnimationGraphHandle` component.
- Removed an unnecessary `clone()`.
- Added various comments.

## Notes

- A draft of this PR was discussed on Discord:
https://discord.com/channels/691052431525675048/1326910663972618302/1326920498663133348
- There was discord discussion on whether a component is "inserted
onto", "inserted into" or "added to" an entity.
- "Added to" is most common in code and docs, and seems best to me. But
it awkwardly differs from the name of `EntityCommands::insert`.
    - This PR prefers "added to".
- I plan to follow up this PR with similar changes to the
`animated_mesh_control` and `animated_mesh_events` examples.
    - But I could roll them into this PR if requested.

## Testing

`cargo run --example animated_mesh`

---------

Co-authored-by: François Mockers <[email protected]>
  • Loading branch information
greeble-dev and mockersf authored Jan 14, 2025
1 parent b90329a commit c96949d
Show file tree
Hide file tree
Showing 3 changed files with 52 additions and 46 deletions.
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -1289,7 +1289,7 @@ doc-scrape-examples = true

[package.metadata.example.animated_mesh]
name = "Animated Mesh"
description = "Plays an animation from a skinned glTF"
description = "Plays an animation on a skinned glTF model of a fox"
category = "Animation"
wasm = true

Expand Down
2 changes: 1 addition & 1 deletion examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,7 @@ Example | Description

Example | Description
--- | ---
[Animated Mesh](../examples/animation/animated_mesh.rs) | Plays an animation from a skinned glTF
[Animated Mesh](../examples/animation/animated_mesh.rs) | Plays an animation on a skinned glTF model of a fox
[Animated Mesh Control](../examples/animation/animated_mesh_control.rs) | Plays an animation from a skinned glTF with keyboard controls
[Animated Mesh Events](../examples/animation/animated_mesh_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
Expand Down
94 changes: 50 additions & 44 deletions examples/animation/animated_mesh.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
//! Plays animations from a skinned glTF.
//! Plays an animation on a skinned glTF model of a fox.
use std::{f32::consts::PI, time::Duration};
use std::f32::consts::PI;

use bevy::{pbr::CascadeShadowConfigBuilder, prelude::*};

const FOX_PATH: &str = "models/animated/Fox.glb";
// An example asset that contains a mesh and animation.
const GLTF_PATH: &str = "models/animated/Fox.glb";

fn main() {
App::new()
Expand All @@ -14,8 +15,9 @@ fn main() {
..default()
})
.add_plugins(DefaultPlugins)
.add_systems(Startup, setup)
.add_systems(Update, setup_scene_once_loaded)
.add_systems(Startup, setup_mesh_and_animation)
.add_systems(Startup, setup_camera_and_environment)
.add_systems(Update, play_animation_once_loaded)
.run();
}

Expand All @@ -25,27 +27,61 @@ struct Animations {
index: AnimationNodeIndex,
}

fn setup(
// Create an animation graph and start loading the mesh and animation.
fn setup_mesh_and_animation(
mut commands: Commands,
asset_server: Res<AssetServer>,
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<StandardMaterial>>,
mut graphs: ResMut<Assets<AnimationGraph>>,
) {
// Build the animation graph
// Build an animation graph containing a single animation.
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)),
// We want the "run" animation from our example asset, which has an
// index of two.
asset_server.load(GltfAssetLabel::Animation(2).from_asset(GLTF_PATH)),
);

// Keep our animation graph in a Resource so that it can be inserted onto
// the correct entity once the scene actually loads.
// Keep our animation graph in a Resource so that it can be added to the
// correct entity once the scene loads.
let graph_handle = graphs.add(graph);
commands.insert_resource(Animations {
graph_handle: graph_handle.clone(),
graph_handle,
index,
});

// Tell the engine to start loading our mesh and animation, and then spawn
// them as a scene when ready.
commands.spawn(SceneRoot(
asset_server.load(GltfAssetLabel::Scene(0).from_asset(GLTF_PATH)),
));
}

// Detect that the scene is loaded and spawned, then play the animation.
fn play_animation_once_loaded(
mut commands: Commands,
animations: Res<Animations>,
mut players: Query<(Entity, &mut AnimationPlayer), Added<AnimationPlayer>>,
) {
for (entity, mut player) in &mut players {
// Start the animation player and tell it to repeat forever.
//
// If you want to try stopping and switching animations, see the
// `animated_mesh_control.rs` example.
player.play(animations.index).repeat();

// Insert the animation graph with our selected animation. This
// connects the animation player to the mesh.
commands
.entity(entity)
.insert(AnimationGraphHandle(animations.graph_handle.clone()));
}
}

// Spawn a camera and a simple environment with a ground plane and light.
fn setup_camera_and_environment(
mut commands: Commands,
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<StandardMaterial>>,
) {
// Camera
commands.spawn((
Camera3d::default(),
Expand All @@ -72,34 +108,4 @@ fn setup(
}
.build(),
));

// Fox
commands.spawn((
SceneRoot(asset_server.load(GltfAssetLabel::Scene(0).from_asset(FOX_PATH))),
AnimationGraphHandle(graph_handle),
));
}

// Once the scene is loaded, start the animation
fn setup_scene_once_loaded(
mut commands: Commands,
animations: Res<Animations>,
mut players: Query<(Entity, &mut AnimationPlayer), Added<AnimationPlayer>>,
) {
for (entity, mut player) in &mut players {
let mut transitions = AnimationTransitions::new();

// Make sure to start the animation via the `AnimationTransitions`
// component. The `AnimationTransitions` component wants to manage all
// the animations and will get confused if the animations are started
// directly via the `AnimationPlayer`.
transitions
.play(&mut player, animations.index, Duration::ZERO)
.repeat();

commands
.entity(entity)
.insert(transitions)
.insert(AnimationGraphHandle(animations.graph_handle.clone()));
}
}

0 comments on commit c96949d

Please sign in to comment.