mirror of
https://github.com/bevyengine/bevy
synced 2025-02-16 22:18:33 +00:00
AnimationEvent -> Event and other improvements (#16440)
# Objective Needing to derive `AnimationEvent` for `Event` is unnecessary, and the trigger logic coupled to it feels like we're coupling "event producer" logic with the event itself, which feels wrong. It also comes with a bunch of complexity, which is again unnecessary. We can have the flexibility of "custom animation event trigger logic" without this coupling and complexity. The current `animation_events` example is also needlessly complicated, due to it needing to work around system ordering issues. The docs describing it are also slightly wrong. We can make this all a non-issue by solving the underlying ordering problem. Related to this, we use the `bevy_animation::Animation` system set to solve PostUpdate animation order-of-operations issues. If we move this to bevy_app as part of our "core schedule", we can cut out needless `bevy_animation` crate dependencies in these instances. ## Solution - Remove `AnimationEvent`, the derive, and all other infrastructure associated with it (such as the `bevy_animation/derive` crate) - Replace all instances of `AnimationEvent` traits with `Event + Clone` - Store and use functions for custom animation trigger logic (ex: `clip.add_event_fn()`). For "normal" cases users dont need to think about this and should use the simpler `clip.add_event()` - Run the `Animation` system set _before_ updating text - Move `bevy_animation::Animation` to `bevy_app::Animation`. Remove unnecessary `bevy_animation` dependency from `bevy_ui` - Adjust `animation_events` example to use the simpler `clip.add_event` API, as the workarounds are no longer necessary This is polishing work that will land in 0.15, and I think it is simple enough and valuable enough to land in 0.15 with it, in the interest of making the feature as compelling as possible.
This commit is contained in:
parent
a54d85bb2d
commit
513be52505
12 changed files with 123 additions and 404 deletions
|
@ -10,7 +10,6 @@ keywords = ["bevy"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
# bevy
|
# bevy
|
||||||
bevy_animation_derive = { path = "derive", version = "0.15.0-dev" }
|
|
||||||
bevy_app = { path = "../bevy_app", version = "0.15.0-dev" }
|
bevy_app = { path = "../bevy_app", version = "0.15.0-dev" }
|
||||||
bevy_asset = { path = "../bevy_asset", version = "0.15.0-dev" }
|
bevy_asset = { path = "../bevy_asset", version = "0.15.0-dev" }
|
||||||
bevy_color = { path = "../bevy_color", version = "0.15.0-dev" }
|
bevy_color = { path = "../bevy_color", version = "0.15.0-dev" }
|
||||||
|
|
|
@ -1,25 +0,0 @@
|
||||||
[package]
|
|
||||||
name = "bevy_animation_derive"
|
|
||||||
version = "0.15.0-dev"
|
|
||||||
edition = "2021"
|
|
||||||
description = "Derive implementations for bevy_animation"
|
|
||||||
homepage = "https://bevyengine.org"
|
|
||||||
repository = "https://github.com/bevyengine/bevy"
|
|
||||||
license = "MIT OR Apache-2.0"
|
|
||||||
keywords = ["bevy"]
|
|
||||||
|
|
||||||
[lib]
|
|
||||||
proc-macro = true
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
bevy_macro_utils = { path = "../../bevy_macro_utils", version = "0.15.0-dev" }
|
|
||||||
proc-macro2 = "1.0"
|
|
||||||
quote = "1.0"
|
|
||||||
syn = { version = "2.0", features = ["full"] }
|
|
||||||
|
|
||||||
[lints]
|
|
||||||
workspace = true
|
|
||||||
|
|
||||||
[package.metadata.docs.rs]
|
|
||||||
rustdoc-args = ["-Zunstable-options", "--generate-link-to-definition"]
|
|
||||||
all-features = true
|
|
|
@ -1,29 +0,0 @@
|
||||||
//! Derive macros for `bevy_animation`.
|
|
||||||
|
|
||||||
extern crate proc_macro;
|
|
||||||
|
|
||||||
use bevy_macro_utils::BevyManifest;
|
|
||||||
use proc_macro::TokenStream;
|
|
||||||
use quote::quote;
|
|
||||||
use syn::{parse_macro_input, DeriveInput};
|
|
||||||
|
|
||||||
/// Used to derive `AnimationEvent` for a type.
|
|
||||||
#[proc_macro_derive(AnimationEvent)]
|
|
||||||
pub fn derive_animation_event(input: TokenStream) -> TokenStream {
|
|
||||||
let ast = parse_macro_input!(input as DeriveInput);
|
|
||||||
let name = ast.ident;
|
|
||||||
let manifest = BevyManifest::default();
|
|
||||||
let bevy_animation_path = manifest.get_path("bevy_animation");
|
|
||||||
let bevy_ecs_path = manifest.get_path("bevy_ecs");
|
|
||||||
let animation_event_path = quote! { #bevy_animation_path::animation_event };
|
|
||||||
let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl();
|
|
||||||
// TODO: This could derive Event as well.
|
|
||||||
quote! {
|
|
||||||
impl #impl_generics #animation_event_path::AnimationEvent for #name #ty_generics #where_clause {
|
|
||||||
fn trigger(&self, _time: f32, _weight: f32, entity: #bevy_ecs_path::entity::Entity, world: &mut #bevy_ecs_path::world::World) {
|
|
||||||
world.entity_mut(entity).trigger(Clone::clone(self));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.into()
|
|
||||||
}
|
|
|
@ -1,281 +0,0 @@
|
||||||
//! Traits and types for triggering events from animations.
|
|
||||||
|
|
||||||
use core::{any::Any, fmt::Debug};
|
|
||||||
|
|
||||||
use bevy_ecs::prelude::*;
|
|
||||||
use bevy_reflect::{
|
|
||||||
prelude::*, utility::NonGenericTypeInfoCell, ApplyError, DynamicTupleStruct, FromType,
|
|
||||||
GetTypeRegistration, ReflectFromPtr, ReflectKind, ReflectMut, ReflectOwned, ReflectRef,
|
|
||||||
TupleStructFieldIter, TupleStructInfo, TypeInfo, TypeRegistration, Typed, UnnamedField,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub use bevy_animation_derive::AnimationEvent;
|
|
||||||
|
|
||||||
pub(crate) fn trigger_animation_event(
|
|
||||||
entity: Entity,
|
|
||||||
time: f32,
|
|
||||||
weight: f32,
|
|
||||||
event: Box<dyn AnimationEvent>,
|
|
||||||
) -> impl Command {
|
|
||||||
move |world: &mut World| {
|
|
||||||
event.trigger(time, weight, entity, world);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// An event that can be used with animations.
|
|
||||||
/// It can be derived to trigger as an observer event,
|
|
||||||
/// if you need more complex behavior, consider
|
|
||||||
/// a manual implementation.
|
|
||||||
///
|
|
||||||
/// # Example
|
|
||||||
///
|
|
||||||
/// ```rust
|
|
||||||
/// # use bevy_animation::prelude::*;
|
|
||||||
/// # use bevy_ecs::prelude::*;
|
|
||||||
/// # use bevy_reflect::prelude::*;
|
|
||||||
/// # use bevy_asset::prelude::*;
|
|
||||||
/// #
|
|
||||||
/// #[derive(Event, AnimationEvent, Reflect, Clone)]
|
|
||||||
/// struct Say(String);
|
|
||||||
///
|
|
||||||
/// fn on_say(trigger: Trigger<Say>) {
|
|
||||||
/// println!("{}", trigger.event().0);
|
|
||||||
/// }
|
|
||||||
///
|
|
||||||
/// fn setup_animation(
|
|
||||||
/// mut commands: Commands,
|
|
||||||
/// mut animations: ResMut<Assets<AnimationClip>>,
|
|
||||||
/// mut graphs: ResMut<Assets<AnimationGraph>>,
|
|
||||||
/// ) {
|
|
||||||
/// // Create a new animation and add an event at 1.0s.
|
|
||||||
/// let mut animation = AnimationClip::default();
|
|
||||||
/// animation.add_event(1.0, Say("Hello".into()));
|
|
||||||
///
|
|
||||||
/// // Create an animation graph.
|
|
||||||
/// let (graph, animation_index) = AnimationGraph::from_clip(animations.add(animation));
|
|
||||||
///
|
|
||||||
/// // Start playing the animation.
|
|
||||||
/// let mut player = AnimationPlayer::default();
|
|
||||||
/// player.play(animation_index).repeat();
|
|
||||||
///
|
|
||||||
/// commands.spawn((AnimationGraphHandle(graphs.add(graph)), player));
|
|
||||||
/// }
|
|
||||||
/// #
|
|
||||||
/// # bevy_ecs::system::assert_is_system(setup_animation);
|
|
||||||
/// ```
|
|
||||||
#[reflect_trait]
|
|
||||||
pub trait AnimationEvent: CloneableAnimationEvent + Reflect + Send + Sync {
|
|
||||||
/// Trigger the event, targeting `entity`.
|
|
||||||
fn trigger(&self, time: f32, weight: f32, entity: Entity, world: &mut World);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// This trait exist so that manual implementors of [`AnimationEvent`]
|
|
||||||
/// do not have to implement `clone_value`.
|
|
||||||
#[diagnostic::on_unimplemented(
|
|
||||||
message = "`{Self}` does not implement `Clone`",
|
|
||||||
note = "consider annotating `{Self}` with `#[derive(Clone)]`"
|
|
||||||
)]
|
|
||||||
pub trait CloneableAnimationEvent {
|
|
||||||
/// Clone this value into a new `Box<dyn AnimationEvent>`
|
|
||||||
fn clone_value(&self) -> Box<dyn AnimationEvent>;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: AnimationEvent + Clone> CloneableAnimationEvent for T {
|
|
||||||
fn clone_value(&self) -> Box<dyn AnimationEvent> {
|
|
||||||
Box::new(self.clone())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The data that will be used to trigger an animation event.
|
|
||||||
#[derive(TypePath)]
|
|
||||||
pub(crate) struct AnimationEventData(pub(crate) Box<dyn AnimationEvent>);
|
|
||||||
|
|
||||||
impl AnimationEventData {
|
|
||||||
pub(crate) fn new(event: impl AnimationEvent) -> Self {
|
|
||||||
Self(Box::new(event))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Debug for AnimationEventData {
|
|
||||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
|
||||||
f.write_str("AnimationEventData(")?;
|
|
||||||
PartialReflect::debug(self.0.as_ref(), f)?;
|
|
||||||
f.write_str(")")?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Clone for AnimationEventData {
|
|
||||||
fn clone(&self) -> Self {
|
|
||||||
Self(CloneableAnimationEvent::clone_value(self.0.as_ref()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// We have to implement `GetTypeRegistration` manually because of the embedded
|
|
||||||
// `Box<dyn AnimationEvent>`, which can't be automatically derived yet.
|
|
||||||
impl GetTypeRegistration for AnimationEventData {
|
|
||||||
fn get_type_registration() -> TypeRegistration {
|
|
||||||
let mut registration = TypeRegistration::of::<Self>();
|
|
||||||
registration.insert::<ReflectFromPtr>(FromType::<Self>::from_type());
|
|
||||||
registration
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// We have to implement `Typed` manually because of the embedded
|
|
||||||
// `Box<dyn AnimationEvent>`, which can't be automatically derived yet.
|
|
||||||
impl Typed for AnimationEventData {
|
|
||||||
fn type_info() -> &'static TypeInfo {
|
|
||||||
static CELL: NonGenericTypeInfoCell = NonGenericTypeInfoCell::new();
|
|
||||||
CELL.get_or_set(|| {
|
|
||||||
TypeInfo::TupleStruct(TupleStructInfo::new::<Self>(&[UnnamedField::new::<()>(0)]))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// We have to implement `TupleStruct` manually because of the embedded
|
|
||||||
// `Box<dyn AnimationEvent>`, which can't be automatically derived yet.
|
|
||||||
impl TupleStruct for AnimationEventData {
|
|
||||||
fn field(&self, index: usize) -> Option<&dyn PartialReflect> {
|
|
||||||
match index {
|
|
||||||
0 => Some(self.0.as_partial_reflect()),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn field_mut(&mut self, index: usize) -> Option<&mut dyn PartialReflect> {
|
|
||||||
match index {
|
|
||||||
0 => Some(self.0.as_partial_reflect_mut()),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn field_len(&self) -> usize {
|
|
||||||
1
|
|
||||||
}
|
|
||||||
|
|
||||||
fn iter_fields(&self) -> TupleStructFieldIter {
|
|
||||||
TupleStructFieldIter::new(self)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn clone_dynamic(&self) -> DynamicTupleStruct {
|
|
||||||
DynamicTupleStruct::from_iter([PartialReflect::clone_value(&*self.0)])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// We have to implement `PartialReflect` manually because of the embedded
|
|
||||||
// `Box<dyn AnimationEvent>`, which can't be automatically derived yet.
|
|
||||||
impl PartialReflect for AnimationEventData {
|
|
||||||
#[inline]
|
|
||||||
fn get_represented_type_info(&self) -> Option<&'static TypeInfo> {
|
|
||||||
Some(<Self as Typed>::type_info())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn into_partial_reflect(self: Box<Self>) -> Box<dyn PartialReflect> {
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn as_partial_reflect(&self) -> &dyn PartialReflect {
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn as_partial_reflect_mut(&mut self) -> &mut dyn PartialReflect {
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
fn try_into_reflect(self: Box<Self>) -> Result<Box<dyn Reflect>, Box<dyn PartialReflect>> {
|
|
||||||
Ok(self)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn try_as_reflect(&self) -> Option<&dyn Reflect> {
|
|
||||||
Some(self)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn try_as_reflect_mut(&mut self) -> Option<&mut dyn Reflect> {
|
|
||||||
Some(self)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn try_apply(&mut self, value: &dyn PartialReflect) -> Result<(), ApplyError> {
|
|
||||||
if let ReflectRef::TupleStruct(struct_value) = value.reflect_ref() {
|
|
||||||
for (i, value) in struct_value.iter_fields().enumerate() {
|
|
||||||
if let Some(v) = self.field_mut(i) {
|
|
||||||
v.try_apply(value)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return Err(ApplyError::MismatchedKinds {
|
|
||||||
from_kind: value.reflect_kind(),
|
|
||||||
to_kind: ReflectKind::TupleStruct,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn reflect_ref(&self) -> ReflectRef {
|
|
||||||
ReflectRef::TupleStruct(self)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn reflect_mut(&mut self) -> ReflectMut {
|
|
||||||
ReflectMut::TupleStruct(self)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn reflect_owned(self: Box<Self>) -> ReflectOwned {
|
|
||||||
ReflectOwned::TupleStruct(self)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn clone_value(&self) -> Box<dyn PartialReflect> {
|
|
||||||
Box::new(Clone::clone(self))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// We have to implement `Reflect` manually because of the embedded
|
|
||||||
// `Box<dyn AnimationEvent>`, which can't be automatically derived yet.
|
|
||||||
impl Reflect for AnimationEventData {
|
|
||||||
#[inline]
|
|
||||||
fn into_any(self: Box<Self>) -> Box<dyn Any> {
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn as_any(&self) -> &dyn Any {
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn as_any_mut(&mut self) -> &mut dyn Any {
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn into_reflect(self: Box<Self>) -> Box<dyn Reflect> {
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn as_reflect(&self) -> &dyn Reflect {
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn as_reflect_mut(&mut self) -> &mut dyn Reflect {
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn set(&mut self, value: Box<dyn Reflect>) -> Result<(), Box<dyn Reflect>> {
|
|
||||||
*self = value.take()?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// We have to implement `FromReflect` manually because of the embedded
|
|
||||||
// `Box<dyn AnimationEvent>`, which can't be automatically derived yet.
|
|
||||||
impl FromReflect for AnimationEventData {
|
|
||||||
fn from_reflect(reflect: &dyn PartialReflect) -> Option<Self> {
|
|
||||||
Some(reflect.try_downcast_ref::<AnimationEventData>()?.clone())
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -11,13 +11,11 @@ extern crate alloc;
|
||||||
|
|
||||||
pub mod animatable;
|
pub mod animatable;
|
||||||
pub mod animation_curves;
|
pub mod animation_curves;
|
||||||
pub mod animation_event;
|
|
||||||
pub mod gltf_curves;
|
pub mod gltf_curves;
|
||||||
pub mod graph;
|
pub mod graph;
|
||||||
pub mod transition;
|
pub mod transition;
|
||||||
mod util;
|
mod util;
|
||||||
|
|
||||||
use animation_event::{trigger_animation_event, AnimationEvent, AnimationEventData};
|
|
||||||
use core::{
|
use core::{
|
||||||
any::{Any, TypeId},
|
any::{Any, TypeId},
|
||||||
cell::RefCell,
|
cell::RefCell,
|
||||||
|
@ -30,7 +28,7 @@ use prelude::AnimationCurveEvaluator;
|
||||||
|
|
||||||
use crate::graph::{AnimationGraphHandle, ThreadedAnimationGraphs};
|
use crate::graph::{AnimationGraphHandle, ThreadedAnimationGraphs};
|
||||||
|
|
||||||
use bevy_app::{App, Plugin, PostUpdate};
|
use bevy_app::{Animation, App, Plugin, PostUpdate};
|
||||||
use bevy_asset::{Asset, AssetApp, Assets};
|
use bevy_asset::{Asset, AssetApp, Assets};
|
||||||
use bevy_core::Name;
|
use bevy_core::Name;
|
||||||
use bevy_ecs::{
|
use bevy_ecs::{
|
||||||
|
@ -64,12 +62,8 @@ use uuid::Uuid;
|
||||||
pub mod prelude {
|
pub mod prelude {
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
pub use crate::{
|
pub use crate::{
|
||||||
animatable::*,
|
animatable::*, animation_curves::*, graph::*, transition::*, AnimationClip,
|
||||||
animation_curves::*,
|
AnimationPlayer, AnimationPlugin, VariableCurve,
|
||||||
animation_event::{AnimationEvent, ReflectAnimationEvent},
|
|
||||||
graph::*,
|
|
||||||
transition::*,
|
|
||||||
AnimationClip, AnimationPlayer, AnimationPlugin, VariableCurve,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -78,6 +72,7 @@ use crate::{
|
||||||
graph::{AnimationGraph, AnimationGraphAssetLoader, AnimationNodeIndex},
|
graph::{AnimationGraph, AnimationGraphAssetLoader, AnimationNodeIndex},
|
||||||
transition::{advance_transitions, expire_completed_transitions, AnimationTransitions},
|
transition::{advance_transitions, expire_completed_transitions, AnimationTransitions},
|
||||||
};
|
};
|
||||||
|
use alloc::sync::Arc;
|
||||||
|
|
||||||
/// The [UUID namespace] of animation targets (e.g. bones).
|
/// The [UUID namespace] of animation targets (e.g. bones).
|
||||||
///
|
///
|
||||||
|
@ -289,7 +284,34 @@ pub struct AnimationClip {
|
||||||
#[derive(Reflect, Debug, Clone)]
|
#[derive(Reflect, Debug, Clone)]
|
||||||
struct TimedAnimationEvent {
|
struct TimedAnimationEvent {
|
||||||
time: f32,
|
time: f32,
|
||||||
event: AnimationEventData,
|
event: AnimationEvent,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Reflect, Debug, Clone)]
|
||||||
|
struct AnimationEvent {
|
||||||
|
#[reflect(ignore)]
|
||||||
|
trigger: AnimationEventFn,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AnimationEvent {
|
||||||
|
fn trigger(&self, commands: &mut Commands, entity: Entity, time: f32, weight: f32) {
|
||||||
|
(self.trigger.0)(commands, entity, time, weight);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Reflect, Clone)]
|
||||||
|
struct AnimationEventFn(Arc<dyn Fn(&mut Commands, Entity, f32, f32) + Send + Sync>);
|
||||||
|
|
||||||
|
impl Default for AnimationEventFn {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self(Arc::new(|_commands, _entity, _time, _weight| {}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Debug for AnimationEventFn {
|
||||||
|
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||||
|
f.debug_tuple("AnimationEventFn").finish()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Reflect, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone)]
|
#[derive(Reflect, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone)]
|
||||||
|
@ -472,9 +494,24 @@ impl AnimationClip {
|
||||||
.push(variable_curve);
|
.push(variable_curve);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Add an [`AnimationEvent`] to an [`AnimationTarget`] named by an [`AnimationTargetId`].
|
/// Add a untargeted [`Event`] to this [`AnimationClip`].
|
||||||
///
|
///
|
||||||
/// The `event` will trigger on the entity matching the target once the `time` (in seconds)
|
/// The `event` will be cloned and triggered on the [`AnimationPlayer`] entity once the `time` (in seconds)
|
||||||
|
/// is reached in the animation.
|
||||||
|
///
|
||||||
|
/// See also [`add_event_to_target`](Self::add_event_to_target).
|
||||||
|
pub fn add_event(&mut self, time: f32, event: impl Event + Clone) {
|
||||||
|
self.add_event_fn(
|
||||||
|
time,
|
||||||
|
move |commands: &mut Commands, entity: Entity, _time: f32, _weight: f32| {
|
||||||
|
commands.entity(entity).trigger(event.clone());
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add an [`Event`] to an [`AnimationTarget`] named by an [`AnimationTargetId`].
|
||||||
|
///
|
||||||
|
/// The `event` will be cloned and triggered on the entity matching the target once the `time` (in seconds)
|
||||||
/// is reached in the animation.
|
/// is reached in the animation.
|
||||||
///
|
///
|
||||||
/// Use [`add_event`](Self::add_event) instead if you don't have a specific target.
|
/// Use [`add_event`](Self::add_event) instead if you don't have a specific target.
|
||||||
|
@ -482,26 +519,69 @@ impl AnimationClip {
|
||||||
&mut self,
|
&mut self,
|
||||||
target_id: AnimationTargetId,
|
target_id: AnimationTargetId,
|
||||||
time: f32,
|
time: f32,
|
||||||
event: impl AnimationEvent,
|
event: impl Event + Clone,
|
||||||
) {
|
) {
|
||||||
self.add_event_to_target_inner(AnimationEventTarget::Node(target_id), time, event);
|
self.add_event_fn_to_target(
|
||||||
|
target_id,
|
||||||
|
time,
|
||||||
|
move |commands: &mut Commands, entity: Entity, _time: f32, _weight: f32| {
|
||||||
|
commands.entity(entity).trigger(event.clone());
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Add a untargeted [`AnimationEvent`] to this [`AnimationClip`].
|
/// Add a untargeted event function to this [`AnimationClip`].
|
||||||
///
|
///
|
||||||
/// The `event` will trigger on the [`AnimationPlayer`] entity once the `time` (in seconds)
|
/// The `func` will trigger on the [`AnimationPlayer`] entity once the `time` (in seconds)
|
||||||
/// is reached in the animation.
|
/// is reached in the animation.
|
||||||
///
|
///
|
||||||
|
/// For a simpler [`Event`]-based alternative, see [`AnimationClip::add_event`].
|
||||||
/// See also [`add_event_to_target`](Self::add_event_to_target).
|
/// See also [`add_event_to_target`](Self::add_event_to_target).
|
||||||
pub fn add_event(&mut self, time: f32, event: impl AnimationEvent) {
|
///
|
||||||
self.add_event_to_target_inner(AnimationEventTarget::Root, time, event);
|
/// ```
|
||||||
|
/// # use bevy_animation::AnimationClip;
|
||||||
|
/// # let mut clip = AnimationClip::default();
|
||||||
|
/// clip.add_event_fn(1.0, |commands, entity, time, weight| {
|
||||||
|
/// println!("Animation Event Triggered {entity:#?} at time {time} with weight {weight}");
|
||||||
|
/// })
|
||||||
|
/// ```
|
||||||
|
pub fn add_event_fn(
|
||||||
|
&mut self,
|
||||||
|
time: f32,
|
||||||
|
func: impl Fn(&mut Commands, Entity, f32, f32) + Send + Sync + 'static,
|
||||||
|
) {
|
||||||
|
self.add_event_internal(AnimationEventTarget::Root, time, func);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_event_to_target_inner(
|
/// Add an event function to an [`AnimationTarget`] named by an [`AnimationTargetId`].
|
||||||
|
///
|
||||||
|
/// The `func` will trigger on the entity matching the target once the `time` (in seconds)
|
||||||
|
/// is reached in the animation.
|
||||||
|
///
|
||||||
|
/// For a simpler [`Event`]-based alternative, see [`AnimationClip::add_event_to_target`].
|
||||||
|
/// Use [`add_event`](Self::add_event) instead if you don't have a specific target.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use bevy_animation::{AnimationClip, AnimationTargetId};
|
||||||
|
/// # let mut clip = AnimationClip::default();
|
||||||
|
/// clip.add_event_fn_to_target(AnimationTargetId::from_iter(["Arm", "Hand"]), 1.0, |commands, entity, time, weight| {
|
||||||
|
/// println!("Animation Event Triggered {entity:#?} at time {time} with weight {weight}");
|
||||||
|
/// })
|
||||||
|
/// ```
|
||||||
|
pub fn add_event_fn_to_target(
|
||||||
|
&mut self,
|
||||||
|
target_id: AnimationTargetId,
|
||||||
|
time: f32,
|
||||||
|
func: impl Fn(&mut Commands, Entity, f32, f32) + Send + Sync + 'static,
|
||||||
|
) {
|
||||||
|
self.add_event_internal(AnimationEventTarget::Node(target_id), time, func);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_event_internal(
|
||||||
&mut self,
|
&mut self,
|
||||||
target: AnimationEventTarget,
|
target: AnimationEventTarget,
|
||||||
time: f32,
|
time: f32,
|
||||||
event: impl AnimationEvent,
|
trigger_fn: impl Fn(&mut Commands, Entity, f32, f32) + Send + Sync + 'static,
|
||||||
) {
|
) {
|
||||||
self.duration = self.duration.max(time);
|
self.duration = self.duration.max(time);
|
||||||
let triggers = self.events.entry(target).or_default();
|
let triggers = self.events.entry(target).or_default();
|
||||||
|
@ -510,7 +590,9 @@ impl AnimationClip {
|
||||||
index,
|
index,
|
||||||
TimedAnimationEvent {
|
TimedAnimationEvent {
|
||||||
time,
|
time,
|
||||||
event: AnimationEventData::new(event),
|
event: AnimationEvent {
|
||||||
|
trigger: AnimationEventFn(Arc::new(trigger_fn)),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
@ -988,12 +1070,7 @@ fn trigger_untargeted_animation_events(
|
||||||
};
|
};
|
||||||
|
|
||||||
for TimedAnimationEvent { time, event } in triggered_events.iter() {
|
for TimedAnimationEvent { time, event } in triggered_events.iter() {
|
||||||
commands.queue(trigger_animation_event(
|
event.trigger(&mut commands, entity, *time, active_animation.weight);
|
||||||
entity,
|
|
||||||
*time,
|
|
||||||
active_animation.weight,
|
|
||||||
event.clone().0,
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1195,12 +1272,12 @@ pub fn animate_targets(
|
||||||
for TimedAnimationEvent { time, event } in
|
for TimedAnimationEvent { time, event } in
|
||||||
triggered_events.iter()
|
triggered_events.iter()
|
||||||
{
|
{
|
||||||
commands.queue(trigger_animation_event(
|
event.trigger(
|
||||||
|
&mut commands,
|
||||||
entity,
|
entity,
|
||||||
*time,
|
*time,
|
||||||
active_animation.weight,
|
active_animation.weight,
|
||||||
event.clone().0,
|
);
|
||||||
));
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -1252,10 +1329,6 @@ pub fn animate_targets(
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Animation system set
|
|
||||||
#[derive(SystemSet, Debug, Hash, PartialEq, Eq, Clone)]
|
|
||||||
pub struct Animation;
|
|
||||||
|
|
||||||
/// Adds animation support to an app
|
/// Adds animation support to an app
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct AnimationPlugin;
|
pub struct AnimationPlugin;
|
||||||
|
@ -1573,12 +1646,6 @@ mod tests {
|
||||||
#[derive(Event, Reflect, Clone)]
|
#[derive(Event, Reflect, Clone)]
|
||||||
struct A;
|
struct A;
|
||||||
|
|
||||||
impl AnimationEvent for A {
|
|
||||||
fn trigger(&self, _time: f32, _weight: f32, target: Entity, world: &mut World) {
|
|
||||||
world.entity_mut(target).trigger(self.clone());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
fn assert_triggered_events_with(
|
fn assert_triggered_events_with(
|
||||||
active_animation: &ActiveAnimation,
|
active_animation: &ActiveAnimation,
|
||||||
|
|
|
@ -181,6 +181,10 @@ pub struct PostUpdate;
|
||||||
#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)]
|
#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)]
|
||||||
pub struct Last;
|
pub struct Last;
|
||||||
|
|
||||||
|
/// Animation system set. This exists in [`PostUpdate`].
|
||||||
|
#[derive(SystemSet, Debug, Hash, PartialEq, Eq, Clone)]
|
||||||
|
pub struct Animation;
|
||||||
|
|
||||||
/// Defines the schedules to be run for the [`Main`] schedule, including
|
/// Defines the schedules to be run for the [`Main`] schedule, including
|
||||||
/// their order.
|
/// their order.
|
||||||
#[derive(Resource, Debug)]
|
#[derive(Resource, Debug)]
|
||||||
|
|
|
@ -71,7 +71,7 @@ pub mod prelude {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
use bevy_app::prelude::*;
|
use bevy_app::{prelude::*, Animation};
|
||||||
use bevy_asset::AssetApp;
|
use bevy_asset::AssetApp;
|
||||||
#[cfg(feature = "default_font")]
|
#[cfg(feature = "default_font")]
|
||||||
use bevy_asset::{load_internal_binary_asset, Handle};
|
use bevy_asset::{load_internal_binary_asset, Handle};
|
||||||
|
@ -138,7 +138,8 @@ impl Plugin for TextPlugin {
|
||||||
calculate_bounds_text2d.in_set(VisibilitySystems::CalculateBounds),
|
calculate_bounds_text2d.in_set(VisibilitySystems::CalculateBounds),
|
||||||
)
|
)
|
||||||
.chain()
|
.chain()
|
||||||
.in_set(Update2dText),
|
.in_set(Update2dText)
|
||||||
|
.after(Animation),
|
||||||
)
|
)
|
||||||
.add_systems(Last, trim_cosmic_cache);
|
.add_systems(Last, trim_cosmic_cache);
|
||||||
|
|
||||||
|
|
|
@ -25,7 +25,6 @@ bevy_reflect = { path = "../bevy_reflect", version = "0.15.0-dev", features = [
|
||||||
"bevy",
|
"bevy",
|
||||||
] }
|
] }
|
||||||
bevy_render = { path = "../bevy_render", version = "0.15.0-dev" }
|
bevy_render = { path = "../bevy_render", version = "0.15.0-dev" }
|
||||||
bevy_animation = { path = "../bevy_animation", version = "0.15.0-dev" }
|
|
||||||
bevy_sprite = { path = "../bevy_sprite", version = "0.15.0-dev" }
|
bevy_sprite = { path = "../bevy_sprite", version = "0.15.0-dev" }
|
||||||
bevy_text = { path = "../bevy_text", version = "0.15.0-dev" }
|
bevy_text = { path = "../bevy_text", version = "0.15.0-dev" }
|
||||||
bevy_picking = { path = "../bevy_picking", version = "0.15.0-dev", optional = true }
|
bevy_picking = { path = "../bevy_picking", version = "0.15.0-dev", optional = true }
|
||||||
|
|
|
@ -66,7 +66,7 @@ pub mod prelude {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
use bevy_app::prelude::*;
|
use bevy_app::{prelude::*, Animation};
|
||||||
use bevy_ecs::prelude::*;
|
use bevy_ecs::prelude::*;
|
||||||
use bevy_input::InputSystem;
|
use bevy_input::InputSystem;
|
||||||
use bevy_render::{
|
use bevy_render::{
|
||||||
|
@ -171,9 +171,7 @@ impl Plugin for UiPlugin {
|
||||||
PostUpdate,
|
PostUpdate,
|
||||||
(
|
(
|
||||||
CameraUpdateSystem,
|
CameraUpdateSystem,
|
||||||
UiSystem::Prepare
|
UiSystem::Prepare.before(UiSystem::Stack).after(Animation),
|
||||||
.before(UiSystem::Stack)
|
|
||||||
.after(bevy_animation::Animation),
|
|
||||||
UiSystem::Layout,
|
UiSystem::Layout,
|
||||||
UiSystem::PostLayout,
|
UiSystem::PostLayout,
|
||||||
)
|
)
|
||||||
|
|
|
@ -34,8 +34,7 @@ struct Animations {
|
||||||
graph: Handle<AnimationGraph>,
|
graph: Handle<AnimationGraph>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Event, AnimationEvent, Reflect, Clone)]
|
#[derive(Event, Reflect, Clone)]
|
||||||
#[reflect(AnimationEvent)]
|
|
||||||
struct OnStep;
|
struct OnStep;
|
||||||
|
|
||||||
fn observe_on_step(
|
fn observe_on_step(
|
||||||
|
|
|
@ -11,39 +11,27 @@ fn main() {
|
||||||
.add_plugins(DefaultPlugins)
|
.add_plugins(DefaultPlugins)
|
||||||
.add_event::<MessageEvent>()
|
.add_event::<MessageEvent>()
|
||||||
.add_systems(Startup, setup)
|
.add_systems(Startup, setup)
|
||||||
.add_systems(PreUpdate, (animate_text_opacity, edit_message))
|
.add_systems(Update, animate_text_opacity)
|
||||||
|
.add_observer(edit_message)
|
||||||
.run();
|
.run();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Component)]
|
#[derive(Component)]
|
||||||
struct MessageText;
|
struct MessageText;
|
||||||
|
|
||||||
#[derive(Event, Reflect, Clone)]
|
#[derive(Event, Clone)]
|
||||||
#[reflect(AnimationEvent)]
|
|
||||||
struct MessageEvent {
|
struct MessageEvent {
|
||||||
value: String,
|
value: String,
|
||||||
color: Color,
|
color: Color,
|
||||||
}
|
}
|
||||||
|
|
||||||
// AnimationEvent can also be derived, but doing so will
|
|
||||||
// trigger it as an observer event which is triggered in PostUpdate.
|
|
||||||
// We need to set the message text before that so it is
|
|
||||||
// updated before rendering without a one frame delay.
|
|
||||||
impl AnimationEvent for MessageEvent {
|
|
||||||
fn trigger(&self, _time: f32, _weight: f32, _entity: Entity, world: &mut World) {
|
|
||||||
world.send_event(self.clone());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn edit_message(
|
fn edit_message(
|
||||||
mut event_reader: EventReader<MessageEvent>,
|
trigger: Trigger<MessageEvent>,
|
||||||
text: Single<(&mut Text2d, &mut TextColor), With<MessageText>>,
|
text: Single<(&mut Text2d, &mut TextColor), With<MessageText>>,
|
||||||
) {
|
) {
|
||||||
let (mut text, mut color) = text.into_inner();
|
let (mut text, mut color) = text.into_inner();
|
||||||
for event in event_reader.read() {
|
text.0 = trigger.event().value.clone();
|
||||||
text.0 = event.value.clone();
|
color.0 = trigger.event().color;
|
||||||
color.0 = event.color;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn setup(
|
fn setup(
|
||||||
|
|
|
@ -34,7 +34,6 @@ crates=(
|
||||||
bevy_core_pipeline
|
bevy_core_pipeline
|
||||||
bevy_input
|
bevy_input
|
||||||
bevy_gilrs
|
bevy_gilrs
|
||||||
bevy_animation/derive
|
|
||||||
bevy_animation
|
bevy_animation
|
||||||
bevy_pbr
|
bevy_pbr
|
||||||
bevy_gltf
|
bevy_gltf
|
||||||
|
|
Loading…
Add table
Reference in a new issue