mirror of
https://github.com/bevyengine/bevy
synced 2024-11-22 12:43:34 +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]
|
||||
# bevy
|
||||
bevy_animation_derive = { path = "derive", 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_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 animation_curves;
|
||||
pub mod animation_event;
|
||||
pub mod gltf_curves;
|
||||
pub mod graph;
|
||||
pub mod transition;
|
||||
mod util;
|
||||
|
||||
use animation_event::{trigger_animation_event, AnimationEvent, AnimationEventData};
|
||||
use core::{
|
||||
any::{Any, TypeId},
|
||||
cell::RefCell,
|
||||
|
@ -30,7 +28,7 @@ use prelude::AnimationCurveEvaluator;
|
|||
|
||||
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_core::Name;
|
||||
use bevy_ecs::{
|
||||
|
@ -64,12 +62,8 @@ use uuid::Uuid;
|
|||
pub mod prelude {
|
||||
#[doc(hidden)]
|
||||
pub use crate::{
|
||||
animatable::*,
|
||||
animation_curves::*,
|
||||
animation_event::{AnimationEvent, ReflectAnimationEvent},
|
||||
graph::*,
|
||||
transition::*,
|
||||
AnimationClip, AnimationPlayer, AnimationPlugin, VariableCurve,
|
||||
animatable::*, animation_curves::*, graph::*, transition::*, AnimationClip,
|
||||
AnimationPlayer, AnimationPlugin, VariableCurve,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -78,6 +72,7 @@ use crate::{
|
|||
graph::{AnimationGraph, AnimationGraphAssetLoader, AnimationNodeIndex},
|
||||
transition::{advance_transitions, expire_completed_transitions, AnimationTransitions},
|
||||
};
|
||||
use alloc::sync::Arc;
|
||||
|
||||
/// The [UUID namespace] of animation targets (e.g. bones).
|
||||
///
|
||||
|
@ -289,7 +284,34 @@ pub struct AnimationClip {
|
|||
#[derive(Reflect, Debug, Clone)]
|
||||
struct TimedAnimationEvent {
|
||||
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)]
|
||||
|
@ -472,9 +494,24 @@ impl AnimationClip {
|
|||
.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.
|
||||
///
|
||||
/// Use [`add_event`](Self::add_event) instead if you don't have a specific target.
|
||||
|
@ -482,26 +519,69 @@ impl AnimationClip {
|
|||
&mut self,
|
||||
target_id: AnimationTargetId,
|
||||
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.
|
||||
///
|
||||
/// For a simpler [`Event`]-based alternative, see [`AnimationClip::add_event`].
|
||||
/// 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,
|
||||
target: AnimationEventTarget,
|
||||
time: f32,
|
||||
event: impl AnimationEvent,
|
||||
trigger_fn: impl Fn(&mut Commands, Entity, f32, f32) + Send + Sync + 'static,
|
||||
) {
|
||||
self.duration = self.duration.max(time);
|
||||
let triggers = self.events.entry(target).or_default();
|
||||
|
@ -510,7 +590,9 @@ impl AnimationClip {
|
|||
index,
|
||||
TimedAnimationEvent {
|
||||
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() {
|
||||
commands.queue(trigger_animation_event(
|
||||
entity,
|
||||
*time,
|
||||
active_animation.weight,
|
||||
event.clone().0,
|
||||
));
|
||||
event.trigger(&mut commands, entity, *time, active_animation.weight);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1195,12 +1272,12 @@ pub fn animate_targets(
|
|||
for TimedAnimationEvent { time, event } in
|
||||
triggered_events.iter()
|
||||
{
|
||||
commands.queue(trigger_animation_event(
|
||||
event.trigger(
|
||||
&mut commands,
|
||||
entity,
|
||||
*time,
|
||||
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
|
||||
#[derive(Default)]
|
||||
pub struct AnimationPlugin;
|
||||
|
@ -1573,12 +1646,6 @@ mod tests {
|
|||
#[derive(Event, Reflect, Clone)]
|
||||
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]
|
||||
fn assert_triggered_events_with(
|
||||
active_animation: &ActiveAnimation,
|
||||
|
|
|
@ -181,6 +181,10 @@ pub struct PostUpdate;
|
|||
#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)]
|
||||
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
|
||||
/// their order.
|
||||
#[derive(Resource, Debug)]
|
||||
|
|
|
@ -71,7 +71,7 @@ pub mod prelude {
|
|||
};
|
||||
}
|
||||
|
||||
use bevy_app::prelude::*;
|
||||
use bevy_app::{prelude::*, Animation};
|
||||
use bevy_asset::AssetApp;
|
||||
#[cfg(feature = "default_font")]
|
||||
use bevy_asset::{load_internal_binary_asset, Handle};
|
||||
|
@ -138,7 +138,8 @@ impl Plugin for TextPlugin {
|
|||
calculate_bounds_text2d.in_set(VisibilitySystems::CalculateBounds),
|
||||
)
|
||||
.chain()
|
||||
.in_set(Update2dText),
|
||||
.in_set(Update2dText)
|
||||
.after(Animation),
|
||||
)
|
||||
.add_systems(Last, trim_cosmic_cache);
|
||||
|
||||
|
|
|
@ -25,7 +25,6 @@ bevy_reflect = { path = "../bevy_reflect", version = "0.15.0-dev", features = [
|
|||
"bevy",
|
||||
] }
|
||||
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_text = { path = "../bevy_text", version = "0.15.0-dev" }
|
||||
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_input::InputSystem;
|
||||
use bevy_render::{
|
||||
|
@ -171,9 +171,7 @@ impl Plugin for UiPlugin {
|
|||
PostUpdate,
|
||||
(
|
||||
CameraUpdateSystem,
|
||||
UiSystem::Prepare
|
||||
.before(UiSystem::Stack)
|
||||
.after(bevy_animation::Animation),
|
||||
UiSystem::Prepare.before(UiSystem::Stack).after(Animation),
|
||||
UiSystem::Layout,
|
||||
UiSystem::PostLayout,
|
||||
)
|
||||
|
|
|
@ -34,8 +34,7 @@ struct Animations {
|
|||
graph: Handle<AnimationGraph>,
|
||||
}
|
||||
|
||||
#[derive(Event, AnimationEvent, Reflect, Clone)]
|
||||
#[reflect(AnimationEvent)]
|
||||
#[derive(Event, Reflect, Clone)]
|
||||
struct OnStep;
|
||||
|
||||
fn observe_on_step(
|
||||
|
|
|
@ -11,39 +11,27 @@ fn main() {
|
|||
.add_plugins(DefaultPlugins)
|
||||
.add_event::<MessageEvent>()
|
||||
.add_systems(Startup, setup)
|
||||
.add_systems(PreUpdate, (animate_text_opacity, edit_message))
|
||||
.add_systems(Update, animate_text_opacity)
|
||||
.add_observer(edit_message)
|
||||
.run();
|
||||
}
|
||||
|
||||
#[derive(Component)]
|
||||
struct MessageText;
|
||||
|
||||
#[derive(Event, Reflect, Clone)]
|
||||
#[reflect(AnimationEvent)]
|
||||
#[derive(Event, Clone)]
|
||||
struct MessageEvent {
|
||||
value: String,
|
||||
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(
|
||||
mut event_reader: EventReader<MessageEvent>,
|
||||
trigger: Trigger<MessageEvent>,
|
||||
text: Single<(&mut Text2d, &mut TextColor), With<MessageText>>,
|
||||
) {
|
||||
let (mut text, mut color) = text.into_inner();
|
||||
for event in event_reader.read() {
|
||||
text.0 = event.value.clone();
|
||||
color.0 = event.color;
|
||||
}
|
||||
text.0 = trigger.event().value.clone();
|
||||
color.0 = trigger.event().color;
|
||||
}
|
||||
|
||||
fn setup(
|
||||
|
|
|
@ -34,7 +34,6 @@ crates=(
|
|||
bevy_core_pipeline
|
||||
bevy_input
|
||||
bevy_gilrs
|
||||
bevy_animation/derive
|
||||
bevy_animation
|
||||
bevy_pbr
|
||||
bevy_gltf
|
||||
|
|
Loading…
Reference in a new issue