diff --git a/Cargo.toml b/Cargo.toml index 9bf54e07..9b51aff6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -93,13 +93,13 @@ path = "examples/demo/hold.rs" required-features = ["bevy_sprite", "bevy_asset"] [[example]] -name = "event" -path = "examples/demo/event.rs" +name = "message" +path = "examples/demo/message.rs" required-features = ["bevy_sprite", "bevy_asset"] [[example]] -name = "entity_event" -path = "examples/demo/entity_event.rs" +name = "events" +path = "examples/demo/events.rs" [[example]] name = "sprite_sheet" diff --git a/README.md b/README.md index 95f9d833..37ebd304 100644 --- a/README.md +++ b/README.md @@ -175,7 +175,7 @@ https://github.com/Multirious/bevy_tween/assets/77918086/33a297a6-19f2-4146-a906 --- -`cargo run --example event -F bevy/bevy_winit`
+`cargo run --example message -F bevy/bevy_winit`
Showcasing the tween event feature.
https://github.com/Multirious/bevy_tween/assets/77918086/9507c467-6428-4aed-bd00-511f05e6e951 diff --git a/examples/demo/entity_event.rs b/examples/demo/events.rs similarity index 63% rename from examples/demo/entity_event.rs rename to examples/demo/events.rs index 0d350d2f..5d62d9bb 100644 --- a/examples/demo/entity_event.rs +++ b/examples/demo/events.rs @@ -10,7 +10,7 @@ fn main() { .add_plugins((MinimalPlugins, DefaultTweenPlugins::default())) .add_systems(Startup, setup) .add_observer(|trigger: On>| { - println!("TweenEvent: {}", trigger.data) + println!("TweenEvent on {}: {}", trigger.entity, trigger.data) }) .add_observer(|trigger: On| { if trigger.is_completed() { @@ -28,10 +28,15 @@ fn setup(mut commands: Commands) { .repeat(Repeat::times(5)) .insert(forward(Duration::from_secs_f32(0.5))); - commands.animation().insert(sequence(( - forward(Duration::from_secs_f32(3.)), - event("tween"), - forward(Duration::from_secs_f32(0.5)), - event("event"), - ))); + commands + .animation() + .insert(sequence(( + forward(Duration::from_secs_f32(3.)), + event("tween"), + forward(Duration::from_secs_f32(0.5)), + event("event"), + ))) + .observe(|trigger: On>| { + println!("TweenEvent on root: {}", trigger.data) + }); } diff --git a/examples/demo/event.rs b/examples/demo/message.rs similarity index 98% rename from examples/demo/event.rs rename to examples/demo/message.rs index 4505cee0..fa400888 100644 --- a/examples/demo/event.rs +++ b/examples/demo/message.rs @@ -65,7 +65,7 @@ fn setup(mut commands: Commands, asset_server: Res) { .animation() .repeat(Repeat::Infinitely) .insert(sequence(( - event("bump"), + message("bump"), tween( secs(1.), EaseKind::ExponentialIn, @@ -76,8 +76,8 @@ fn setup(mut commands: Commands, asset_server: Res) { ), ), backward(secs(0.2)), - event_for(secs(0.2), "small_boom"), - event("boom"), + message_for(secs(0.2), "small_boom"), + message("boom"), tween( secs(1.), EaseKind::CircularOut, diff --git a/src/combinator/animation_combinators.rs b/src/combinator/animation_combinators.rs index 9eb9b79f..735fad1b 100644 --- a/src/combinator/animation_combinators.rs +++ b/src/combinator/animation_combinators.rs @@ -1,5 +1,8 @@ use super::AnimationCommands; -use crate::prelude::TweenEventData; +use crate::{ + prelude::TweenEventData, + tween_event::{EventEmittingTween, MessageEmittingTween}, +}; use bevy::prelude::*; use bevy_time_runner::TimeSpan; use std::time::Duration; @@ -77,7 +80,47 @@ where } } -/// Combinator for creating an tween event. +/// Combinator for creating both a tween event and message. +/// +/// Event will be emitted at current position. +/// +/// Position is not mutated because the event has no length. +/// +///
+/// +/// Your event should be registered with [`TweenEventPlugin`](crate::tween_event::TweenEventPlugin). +/// +///
+pub fn send( + event_data: Data, +) -> impl FnOnce(&mut AnimationCommands, &mut Duration) +where + Data: Send + Sync + 'static, +{ + dispatch(event_data, (MessageEmittingTween, EventEmittingTween)) +} + +/// Combinator for dispatching a tween message. +/// +/// Message will be emitted at current position. +/// +/// Position is not mutated because the event has no length. +/// +///
+/// +/// Your event should be registered with [`TweenEventPlugin`](crate::tween_event::TweenEventPlugin). +/// +///
+pub fn message( + event_data: Data, +) -> impl FnOnce(&mut AnimationCommands, &mut Duration) +where + Data: Send + Sync + 'static, +{ + dispatch(event_data, MessageEmittingTween) +} + +/// Combinator for creating a tween event on the animation root containing a [`bevy_time_runner::TimeRunner`]. /// /// Event will be emitted at current position. /// @@ -91,6 +134,21 @@ where pub fn event( event_data: Data, ) -> impl FnOnce(&mut AnimationCommands, &mut Duration) +where + Data: Send + Sync + 'static, +{ + dispatch(event_data, EventEmittingTween) +} + +/// Combinator for dispatching a tween event. +/// +/// `with` informs how the tween event is dispatched. +/// +/// Event will be emitted at current position. +fn dispatch( + event_data: Data, + with: impl Bundle, +) -> impl FnOnce(&mut AnimationCommands, &mut Duration) where Data: Send + Sync + 'static, { @@ -98,11 +156,54 @@ where a.spawn(( TimeSpan::try_from(*pos..=*pos).unwrap(), TweenEventData::with_data(event_data), + with, )); } } -/// Combinator for creating an tween event. +/// Combinator for creating both a tween event and message. +/// +/// Event will be emitted at the provided position. +/// +/// Position is not mutated because the operation is not relative. +/// +///
+/// +/// Your event should be registered with [`TweenEventPlugin`](crate::tween_event::TweenEventPlugin). +/// +///
+pub fn send_at( + at: Duration, + event_data: Data, +) -> impl FnOnce(&mut AnimationCommands, &mut Duration) +where + Data: Send + Sync + 'static, +{ + dispatch_at(at, event_data, (MessageEmittingTween, EventEmittingTween)) +} + +/// Combinator for dispatching a tween message. +/// +/// Message will be emitted at the provided position. +/// +/// Position is not mutated because the operation is not relative. +/// +///
+/// +/// Your event should be registered with [`TweenEventPlugin`](crate::tween_event::TweenEventPlugin). +/// +///
+pub fn message_at( + at: Duration, + event_data: Data, +) -> impl FnOnce(&mut AnimationCommands, &mut Duration) +where + Data: Send + Sync + 'static, +{ + dispatch_at(at, event_data, MessageEmittingTween) +} + +/// Combinator for creating an tween event on the animation root containing a [`bevy_time_runner::TimeRunner`]. /// /// Event will be emitted at the provided position. /// @@ -117,6 +218,22 @@ pub fn event_at( at: Duration, event_data: Data, ) -> impl FnOnce(&mut AnimationCommands, &mut Duration) +where + Data: Send + Sync + 'static, +{ + dispatch_at(at, event_data, EventEmittingTween) +} + +/// Combinator for dispatching a tween event. +/// +/// `with` informs how the tween event is dispatched. +/// +/// Event will be emitted at the provided position. +fn dispatch_at( + at: Duration, + event_data: Data, + with: impl Bundle, +) -> impl FnOnce(&mut AnimationCommands, &mut Duration) where Data: Send + Sync + 'static, { @@ -124,11 +241,58 @@ where a.spawn(( TimeSpan::try_from(at..=at).unwrap(), TweenEventData::with_data(event_data), + with, )); } } -/// Combinator for creating an tween event. +/// Combinator for creating both a tween event and message. +/// +/// Event will be emitted at the current position for provided length every frame. +/// +/// Position is at the end of the event. +/// +///
+/// +/// Your event should be registered with [`TweenEventPlugin`](crate::tween_event::TweenEventPlugin). +/// +///
+pub fn send_for( + length: Duration, + event_data: Data, +) -> impl FnOnce(&mut AnimationCommands, &mut Duration) +where + Data: Send + Sync + 'static, +{ + dispatch_for( + length, + event_data, + (MessageEmittingTween, EventEmittingTween), + ) +} + +/// Combinator for dispatching a tween message. +/// +/// Message will be emitted at the current position for provided length every frame. +/// +/// Position is at the end of the event. +/// +///
+/// +/// Your event should be registered with [`TweenEventPlugin`](crate::tween_event::TweenEventPlugin). +/// +///
+pub fn message_for( + length: Duration, + event_data: Data, +) -> impl FnOnce(&mut AnimationCommands, &mut Duration) +where + Data: Send + Sync + 'static, +{ + dispatch_for(length, event_data, MessageEmittingTween) +} + +/// Combinator for creating an tween event on the animation root containing a [`bevy_time_runner::TimeRunner`]. /// /// Event will be emitted at the current position for provided length every frame. /// @@ -143,6 +307,24 @@ pub fn event_for( length: Duration, event_data: Data, ) -> impl FnOnce(&mut AnimationCommands, &mut Duration) +where + Data: Send + Sync + 'static, +{ + dispatch_for(length, event_data, EventEmittingTween) +} + +/// Combinator for dispatching a tween event. +/// +/// `with` informs how the tween event is dispatched. +/// +/// Event will be emitted at the current position for provided length every frame. +/// +/// Position is at the end of the event. +fn dispatch_for( + length: Duration, + event_data: Data, + with: impl Bundle, +) -> impl FnOnce(&mut AnimationCommands, &mut Duration) where Data: Send + Sync + 'static, { @@ -152,12 +334,59 @@ where a.spawn(( TimeSpan::try_from(start..end).unwrap(), TweenEventData::with_data(event_data), + with, )); *pos = end; } } -/// Combinator for creating an tween event. +/// Combinator for creating both a tween event and message. +/// +/// Event will be emitted at the provided span every frame. +/// +/// Position is not mutated because the operation is not relative. +/// +///
+/// +/// Your event should be registered with [`TweenEventPlugin`](crate::tween_event::TweenEventPlugin). +/// +///
+pub fn send_exact( + span: S, + event_data: Data, +) -> impl FnOnce(&mut AnimationCommands, &mut Duration) +where + S: TryInto, + S::Error: std::fmt::Debug, + Data: Send + Sync + 'static, +{ + dispatch_exact(span, event_data, (MessageEmittingTween, EventEmittingTween)) +} + +/// Combinator for dispatching a tween message. +/// +/// Message will be emitted at the provided span every frame. +/// +/// Position is not mutated because the operation is not relative. +/// +///
+/// +/// Your event should be registered with [`TweenEventPlugin`](crate::tween_event::TweenEventPlugin). +/// +///
+pub fn message_exact( + span: S, + event_data: Data, +) -> impl FnOnce(&mut AnimationCommands, &mut Duration) +where + S: TryInto, + S::Error: std::fmt::Debug, + Data: Send + Sync + 'static, +{ + dispatch_exact(span, event_data, MessageEmittingTween) +} + +/// Combinator for creating an tween event on the animation root containing a [`bevy_time_runner::TimeRunner`]. /// /// Event will be emitted at the provided span every frame. /// @@ -172,6 +401,26 @@ pub fn event_exact( span: S, event_data: Data, ) -> impl FnOnce(&mut AnimationCommands, &mut Duration) +where + S: TryInto, + S::Error: std::fmt::Debug, + Data: Send + Sync + 'static, +{ + dispatch_exact(span, event_data, EventEmittingTween) +} + +/// Combinator for dispatching a tween event. +/// +/// `with` informs how the tween event is dispatched. +/// +/// Event will be emitted at the provided span every frame. +/// +/// Position is not mutated because the operation is not relative. +pub fn dispatch_exact( + span: S, + event_data: Data, + with: impl Bundle, +) -> impl FnOnce(&mut AnimationCommands, &mut Duration) where S: TryInto, S::Error: std::fmt::Debug, @@ -181,6 +430,7 @@ where a.spawn(( span.try_into().unwrap(), TweenEventData::with_data(event_data), + with, )); } } diff --git a/src/lib.rs b/src/lib.rs index b5cd702a..be39db5a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -416,7 +416,7 @@ pub use tween::component_tween_system; pub use tween::resource_dyn_tween_system; pub use tween::resource_tween_system; -pub use tween_event::tween_event_system; +pub use tween_event::{tween_event_system, tween_message_system}; /// Default plugins for using crate. pub struct DefaultTweenPlugins @@ -599,6 +599,7 @@ pub enum TweenSystemSet { /// - [`tween::asset_tween_system`] /// /// Events is not necessary related to tweening but their code is still working in the same area. + /// - [`tween_event::tween_message_system`] /// - [`tween_event::tween_event_system`] ApplyTween, } diff --git a/src/tween_event.rs b/src/tween_event.rs index 9ce7e227..a4770ef6 100644 --- a/src/tween_event.rs +++ b/src/tween_event.rs @@ -10,12 +10,13 @@ //! - [`TweenEventData`] //! //! **Systems** +//! - [`tween_message_system`] //! - [`tween_event_system`] //! //! **Events**: //! - [`TweenEvent`] //! -//! Simple event system that fires generic data in a timed manner. +//! Simple event systems that fires generic data in a timed manner. //! Your data is stored in [`TweenEventData`] and the event type is [`TweenEvent`]. //! Tween event is not necessarily related to tweening. //! @@ -25,12 +26,17 @@ //! See [`DefaultTweenEventPlugins`] for default events which is also added in //! [`DefaultTweenPlugins`](crate::DefaultTweenPlugins) -use bevy_time_runner::TimeContext; +use bevy_time_runner::{TimeContext, TimeRunner}; +use std::fmt::Debug; use std::marker::PhantomData; +use tracing::warn; use bevy::{ app::PluginGroupBuilder, - ecs::schedule::{InternedScheduleLabel, ScheduleLabel}, + ecs::{ + query::QueryData, + schedule::{InternedScheduleLabel, ScheduleLabel}, + }, prelude::*, }; @@ -69,7 +75,10 @@ where fn build(&self, app: &mut App) { app.add_systems( self.schedule, - (tween_event_system::) + ( + tween_message_system::, + tween_event_system::, + ) .in_set(crate::TweenSystemSet::ApplyTween), ) .add_message::>(); @@ -128,12 +137,16 @@ impl Default for DefaultTweenEventPlugins<()> { /// Fires [`TweenEvent`] whenever [`TimeSpanProgress`] and [`TweenEventData`] exist in the same entity. #[derive(Default, Debug, Clone, PartialEq, Eq, Hash, Component, Reflect)] #[reflect(Component)] -#[require(EventEmittingTween)] pub struct TweenEventData(pub Data) where Data: Send + Sync + 'static; -/// Used to mark event-emitting tweens (tweens with `TweenEventData` for some registered `Data`) +/// Used to mark message-emitting tweens (tweens that emit a tween event message) +#[derive(Default, Debug, Clone, PartialEq, Eq, Hash, Component, Reflect)] +#[reflect(Component)] +pub struct MessageEmittingTween; + +/// Used to mark root event-emitting tween (tweens that emit an entity event to the animation root) #[derive(Default, Debug, Clone, PartialEq, Eq, Hash, Component, Reflect)] #[reflect(Component)] pub struct EventEmittingTween; @@ -152,8 +165,8 @@ impl TweenEventData<()> { } } -/// Fires whenever [`TimeSpanProgress`] and [`TweenEventData`] exist in the same entity -/// by [`tween_event_system`]. +/// Fires whenever [`TimeSpanProgress`], [`TweenEventData`], and [`MessageEmittingTween`]/[`EventEmittingTween`] exist in the same entity +/// by [`tween_message_system`] and [`tween_event_system`]. #[derive(Debug, Clone, PartialEq, Message, Reflect, EntityEvent)] pub struct TweenEvent { /// Custom user data @@ -166,36 +179,98 @@ pub struct TweenEvent { pub entity: Entity, } -/// Fires [`TweenEvent`] with optional user data whenever [`TimeSpanProgress`] -/// and [`TweenEventData`] exist in the same entity and data is `Some`, -/// cloning the data. +/// QueryData used to construct a [`TweenEvent`] for a given [`TweenEventData`] +#[derive(QueryData)] +pub struct TweenSystemData { + /// The tween entity the event combinator created + entity: Entity, + /// The data passed to the event combinator + event_data: &'static TweenEventData, + /// The progress for the event + progress: &'static TimeSpanProgress, + /// The current interpolation value + interpolation_value: Option<&'static TweenInterpolationValue>, +} + +// Note we need to implement event for the data item, +// due to how the [`QueryData`] derive is implemented. +impl<'w, 's, Data> TweenSystemDataItem<'w, 's, Data> +where + Data: Clone + Send + Sync + 'static, +{ + /// Constructs a tween event for the given tween data entity + pub fn event(&self) -> TweenEvent { + TweenEvent { + data: self.event_data.0.clone(), + progress: *self.progress, + interpolation_value: self.interpolation_value.map(|v| v.0), + entity: self.entity, + } + } + + /// Constructs a tween event for a different tween data entity + pub fn event_on(&self, entity: Entity) -> TweenEvent { + TweenEvent { + entity, + ..self.event() + } + } +} + +/// Fires a [`TweenEvent`] message with optional user data +/// whenever [`TimeSpanProgress`] and [`TweenEventData`] exist in the +/// same entity and data is `Some`, cloning the data. +#[allow(clippy::type_complexity)] +pub fn tween_message_system( + q_tween_event_data: Query< + TweenSystemData, + ( + Without, + With>, + With, + ), + >, + mut event_writer: MessageWriter>, +) where + Data: Clone + Send + Sync + 'static, + TimeCtx: Default + Send + Sync + 'static, +{ + q_tween_event_data.iter().for_each(|data| { + event_writer.write(data.event()); + }); +} + +/// Fires a [`TweenEvent`] to the animation root with optional user data +/// whenever [`TimeSpanProgress`] and [`TweenEventData`] exist in the +/// same entity and data is `Some`, cloning the data. #[allow(clippy::type_complexity)] pub fn tween_event_system( mut commands: Commands, q_tween_event_data: Query< + (&ChildOf, TweenSystemData), ( - Entity, - &TweenEventData, - &TimeSpanProgress, - Option<&TweenInterpolationValue>, + Without, + With>, + With, ), - (Without, With>), >, - mut event_writer: MessageWriter>, + q_tween_roots: Query<(), With>, ) where Data: Clone + Send + Sync + 'static, TimeCtx: Default + Send + Sync + 'static, { - q_tween_event_data.iter().for_each( - |(entity, event_data, progress, interpolation_value)| { - let event = TweenEvent { - data: event_data.0.clone(), - progress: *progress, - interpolation_value: interpolation_value.map(|v| v.0), - entity, - }; - commands.trigger(event.clone()); - event_writer.write(event); - }, - ); + q_tween_event_data.iter().for_each(|(child_of, data)| { + let root = child_of.parent(); + if !q_tween_roots.contains(root) { + warn!( + "Could not find an associated animation root for a tween event on entity {:?} with data {:?}\n\ + help: make sure you have a `TimeRunner` defined on the entity you plan on animating", + data.event().entity, + std::any::type_name::(), + ); + return; + }; + + commands.trigger(data.event_on(root)); + }); }