
# Objective `Entity::PLACEHOLDER` acts as a magic number that will *probably* never really exist, but it certainly could. And, `Entity` has a niche, so the only reason to use `PLACEHOLDER` is as an alternative to `MaybeUninit` that trades safety risks for logic risks. As a result, bevy has generally advised against using `PLACEHOLDER`, but we still use if for a lot internally. This pr starts removing internal uses of it, starting from observers. ## Solution Change all trigger target related types from `Entity` to `Option<Entity>` Small migration guide to come. ## Testing CI ## Future Work This turned a lot of code from ```rust trigger.target() ``` to ```rust trigger.target().unwrap() ``` The extra panic is no worse than before; it's just earlier than panicking after passing the placeholder to something else. But this is kinda annoying. I would like to add a `TriggerMode` or something to `Event` that would restrict what kinds of targets can be used for that event. Many events like `Removed` etc, are always triggered with a target. We can make those have a way to assume Some, etc. But I wanted to save that for a future pr.
130 lines
4.7 KiB
Rust
130 lines
4.7 KiB
Rust
//! Plays an animation on a skinned glTF model of a fox.
|
|
|
|
use std::f32::consts::PI;
|
|
|
|
use bevy::{pbr::CascadeShadowConfigBuilder, prelude::*, scene::SceneInstanceReady};
|
|
|
|
// An example asset that contains a mesh and animation.
|
|
const GLTF_PATH: &str = "models/animated/Fox.glb";
|
|
|
|
fn main() {
|
|
App::new()
|
|
.insert_resource(AmbientLight {
|
|
color: Color::WHITE,
|
|
brightness: 2000.,
|
|
..default()
|
|
})
|
|
.add_plugins(DefaultPlugins)
|
|
.add_systems(Startup, setup_mesh_and_animation)
|
|
.add_systems(Startup, setup_camera_and_environment)
|
|
.run();
|
|
}
|
|
|
|
// A component that stores a reference to an animation we want to play. This is
|
|
// created when we start loading the mesh (see `setup_mesh_and_animation`) and
|
|
// read when the mesh has spawned (see `play_animation_once_loaded`).
|
|
#[derive(Component)]
|
|
struct AnimationToPlay {
|
|
graph_handle: Handle<AnimationGraph>,
|
|
index: AnimationNodeIndex,
|
|
}
|
|
|
|
fn setup_mesh_and_animation(
|
|
mut commands: Commands,
|
|
asset_server: Res<AssetServer>,
|
|
mut graphs: ResMut<Assets<AnimationGraph>>,
|
|
) {
|
|
// Create an animation graph containing a single animation. We want the "run"
|
|
// animation from our example asset, which has an index of two.
|
|
let (graph, index) = AnimationGraph::from_clip(
|
|
asset_server.load(GltfAssetLabel::Animation(2).from_asset(GLTF_PATH)),
|
|
);
|
|
|
|
// Store the animation graph as an asset.
|
|
let graph_handle = graphs.add(graph);
|
|
|
|
// Create a component that stores a reference to our animation.
|
|
let animation_to_play = AnimationToPlay {
|
|
graph_handle,
|
|
index,
|
|
};
|
|
|
|
// Start loading the asset as a scene and store a reference to it in a
|
|
// SceneRoot component. This component will automatically spawn a scene
|
|
// containing our mesh once it has loaded.
|
|
let mesh_scene = SceneRoot(asset_server.load(GltfAssetLabel::Scene(0).from_asset(GLTF_PATH)));
|
|
|
|
// Spawn an entity with our components, and connect it to an observer that
|
|
// will trigger when the scene is loaded and spawned.
|
|
commands
|
|
.spawn((animation_to_play, mesh_scene))
|
|
.observe(play_animation_when_ready);
|
|
}
|
|
|
|
fn play_animation_when_ready(
|
|
trigger: Trigger<SceneInstanceReady>,
|
|
mut commands: Commands,
|
|
children: Query<&Children>,
|
|
animations_to_play: Query<&AnimationToPlay>,
|
|
mut players: Query<&mut AnimationPlayer>,
|
|
) {
|
|
// The entity we spawned in `setup_mesh_and_animation` is the trigger's target.
|
|
// Start by finding the AnimationToPlay component we added to that entity.
|
|
if let Ok(animation_to_play) = animations_to_play.get(trigger.target().unwrap()) {
|
|
// The SceneRoot component will have spawned the scene as a hierarchy
|
|
// of entities parented to our entity. Since the asset contained a skinned
|
|
// mesh and animations, it will also have spawned an animation player
|
|
// component. Search our entity's descendants to find the animation player.
|
|
for child in children.iter_descendants(trigger.target().unwrap()) {
|
|
if let Ok(mut player) = players.get_mut(child) {
|
|
// Tell the animation player to start the animation and keep
|
|
// repeating it.
|
|
//
|
|
// If you want to try stopping and switching animations, see the
|
|
// `animated_mesh_control.rs` example.
|
|
player.play(animation_to_play.index).repeat();
|
|
|
|
// Add the animation graph. This only needs to be done once to
|
|
// connect the animation player to the mesh.
|
|
commands
|
|
.entity(child)
|
|
.insert(AnimationGraphHandle(animation_to_play.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(),
|
|
Transform::from_xyz(100.0, 100.0, 150.0).looking_at(Vec3::new(0.0, 20.0, 0.0), Vec3::Y),
|
|
));
|
|
|
|
// Plane
|
|
commands.spawn((
|
|
Mesh3d(meshes.add(Plane3d::default().mesh().size(500000.0, 500000.0))),
|
|
MeshMaterial3d(materials.add(Color::srgb(0.3, 0.5, 0.3))),
|
|
));
|
|
|
|
// Light
|
|
commands.spawn((
|
|
Transform::from_rotation(Quat::from_euler(EulerRot::ZYX, 0.0, 1.0, -PI / 4.)),
|
|
DirectionalLight {
|
|
shadows_enabled: true,
|
|
..default()
|
|
},
|
|
CascadeShadowConfigBuilder {
|
|
first_cascade_far_bound: 200.0,
|
|
maximum_distance: 400.0,
|
|
..default()
|
|
}
|
|
.build(),
|
|
));
|
|
}
|