Animation / Morph Targets

Back to examples View in GitHub

Play an animation with morph targets.

Also illustrates how to read morph target names in name_morphs.

use bevy::{prelude::*, scene::SceneInstanceReady};
use std::f32::consts::PI;

const GLTF_PATH: &str = "models/animated/MorphStressTest.gltf";

fn main() {
    App::new()
        .add_plugins(DefaultPlugins)
        .insert_resource(AmbientLight {
            brightness: 150.0,
            ..default()
        })
        .add_systems(Startup, setup)
        .add_systems(Update, name_morphs)
        .run();
}

#[derive(Component)]
struct AnimationToPlay {
    graph_handle: Handle<AnimationGraph>,
    index: AnimationNodeIndex,
}

fn setup(
    mut commands: Commands,
    asset_server: Res<AssetServer>,
    mut graphs: ResMut<Assets<AnimationGraph>>,
) {
    let (graph, index) = AnimationGraph::from_clip(
        asset_server.load(GltfAssetLabel::Animation(2).from_asset(GLTF_PATH)),
    );

    commands
        .spawn((
            AnimationToPlay {
                graph_handle: graphs.add(graph),
                index,
            },
            SceneRoot(asset_server.load(GltfAssetLabel::Scene(0).from_asset(GLTF_PATH))),
        ))
        .observe(play_animation_when_ready);

    commands.spawn((
        DirectionalLight::default(),
        Transform::from_rotation(Quat::from_rotation_z(PI / 2.0)),
    ));

    commands.spawn((
        Camera3d::default(),
        Transform::from_xyz(3.0, 2.1, 10.2).looking_at(Vec3::ZERO, Vec3::Y),
    ));
}

fn play_animation_when_ready(
    scene_ready: On<SceneInstanceReady>,
    mut commands: Commands,
    children: Query<&Children>,
    animations_to_play: Query<&AnimationToPlay>,
    mut players: Query<&mut AnimationPlayer>,
) {
    if let Ok(animation_to_play) = animations_to_play.get(scene_ready.entity) {
        for child in children.iter_descendants(scene_ready.entity) {
            if let Ok(mut player) = players.get_mut(child) {
                player.play(animation_to_play.index).repeat();

                commands
                    .entity(child)
                    .insert(AnimationGraphHandle(animation_to_play.graph_handle.clone()));
            }
        }
    }
}

/// Whenever a mesh asset is loaded, print the name of the asset and the names
/// of its morph targets.
fn name_morphs(
    asset_server: Res<AssetServer>,
    mut events: MessageReader<AssetEvent<Mesh>>,
    meshes: Res<Assets<Mesh>>,
) {
    for event in events.read() {
        if let AssetEvent::<Mesh>::Added { id } = event
            && let Some(path) = asset_server.get_path(*id)
            && let Some(mesh) = meshes.get(*id)
            && let Some(names) = mesh.morph_target_names()
        {
            info!("Morph target names for {path:?}:");

            for name in names {
                info!("  {name}");
            }
        }
    }
}