Skip to content
Snippets Groups Projects
Verified Commit b2697ca0 authored by Louis's avatar Louis :fire:
Browse files

Fork from DSD code

parents
No related branches found
No related tags found
No related merge requests found
/target
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/weirdboi_tween.iml" filepath="$PROJECT_DIR$/.idea/weirdboi_tween.iml" />
</modules>
</component>
</project>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<module type="EMPTY_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
<excludeFolder url="file://$MODULE_DIR$/target" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>
\ No newline at end of file
This diff is collapsed.
[package]
name = "weirdboi_tween"
version = "0.1.0"
edition = "2024"
[features]
default = []
reflect = ["dep:bevy_reflect"]
bevy_defaults = [
"dep:bevy_transform",
"dep:bevy_sprite",
"dep:bevy_ui",
"dep:bevy_derive",
"dep:bevy_color",
"dep:bevy_text",
]
[dependencies]
log = "0.4"
bevy_math = "0.16"
bevy_app = "0.16"
bevy_ecs = "0.16"
bevy_time = "0.16"
bevy_transform = { version = "0.16", optional = true }
bevy_sprite = { version = "0.16", optional = true }
bevy_ui = { version = "0.16", optional = true }
bevy_reflect = { version = "0.16", optional = true }
bevy_derive = { version = "0.16", optional = true }
bevy_color = { version = "0.16", optional = true }
bevy_text = { version = "0.16", optional = true }
hard_tabs = true
tab_spaces = 4
use_field_init_shorthand = true
use_try_shorthand = true
\ No newline at end of file
use crate::Tweenable;
use bevy_color::{Color, ColorToComponents};
use bevy_derive::{Deref, DerefMut};
use bevy_math::curve::CurveExt;
use bevy_math::curve::Ease;
use bevy_math::{Curve, Vec3, Vec4};
use bevy_sprite::Sprite;
use bevy_text::TextColor;
use bevy_transform::components::Transform;
use bevy_ui::widget::ImageNode;
#[derive(Deref, DerefMut, Clone)]
#[repr(transparent)]
pub struct TweenableColour(pub Color);
impl From<Color> for TweenableColour {
fn from(value: Color) -> Self {
Self(value)
}
}
impl Ease for TweenableColour {
fn interpolating_curve_unbounded(start: Self, end: Self) -> impl Curve<Self> {
let start = start.to_srgba().to_vec4();
let end = end.to_srgba().to_vec4();
<Vec4 as Ease>::interpolating_curve_unbounded(start, end).map(|components| {
TweenableColour::from(Color::srgba(
components.x,
components.y,
components.z,
components.w,
))
})
}
}
pub struct TweenSpriteColour;
impl Tweenable for TweenSpriteColour {
type Comp = Sprite;
type Data = TweenableColour;
fn current_value(cmp: &Self::Comp) -> Self::Data {
cmp.color.into()
}
fn update_component(cmp: &mut Self::Comp, value: Self::Data) {
cmp.color = *value;
}
}
pub struct TweenImageNodeColour;
impl Tweenable for TweenImageNodeColour {
type Comp = ImageNode;
type Data = TweenableColour;
fn current_value(cmp: &Self::Comp) -> Self::Data {
cmp.color.into()
}
fn update_component(cmp: &mut Self::Comp, value: Self::Data) {
cmp.color = *value;
}
}
pub struct TweenTextColour;
impl Tweenable for TweenTextColour {
type Comp = TextColor;
type Data = TweenableColour;
fn current_value(cmp: &Self::Comp) -> Self::Data {
cmp.0.into()
}
fn update_component(cmp: &mut Self::Comp, value: Self::Data) {
cmp.0 = *value;
}
}
pub struct TweenTransformTranslation;
impl Tweenable for TweenTransformTranslation {
type Comp = Transform;
type Data = Vec3;
fn current_value(cmp: &Self::Comp) -> Self::Data {
cmp.translation
}
fn update_component(cmp: &mut Self::Comp, value: Self::Data) {
cmp.translation = value;
}
}
pub struct TweenTransformScale;
impl Tweenable for TweenTransformScale {
type Comp = Transform;
type Data = Vec3;
fn current_value(cmp: &Self::Comp) -> Self::Data {
cmp.scale
}
fn update_component(cmp: &mut Self::Comp, value: Self::Data) {
cmp.scale = value;
}
}
use crate::components::Tweenable;
use crate::easing::EaseTween;
use crate::systems::{remove_tween, tick_tweens, update_or_create_tween};
use crate::{UpdateTween, UpdateUserData};
use bevy_app::{App, Update};
use bevy_ecs::entity::Entity;
use bevy_ecs::system::{Commands, EntityCommands};
use bevy_ecs::world::World;
use std::time::Duration;
pub trait RegisterTweenableExt {
fn register_tweenable<T: Tweenable>(&mut self) -> &mut Self;
}
impl RegisterTweenableExt for App {
fn register_tweenable<T: Tweenable>(&mut self) -> &mut Self {
self.add_systems(Update, tick_tweens::<T>)
}
}
pub trait ClearTweensExt {
fn clear_tweens<T: Tweenable>(&mut self, entity: Entity) -> &mut Self;
}
impl ClearTweensExt for Commands<'_, '_> {
fn clear_tweens<T: Tweenable>(&mut self, entity: Entity) -> &mut Self {
self.queue(move |world: &mut World| {
if let Err(err) = world.run_system_cached_with(remove_tween::<T>, entity) {
log::error!("Error removing tween: {err}");
}
});
self
}
}
pub trait UpdateTweenExt {
fn update_tween<T: Tweenable>(
&mut self,
entity: Entity,
duration: impl Into<Duration>,
end: T::Data,
easing: EaseTween,
event: impl Into<UpdateUserData>,
) -> &mut Self;
}
pub trait UpdateOwnTweenExt {
fn update_tween<T: Tweenable>(
&mut self,
duration: impl Into<Duration>,
end: T::Data,
easing: EaseTween,
event: impl Into<UpdateUserData>,
) -> &mut Self;
}
impl UpdateTweenExt for Commands<'_, '_> {
fn update_tween<T: Tweenable>(
&mut self,
entity: Entity,
duration: impl Into<Duration>,
end: T::Data,
easing: EaseTween,
event: impl Into<UpdateUserData>,
) -> &mut Self {
let update = UpdateTween::<T> {
entity,
duration: duration.into(),
end,
easing,
user_data: event.into(),
};
self.queue(move |world: &mut World| {
if let Err(err) = world.run_system_cached_with(update_or_create_tween::<T>, update) {
log::error!("Error updating tween: {err}");
}
});
self
}
}
impl UpdateOwnTweenExt for EntityCommands<'_> {
fn update_tween<T: Tweenable>(
&mut self,
duration: impl Into<Duration>,
end: T::Data,
easing: EaseTween,
event: impl Into<UpdateUserData>,
) -> &mut Self {
let entity = self.id();
let cmds = self.commands_mut();
cmds.update_tween::<T>(entity, duration, end, easing, event);
self
}
}
use crate::easing::EaseTween;
use bevy_ecs::component::{Component, Mutable};
use bevy_ecs::entity::Entity;
use bevy_math::Curve;
use bevy_math::curve::{Ease, Interval};
use std::fmt::{Debug, Formatter};
use std::time::Duration;
pub trait Tweenable: Send + Sync + 'static {
type Comp: Component<Mutability = Mutable>;
type Data: Ease + Clone + Send + Sync + 'static;
fn tween(
duration: impl Into<Duration>,
from: Self::Data,
to: Self::Data,
easing: EaseTween,
) -> Tween<Self>
where
Self: Sized,
{
Self::delayed(duration, Duration::ZERO, from, to, easing)
}
fn delayed(
duration: impl Into<Duration>,
delay: impl Into<Duration>,
from: Self::Data,
to: Self::Data,
easing: EaseTween,
) -> Tween<Self>
where
Self: Sized,
{
Tween::delayed(duration, delay, from, to, easing)
}
fn sample(progress: f32, easing: &TweenEasing<Self::Data>) -> Self::Data {
easing.sample_clamped(progress)
}
fn current_value(cmp: &Self::Comp) -> Self::Data;
fn update_component(cmp: &mut Self::Comp, value: Self::Data);
fn interpolate(cmp: &mut Self::Comp, progress: f32, easing: &TweenEasing<Self::Data>) {
let value = Self::sample(progress, easing);
Self::update_component(cmp, value);
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Component, Default)]
pub enum TweenMode {
#[default]
Once,
PingPong,
Loop,
}
impl TweenMode {
pub fn is_loop(&self) -> bool {
matches!(self, Self::PingPong | Self::Loop)
}
pub fn is_once(&self) -> bool {
matches!(self, Self::Once)
}
}
#[derive(Component)]
#[require(TweenMode)]
pub struct Tween<T: Tweenable> {
pub delay: Duration,
pub duration: Duration,
pub elapsed: Duration,
pub easing: TweenEasing<T::Data>,
pub user_data: Option<u32>,
}
#[derive(Component)]
#[relationship(relationship_target = ActiveTweens)]
pub struct TweenTarget(pub Entity);
#[derive(Component)]
#[relationship_target(relationship = TweenTarget)]
pub struct ActiveTweens(Vec<Entity>);
impl<T: Tweenable> Debug for Tween<T> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Tween<T>")
.field("delay", &self.delay)
.field("duration", &self.duration)
.field("elapsed", &self.elapsed)
.field(
"easing",
&format!("TweenEasing<{}>", std::any::type_name::<T::Data>()),
)
.field("user_data", &self.user_data)
.finish()
}
}
impl<T> Clone for Tween<T>
where
T: Tweenable,
T::Data: Clone,
{
fn clone(&self) -> Self {
Self {
delay: self.delay,
duration: self.duration,
elapsed: self.elapsed,
easing: self.easing.clone(),
user_data: self.user_data,
}
}
}
impl<T: Tweenable> Tween<T> {
pub fn new(
duration: impl Into<Duration>,
from: T::Data,
to: T::Data,
easing: EaseTween,
) -> Self {
Self::delayed(duration, Duration::ZERO, from, to, easing)
}
pub fn delayed(
duration: impl Into<Duration>,
delay: impl Into<Duration>,
from: T::Data,
to: T::Data,
easing: EaseTween,
) -> Self {
let easing = TweenEasing::new(from, to, easing);
Self {
delay: delay.into(),
duration: duration.into(),
elapsed: Duration::ZERO,
easing,
user_data: None,
}
}
pub fn linear(duration: impl Into<Duration>, from: T::Data, to: T::Data) -> Self {
Tween::new(duration, from, to, EaseTween::Linear)
}
pub fn with_user_data(self, user_data: Option<u32>) -> Self {
Self { user_data, ..self }
}
}
#[derive(Copy, Clone, Default, Debug)]
#[cfg_attr(feature = "reflect", derive(bevy_reflect::Reflect))]
pub enum UpdateUserData {
#[default]
Unchanged,
Set(u32),
Remove,
}
impl From<Option<u32>> for UpdateUserData {
fn from(value: Option<u32>) -> Self {
match value {
Some(value) => UpdateUserData::Set(value),
None => UpdateUserData::Unchanged,
}
}
}
impl From<UpdateUserData> for Option<u32> {
fn from(value: UpdateUserData) -> Self {
match value {
UpdateUserData::Unchanged => None,
UpdateUserData::Set(value) => Some(value),
UpdateUserData::Remove => None,
}
}
}
pub struct UpdateTween<T: Tweenable> {
pub entity: Entity,
pub duration: Duration,
pub end: T::Data,
pub easing: EaseTween,
pub user_data: UpdateUserData,
}
#[derive(Clone, Debug)]
pub struct TweenEasing<T> {
start: T,
end: T,
ease_fn: EaseTween,
}
impl<T: Clone> TweenEasing<T> {
pub fn new(start: T, end: T, ease_fn: EaseTween) -> Self {
Self {
start,
end,
ease_fn,
}
}
pub fn start(&self) -> &T {
&self.start
}
pub fn end(&self) -> &T {
&self.end
}
pub fn easing(&self) -> EaseTween {
self.ease_fn
}
pub fn flip(&self) -> Self {
TweenEasing {
start: self.end.clone(),
end: self.start.clone(),
ease_fn: self.ease_fn,
}
}
}
impl<T> Curve<T> for TweenEasing<T>
where
T: Ease + Clone,
{
#[inline]
fn domain(&self) -> Interval {
Interval::UNIT
}
#[inline]
fn sample_unchecked(&self, t: f32) -> T {
let remapped_t = self.ease_fn.eval(t);
T::interpolating_curve_unbounded(self.start.clone(), self.end.clone())
.sample_unchecked(remapped_t)
}
}
#[non_exhaustive]
#[derive(Debug, Copy, Clone, PartialEq)]
pub enum EaseTween {
Linear,
QuadraticIn,
QuadraticOut,
QuadraticInOut,
CubicIn,
CubicOut,
CubicInOut,
QuarticIn,
QuarticOut,
QuarticInOut,
QuinticIn,
QuinticOut,
QuinticInOut,
SmoothStepIn,
SmoothStepOut,
SmoothStep,
SmootherStepIn,
SmootherStepOut,
SmootherStep,
SineIn,
SineOut,
SineInOut,
CircularIn,
CircularOut,
CircularInOut,
ExponentialIn,
ExponentialOut,
ExponentialInOut,
ElasticIn,
ElasticOut,
ElasticInOut,
BackIn,
BackOut,
BackInOut,
BounceIn,
BounceOut,
BounceInOut,
Elastic(f32),
}
impl EaseTween {
pub fn eval(&self, t: f32) -> f32 {
match self {
EaseTween::Linear => easing_functions::linear(t),
EaseTween::QuadraticIn => easing_functions::quadratic_in(t),
EaseTween::QuadraticOut => easing_functions::quadratic_out(t),
EaseTween::QuadraticInOut => easing_functions::quadratic_in_out(t),
EaseTween::CubicIn => easing_functions::cubic_in(t),
EaseTween::CubicOut => easing_functions::cubic_out(t),
EaseTween::CubicInOut => easing_functions::cubic_in_out(t),
EaseTween::QuarticIn => easing_functions::quartic_in(t),
EaseTween::QuarticOut => easing_functions::quartic_out(t),
EaseTween::QuarticInOut => easing_functions::quartic_in_out(t),
EaseTween::QuinticIn => easing_functions::quintic_in(t),
EaseTween::QuinticOut => easing_functions::quintic_out(t),
EaseTween::QuinticInOut => easing_functions::quintic_in_out(t),
EaseTween::SmoothStepIn => easing_functions::smoothstep_in(t),
EaseTween::SmoothStepOut => easing_functions::smoothstep_out(t),
EaseTween::SmoothStep => easing_functions::smoothstep(t),
EaseTween::SmootherStepIn => easing_functions::smootherstep_in(t),
EaseTween::SmootherStepOut => easing_functions::smootherstep_out(t),
EaseTween::SmootherStep => easing_functions::smootherstep(t),
EaseTween::SineIn => easing_functions::sine_in(t),
EaseTween::SineOut => easing_functions::sine_out(t),
EaseTween::SineInOut => easing_functions::sine_in_out(t),
EaseTween::CircularIn => easing_functions::circular_in(t),
EaseTween::CircularOut => easing_functions::circular_out(t),
EaseTween::CircularInOut => easing_functions::circular_in_out(t),
EaseTween::ExponentialIn => easing_functions::exponential_in(t),
EaseTween::ExponentialOut => easing_functions::exponential_out(t),
EaseTween::ExponentialInOut => easing_functions::exponential_in_out(t),
EaseTween::ElasticIn => easing_functions::elastic_in(t),
EaseTween::ElasticOut => easing_functions::elastic_out(t),
EaseTween::ElasticInOut => easing_functions::elastic_in_out(t),
EaseTween::BackIn => easing_functions::back_in(t),
EaseTween::BackOut => easing_functions::back_out(t),
EaseTween::BackInOut => easing_functions::back_in_out(t),
EaseTween::BounceIn => easing_functions::bounce_in(t),
EaseTween::BounceOut => easing_functions::bounce_out(t),
EaseTween::BounceInOut => easing_functions::bounce_in_out(t),
EaseTween::Elastic(omega) => easing_functions::elastic(*omega, t),
}
}
}
pub mod easing_functions {
use bevy_math::{FloatPow, ops};
use core::f32::consts::{FRAC_PI_2, FRAC_PI_3, PI};
#[inline]
pub(crate) fn linear(t: f32) -> f32 {
t
}
#[inline]
pub(crate) fn quadratic_in(t: f32) -> f32 {
t.squared()
}
#[inline]
pub(crate) fn quadratic_out(t: f32) -> f32 {
1.0 - (1.0 - t).squared()
}
#[inline]
pub(crate) fn quadratic_in_out(t: f32) -> f32 {
if t < 0.5 {
2.0 * t.squared()
} else {
1.0 - (-2.0 * t + 2.0).squared() / 2.0
}
}
#[inline]
pub(crate) fn cubic_in(t: f32) -> f32 {
t.cubed()
}
#[inline]
pub(crate) fn cubic_out(t: f32) -> f32 {
1.0 - (1.0 - t).cubed()
}
#[inline]
pub(crate) fn cubic_in_out(t: f32) -> f32 {
if t < 0.5 {
4.0 * t.cubed()
} else {
1.0 - (-2.0 * t + 2.0).cubed() / 2.0
}
}
#[inline]
pub(crate) fn quartic_in(t: f32) -> f32 {
t * t * t * t
}
#[inline]
pub(crate) fn quartic_out(t: f32) -> f32 {
1.0 - (1.0 - t) * (1.0 - t) * (1.0 - t) * (1.0 - t)
}
#[inline]
pub(crate) fn quartic_in_out(t: f32) -> f32 {
if t < 0.5 {
8.0 * t * t * t * t
} else {
1.0 - (-2.0 * t + 2.0) * (-2.0 * t + 2.0) * (-2.0 * t + 2.0) * (-2.0 * t + 2.0) / 2.0
}
}
#[inline]
pub(crate) fn quintic_in(t: f32) -> f32 {
t * t * t * t * t
}
#[inline]
pub(crate) fn quintic_out(t: f32) -> f32 {
1.0 - (1.0 - t) * (1.0 - t) * (1.0 - t) * (1.0 - t) * (1.0 - t)
}
#[inline]
pub(crate) fn quintic_in_out(t: f32) -> f32 {
if t < 0.5 {
16.0 * t * t * t * t * t
} else {
1.0 - (-2.0 * t + 2.0)
* (-2.0 * t + 2.0)
* (-2.0 * t + 2.0)
* (-2.0 * t + 2.0)
* (-2.0 * t + 2.0)
/ 2.0
}
}
#[inline]
pub(crate) fn smoothstep_in(t: f32) -> f32 {
((1.5 - 0.5 * t) * t) * t
}
#[inline]
pub(crate) fn smoothstep_out(t: f32) -> f32 {
(1.5 + (-0.5 * t) * t) * t
}
#[inline]
pub(crate) fn smoothstep(t: f32) -> f32 {
((3.0 - 2.0 * t) * t) * t
}
#[inline]
pub(crate) fn smootherstep_in(t: f32) -> f32 {
(((2.5 + (-1.875 + 0.375 * t) * t) * t) * t) * t
}
#[inline]
pub(crate) fn smootherstep_out(t: f32) -> f32 {
(1.875 + ((-1.25 + (0.375 * t) * t) * t) * t) * t
}
#[inline]
pub(crate) fn smootherstep(t: f32) -> f32 {
(((10.0 + (-15.0 + 6.0 * t) * t) * t) * t) * t
}
#[inline]
pub(crate) fn sine_in(t: f32) -> f32 {
1.0 - ops::cos(t * FRAC_PI_2)
}
#[inline]
pub(crate) fn sine_out(t: f32) -> f32 {
ops::sin(t * FRAC_PI_2)
}
#[inline]
pub(crate) fn sine_in_out(t: f32) -> f32 {
-(ops::cos(PI * t) - 1.0) / 2.0
}
#[inline]
pub(crate) fn circular_in(t: f32) -> f32 {
1.0 - ops::sqrt(1.0 - t.squared())
}
#[inline]
pub(crate) fn circular_out(t: f32) -> f32 {
ops::sqrt(1.0 - (t - 1.0).squared())
}
#[inline]
pub(crate) fn circular_in_out(t: f32) -> f32 {
if t < 0.5 {
(1.0 - ops::sqrt(1.0 - (2.0 * t).squared())) / 2.0
} else {
(ops::sqrt(1.0 - (-2.0 * t + 2.0).squared()) + 1.0) / 2.0
}
}
// These are copied from a high precision calculator; I'd rather show them
// with blatantly more digits than needed (since rust will round them to the
// nearest representable value anyway) rather than make it seem like the
// truncated value is somehow carefully chosen.
#[expect(
clippy::excessive_precision,
reason = "This is deliberately more precise than an f32 will allow, as truncating the value might imply that the value is carefully chosen."
)]
const LOG2_1023: f32 = 9.998590429745328646459226;
#[expect(
clippy::excessive_precision,
reason = "This is deliberately more precise than an f32 will allow, as truncating the value might imply that the value is carefully chosen."
)]
const FRAC_1_1023: f32 = 0.00097751710654936461388074291;
#[inline]
pub(crate) fn exponential_in(t: f32) -> f32 {
// Derived from a rescaled exponential formula `(2^(10*t) - 1) / (2^10 - 1)`
// See <https://www.wolframalpha.com/input?i=solve+over+the+reals%3A+pow%282%2C+10-A%29+-+pow%282%2C+-A%29%3D+1>
ops::exp2(10.0 * t - LOG2_1023) - FRAC_1_1023
}
#[inline]
pub(crate) fn exponential_out(t: f32) -> f32 {
(FRAC_1_1023 + 1.0) - ops::exp2(-10.0 * t - (LOG2_1023 - 10.0))
}
#[inline]
pub(crate) fn exponential_in_out(t: f32) -> f32 {
if t < 0.5 {
ops::exp2(20.0 * t - (LOG2_1023 + 1.0)) - (FRAC_1_1023 / 2.0)
} else {
(FRAC_1_1023 / 2.0 + 1.0) - ops::exp2(-20.0 * t - (LOG2_1023 - 19.0))
}
}
#[inline]
pub(crate) fn back_in(t: f32) -> f32 {
let c = 1.70158;
(c + 1.0) * t.cubed() - c * t.squared()
}
#[inline]
pub(crate) fn back_out(t: f32) -> f32 {
let c = 1.70158;
1.0 + (c + 1.0) * (t - 1.0).cubed() + c * (t - 1.0).squared()
}
#[inline]
pub(crate) fn back_in_out(t: f32) -> f32 {
let c1 = 1.70158;
let c2 = c1 + 1.525;
if t < 0.5 {
(2.0 * t).squared() * ((c2 + 1.0) * 2.0 * t - c2) / 2.0
} else {
((2.0 * t - 2.0).squared() * ((c2 + 1.0) * (2.0 * t - 2.0) + c2) + 2.0) / 2.0
}
}
#[inline]
pub(crate) fn elastic_in(t: f32) -> f32 {
-ops::powf(2.0, 10.0 * t - 10.0) * ops::sin((t * 10.0 - 10.75) * 2.0 * FRAC_PI_3)
}
#[inline]
pub(crate) fn elastic_out(t: f32) -> f32 {
ops::powf(2.0, -10.0 * t) * ops::sin((t * 10.0 - 0.75) * 2.0 * FRAC_PI_3) + 1.0
}
#[inline]
pub(crate) fn elastic_in_out(t: f32) -> f32 {
let c = (2.0 * PI) / 4.5;
if t < 0.5 {
-ops::powf(2.0, 20.0 * t - 10.0) * ops::sin((t * 20.0 - 11.125) * c) / 2.0
} else {
ops::powf(2.0, -20.0 * t + 10.0) * ops::sin((t * 20.0 - 11.125) * c) / 2.0 + 1.0
}
}
#[inline]
pub(crate) fn bounce_in(t: f32) -> f32 {
1.0 - bounce_out(1.0 - t)
}
#[inline]
pub(crate) fn bounce_out(t: f32) -> f32 {
if t < 4.0 / 11.0 {
(121.0 * t.squared()) / 16.0
} else if t < 8.0 / 11.0 {
(363.0 / 40.0 * t.squared()) - (99.0 / 10.0 * t) + 17.0 / 5.0
} else if t < 9.0 / 10.0 {
(4356.0 / 361.0 * t.squared()) - (35442.0 / 1805.0 * t) + 16061.0 / 1805.0
} else {
(54.0 / 5.0 * t.squared()) - (513.0 / 25.0 * t) + 268.0 / 25.0
}
}
#[inline]
pub(crate) fn bounce_in_out(t: f32) -> f32 {
if t < 0.5 {
(1.0 - bounce_out(1.0 - 2.0 * t)) / 2.0
} else {
(1.0 + bounce_out(2.0 * t - 1.0)) / 2.0
}
}
#[inline]
pub(crate) fn elastic(omega: f32, t: f32) -> f32 {
1.0 - (1.0 - t).squared() * (2.0 * ops::sin(omega * t) / omega + ops::cos(omega * t))
}
}
use bevy_ecs::entity::Entity;
use bevy_ecs::event::Event;
pub const DESPAWN_ON_TWEEN_COMPLETE_EVENT: u32 = u32::MAX;
pub const DESPAWN_ANCESTORS_ON_TWEEN_COMPLETE_EVENT: u32 = u32::MAX - 1;
#[derive(Debug, Copy, Clone, PartialEq, Eq, Event)]
pub struct TweenComplete {
pub entity: Entity,
pub user_data: u32,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Event)]
pub struct TweenLooped {
pub entity: Entity,
pub user_data: u32,
}
use bevy_app::{App, Last, Plugin};
#[cfg(feature = "bevy_defaults")]
mod bevy_tweenables;
mod commands;
mod components;
mod easing;
mod events;
mod systems;
#[cfg(feature = "bevy_defaults")]
pub use bevy_tweenables::{
TweenImageNodeColour, TweenSpriteColour, TweenTextColour, TweenTransformScale,
TweenTransformTranslation, TweenableColour,
};
pub use commands::{ClearTweensExt, RegisterTweenableExt, UpdateOwnTweenExt, UpdateTweenExt};
pub use components::{
ActiveTweens, Tween, TweenMode, TweenTarget, Tweenable, UpdateTween, UpdateUserData,
};
pub use easing::{EaseTween, easing_functions};
pub use events::{
DESPAWN_ANCESTORS_ON_TWEEN_COMPLETE_EVENT, DESPAWN_ON_TWEEN_COMPLETE_EVENT, TweenComplete,
TweenLooped,
};
pub struct TweenPlugin;
impl Plugin for TweenPlugin {
fn build(&self, app: &mut App) {
#[cfg(feature = "bevy_defaults")]
{
app.register_tweenable::<TweenImageNodeColour>()
.register_tweenable::<TweenSpriteColour>()
.register_tweenable::<TweenTextColour>()
.register_tweenable::<TweenTransformTranslation>()
.register_tweenable::<TweenTransformScale>();
}
app.add_event::<TweenComplete>()
.add_event::<TweenLooped>()
.add_systems(
Last,
(
systems::despawn_on_complete,
systems::despawn_ancestors_on_complete,
),
);
}
}
use crate::components::{Tween, TweenEasing, TweenMode, TweenTarget, Tweenable, UpdateTween};
use crate::events::TweenLooped;
use crate::{
DESPAWN_ANCESTORS_ON_TWEEN_COMPLETE_EVENT, DESPAWN_ON_TWEEN_COMPLETE_EVENT, TweenComplete,
UpdateUserData,
};
use bevy_ecs::entity::Entity;
use bevy_ecs::event::{EventReader, EventWriter};
use bevy_ecs::hierarchy::ChildOf;
use bevy_ecs::query::With;
use bevy_ecs::relationship::Relationship;
use bevy_ecs::system::{Commands, In, Query, Res};
use bevy_time::Time;
use std::time::Duration;
pub fn despawn_on_complete(mut commands: Commands, mut events: EventReader<TweenComplete>) {
for event in events.read() {
if event.user_data == DESPAWN_ON_TWEEN_COMPLETE_EVENT {
log::debug!("Despawn on complete {:?}", event.entity);
commands.entity(event.entity).despawn();
}
}
}
pub fn despawn_ancestors_on_complete(
mut commands: Commands,
mut events: EventReader<TweenComplete>,
parent_query: Query<&ChildOf>,
) {
for event in events.read() {
if event.user_data == DESPAWN_ANCESTORS_ON_TWEEN_COMPLETE_EVENT {
let mut assumed_root = event.entity;
while let Ok(rel) = parent_query.get(assumed_root) {
assumed_root = rel.parent();
}
log::debug!(
"Despawn on complete {:?} [Found parent {:?}]",
event.entity,
assumed_root
);
commands.entity(assumed_root).despawn();
}
}
}
pub fn tick_tweens<T: Tweenable>(
mut commands: Commands,
time: Res<Time>,
mut tweeners: Query<(Entity, &mut Tween<T>, &TweenTarget, &TweenMode)>,
mut components: Query<&mut T::Comp>,
mut complete_events: EventWriter<TweenComplete>,
mut looped_events: EventWriter<TweenLooped>,
) {
let delta = time.delta();
for (entity, mut tween, target, mode) in &mut tweeners {
if !tween.delay.is_zero() {
tween.delay = tween.delay.saturating_sub(delta);
continue;
}
tween.elapsed += delta;
let tween_target = target.get();
let is_complete = tween.elapsed >= tween.duration;
let progress = (tween.elapsed.as_secs_f32() / tween.duration.as_secs_f32()).clamp(0.0, 1.0);
let despawn = if let Ok(mut cmp) = components.get_mut(tween_target) {
T::interpolate(&mut cmp, progress, &tween.easing);
false
} else {
true
};
if is_complete {
if let Some(user_data) = tween.user_data {
if mode.is_once() {
let event = TweenComplete {
entity: tween_target,
user_data,
};
complete_events.write(event);
commands.trigger_targets(event, tween_target);
} else {
let event = TweenLooped {
entity: tween_target,
user_data,
};
looped_events.write(event);
commands.trigger_targets(event, tween_target);
}
}
}
if despawn {
commands.entity(entity).despawn();
} else if is_complete {
match mode {
TweenMode::PingPong => {
tween.easing = tween.easing.flip();
tween.elapsed = Duration::ZERO;
}
TweenMode::Loop => {
tween.elapsed = Duration::ZERO;
}
TweenMode::Once => {
commands.entity(entity).despawn();
}
}
}
}
}
pub fn remove_tween<T: Tweenable>(
In(target): In<Entity>,
mut commands: Commands,
query: Query<(Entity, &TweenTarget), With<Tween<T>>>,
) {
for (entity, tween_target) in &query {
if tween_target.get() == target {
commands.entity(entity).despawn();
}
}
}
pub fn update_or_create_tween<T: Tweenable>(
In(options): In<UpdateTween<T>>,
mut commands: Commands,
mut tweeners: Query<(&mut Tween<T>, &TweenTarget)>,
data_source: Query<&T::Comp>,
) {
let target = options.entity;
if let Ok(value) = data_source.get(target) {
let value = T::current_value(value);
if let Some((mut tween, _)) = tweeners
.iter_mut()
.find(|(_, tween_target)| tween_target.get() == target)
{
tween.elapsed = Duration::ZERO;
tween.duration = options.duration;
tween.easing = TweenEasing::new(value, options.end, options.easing);
match options.user_data {
UpdateUserData::Set(value) => tween.user_data = Some(value),
UpdateUserData::Remove => tween.user_data = None,
UpdateUserData::Unchanged => {}
}
} else {
commands.entity(target).with_related::<TweenTarget>(
T::tween(options.duration, value, options.end, options.easing)
.with_user_data(options.user_data.into()),
);
}
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment