mirror of
https://github.com/bevyengine/bevy
synced 2024-11-22 12:43:34 +00:00
8154164f1b
Currently, Bevy restricts animation clips to animating `Transform::translation`, `Transform::rotation`, `Transform::scale`, or `MorphWeights`, which correspond to the properties that glTF can animate. This is insufficient for many use cases such as animating UI, as the UI layout systems expect to have exclusive control over UI elements' `Transform`s and therefore the `Style` properties must be animated instead. This commit fixes this, allowing for `AnimationClip`s to animate arbitrary properties. The `Keyframes` structure has been turned into a low-level trait that can be implemented to achieve arbitrary animation behavior. Along with `Keyframes`, this patch adds a higher-level trait, `AnimatableProperty`, that simplifies the task of animating single interpolable properties. Built-in `Keyframes` implementations exist for translation, rotation, scale, and morph weights. For the most part, you can migrate by simply changing your code from `Keyframes::Translation(...)` to `TranslationKeyframes(...)`, and likewise for rotation, scale, and morph weights. An example `AnimatableProperty` implementation for the font size of a text section follows: #[derive(Reflect)] struct FontSizeProperty; impl AnimatableProperty for FontSizeProperty { type Component = Text; type Property = f32; fn get_mut(component: &mut Self::Component) -> Option<&mut Self::Property> { Some(&mut component.sections.get_mut(0)?.style.font_size) } } In order to keep this patch relatively small, this patch doesn't include an implementation of `AnimatableProperty` on top of the reflection system. That can be a follow-up. This patch builds on top of the new `EntityMutExcept<>` type in order to widen the `AnimationTarget` query to include write access to all components. Because `EntityMutExcept<>` has some performance overhead over an explicit query, we continue to explicitly query `Transform` in order to avoid regressing the performance of skeletal animation, such as the `many_foxes` benchmark. I've measured the performance of that benchmark and have found no significant regressions. A new example, `animated_ui`, has been added. This example shows how to use Bevy's built-in animation infrastructure to animate font size and color, which wasn't possible before this patch. ## Showcase https://github.com/user-attachments/assets/1fa73492-a9ce-405a-a8f2-4aacd7f6dc97 ## Migration Guide * Animation keyframes are now an extensible trait, not an enum. Replace `Keyframes::Translation(...)`, `Keyframes::Scale(...)`, `Keyframes::Rotation(...)`, and `Keyframes::Weights(...)` with `Box::new(TranslationKeyframes(...))`, `Box::new(ScaleKeyframes(...))`, `Box::new(RotationKeyframes(...))`, and `Box::new(MorphWeightsKeyframes(...))` respectively.
180 lines
6.3 KiB
Rust
180 lines
6.3 KiB
Rust
//! Create and play an animation defined by code that operates on the [`Transform`] component.
|
|
|
|
use std::f32::consts::PI;
|
|
|
|
use bevy::animation::{AnimationTarget, AnimationTargetId};
|
|
use bevy::prelude::*;
|
|
|
|
fn main() {
|
|
App::new()
|
|
.add_plugins(DefaultPlugins)
|
|
.insert_resource(AmbientLight {
|
|
color: Color::WHITE,
|
|
brightness: 150.0,
|
|
})
|
|
.add_systems(Startup, setup)
|
|
.run();
|
|
}
|
|
|
|
fn setup(
|
|
mut commands: Commands,
|
|
mut meshes: ResMut<Assets<Mesh>>,
|
|
mut materials: ResMut<Assets<StandardMaterial>>,
|
|
mut animations: ResMut<Assets<AnimationClip>>,
|
|
mut graphs: ResMut<Assets<AnimationGraph>>,
|
|
) {
|
|
// Camera
|
|
commands.spawn(Camera3dBundle {
|
|
transform: Transform::from_xyz(-2.0, 2.5, 5.0).looking_at(Vec3::ZERO, Vec3::Y),
|
|
..default()
|
|
});
|
|
|
|
// Light
|
|
commands.spawn(PointLightBundle {
|
|
point_light: PointLight {
|
|
intensity: 500_000.0,
|
|
..default()
|
|
},
|
|
transform: Transform::from_xyz(0.0, 2.5, 0.0),
|
|
..default()
|
|
});
|
|
|
|
// Let's use the `Name` component to target entities. We can use anything we
|
|
// like, but names are convenient.
|
|
let planet = Name::new("planet");
|
|
let orbit_controller = Name::new("orbit_controller");
|
|
let satellite = Name::new("satellite");
|
|
|
|
// Creating the animation
|
|
let mut animation = AnimationClip::default();
|
|
// A curve can modify a single part of a transform, here the translation
|
|
let planet_animation_target_id = AnimationTargetId::from_name(&planet);
|
|
animation.add_curve_to_target(
|
|
planet_animation_target_id,
|
|
VariableCurve::linear::<TranslationKeyframes>(
|
|
[0.0, 1.0, 2.0, 3.0, 4.0],
|
|
[
|
|
Vec3::new(1.0, 0.0, 1.0),
|
|
Vec3::new(-1.0, 0.0, 1.0),
|
|
Vec3::new(-1.0, 0.0, -1.0),
|
|
Vec3::new(1.0, 0.0, -1.0),
|
|
// in case seamless looping is wanted, the last keyframe should
|
|
// be the same as the first one
|
|
Vec3::new(1.0, 0.0, 1.0),
|
|
],
|
|
),
|
|
);
|
|
// Or it can modify the rotation of the transform.
|
|
// To find the entity to modify, the hierarchy will be traversed looking for
|
|
// an entity with the right name at each level
|
|
let orbit_controller_animation_target_id =
|
|
AnimationTargetId::from_names([planet.clone(), orbit_controller.clone()].iter());
|
|
animation.add_curve_to_target(
|
|
orbit_controller_animation_target_id,
|
|
VariableCurve::linear::<RotationKeyframes>(
|
|
[0.0, 1.0, 2.0, 3.0, 4.0],
|
|
[
|
|
Quat::IDENTITY,
|
|
Quat::from_axis_angle(Vec3::Y, PI / 2.),
|
|
Quat::from_axis_angle(Vec3::Y, PI / 2. * 2.),
|
|
Quat::from_axis_angle(Vec3::Y, PI / 2. * 3.),
|
|
Quat::IDENTITY,
|
|
],
|
|
),
|
|
);
|
|
// If a curve in an animation is shorter than the other, it will not repeat
|
|
// until all other curves are finished. In that case, another animation should
|
|
// be created for each part that would have a different duration / period
|
|
let satellite_animation_target_id = AnimationTargetId::from_names(
|
|
[planet.clone(), orbit_controller.clone(), satellite.clone()].iter(),
|
|
);
|
|
animation.add_curve_to_target(
|
|
satellite_animation_target_id,
|
|
VariableCurve::linear::<ScaleKeyframes>(
|
|
[0.0, 0.5, 1.0, 1.5, 2.0, 2.5, 3.0, 3.5, 4.0],
|
|
[
|
|
Vec3::splat(0.8),
|
|
Vec3::splat(1.2),
|
|
Vec3::splat(0.8),
|
|
Vec3::splat(1.2),
|
|
Vec3::splat(0.8),
|
|
Vec3::splat(1.2),
|
|
Vec3::splat(0.8),
|
|
Vec3::splat(1.2),
|
|
Vec3::splat(0.8),
|
|
],
|
|
),
|
|
);
|
|
// There can be more than one curve targeting the same entity path
|
|
animation.add_curve_to_target(
|
|
AnimationTargetId::from_names(
|
|
[planet.clone(), orbit_controller.clone(), satellite.clone()].iter(),
|
|
),
|
|
VariableCurve::linear::<RotationKeyframes>(
|
|
[0.0, 1.0, 2.0, 3.0, 4.0],
|
|
[
|
|
Quat::IDENTITY,
|
|
Quat::from_axis_angle(Vec3::Y, PI / 2.),
|
|
Quat::from_axis_angle(Vec3::Y, PI / 2. * 2.),
|
|
Quat::from_axis_angle(Vec3::Y, PI / 2. * 3.),
|
|
Quat::IDENTITY,
|
|
],
|
|
),
|
|
);
|
|
|
|
// Create the animation graph
|
|
let (graph, animation_index) = AnimationGraph::from_clip(animations.add(animation));
|
|
|
|
// Create the animation player, and set it to repeat
|
|
let mut player = AnimationPlayer::default();
|
|
player.play(animation_index).repeat();
|
|
|
|
// Create the scene that will be animated
|
|
// First entity is the planet
|
|
let planet_entity = commands
|
|
.spawn((
|
|
PbrBundle {
|
|
mesh: meshes.add(Sphere::default()),
|
|
material: materials.add(Color::srgb(0.8, 0.7, 0.6)),
|
|
..default()
|
|
},
|
|
// Add the animation graph and player
|
|
planet,
|
|
graphs.add(graph),
|
|
player,
|
|
))
|
|
.id();
|
|
commands
|
|
.entity(planet_entity)
|
|
.insert(AnimationTarget {
|
|
id: planet_animation_target_id,
|
|
player: planet_entity,
|
|
})
|
|
.with_children(|p| {
|
|
// This entity is just used for animation, but doesn't display anything
|
|
p.spawn((
|
|
SpatialBundle::INHERITED_IDENTITY,
|
|
orbit_controller,
|
|
AnimationTarget {
|
|
id: orbit_controller_animation_target_id,
|
|
player: planet_entity,
|
|
},
|
|
))
|
|
.with_children(|p| {
|
|
// The satellite, placed at a distance of the planet
|
|
p.spawn((
|
|
PbrBundle {
|
|
transform: Transform::from_xyz(1.5, 0.0, 0.0),
|
|
mesh: meshes.add(Cuboid::new(0.5, 0.5, 0.5)),
|
|
material: materials.add(Color::srgb(0.3, 0.9, 0.3)),
|
|
..default()
|
|
},
|
|
AnimationTarget {
|
|
id: satellite_animation_target_id,
|
|
player: planet_entity,
|
|
},
|
|
satellite,
|
|
));
|
|
});
|
|
});
|
|
}
|