feat: basic tween system added
This commit is contained in:
parent
cd2cd89eae
commit
9f3e39e337
72
Engine.Systems/Tween/Easings.cs
Normal file
72
Engine.Systems/Tween/Easings.cs
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
// Reference: https://easings.net
|
||||||
|
|
||||||
|
namespace Syntriax.Engine.Systems.Tween;
|
||||||
|
|
||||||
|
internal static class EaseConstants
|
||||||
|
{
|
||||||
|
internal const float c1 = 1.70158f;
|
||||||
|
internal const float c2 = c1 * 1.525f;
|
||||||
|
internal const float c3 = c1 + 1f;
|
||||||
|
internal const float c4 = 2f * Core.Math.PI / 3;
|
||||||
|
internal const float c5 = 2f * Core.Math.PI / 4.5f;
|
||||||
|
}
|
||||||
|
|
||||||
|
public readonly struct EaseLinear : IEasing { public readonly float Evaluate(float value) => value; }
|
||||||
|
|
||||||
|
public readonly struct EaseInQuad : IEasing { public readonly float Evaluate(float value) => value * value; }
|
||||||
|
public readonly struct EaseOutQuad : IEasing { public readonly float Evaluate(float value) => 1f - (1f - value) * (1f - value); }
|
||||||
|
public readonly struct EaseInOutQuad : IEasing { public readonly float Evaluate(float value) => value < 0.5f ? 2f * value * value : 1f - Core.Math.Pow(-2f * value + 2f, 2f) * .5f; }
|
||||||
|
|
||||||
|
public readonly struct EaseInCubic : IEasing { public readonly float Evaluate(float value) => value * value * value; }
|
||||||
|
public readonly struct EaseOutCubic : IEasing { public readonly float Evaluate(float value) => 1f - Core.Math.Pow(1f - value, 3); }
|
||||||
|
public readonly struct EaseInOutCubic : IEasing { public readonly float Evaluate(float value) => value < 0.5f ? 4f * value * value * value : 1f - Core.Math.Pow(-2f * value + 2f, 3) * .5f; }
|
||||||
|
|
||||||
|
public readonly struct EaseInQuart : IEasing { public readonly float Evaluate(float value) => value * value * value * value; }
|
||||||
|
public readonly struct EaseOutQuart : IEasing { public readonly float Evaluate(float value) => 1f - Core.Math.Pow(1f - value, 4f); }
|
||||||
|
public readonly struct EaseInOutQuart : IEasing { public readonly float Evaluate(float value) => value < 0.5f ? 8 * value * value * value * value : 1f - Core.Math.Pow(-2f * value + 2f, 4f) * .5f; }
|
||||||
|
|
||||||
|
public readonly struct EaseInQuint : IEasing { public readonly float Evaluate(float value) => value * value * value * value * value; }
|
||||||
|
public readonly struct EaseOutQuint : IEasing { public readonly float Evaluate(float value) => 1f - Core.Math.Pow(1f - value, 5); }
|
||||||
|
public readonly struct EaseInOutQuint : IEasing { public readonly float Evaluate(float value) => value < 0.5f ? 16 * value * value * value * value * value : 1f - Core.Math.Pow(-2f * value + 2f, 5) * .5f; }
|
||||||
|
|
||||||
|
public readonly struct EaseInSine : IEasing { public readonly float Evaluate(float value) => 1f - Core.Math.Cos(value * Core.Math.PI * .5f); }
|
||||||
|
public readonly struct EaseOutSine : IEasing { public readonly float Evaluate(float value) => Core.Math.Sin(value * Core.Math.PI * .5f); }
|
||||||
|
public readonly struct EaseInOutSine : IEasing { public readonly float Evaluate(float value) => -(Core.Math.Cos(Core.Math.PI * value) - 1f) * .5f; }
|
||||||
|
|
||||||
|
public readonly struct EaseInExpo : IEasing { public readonly float Evaluate(float value) => value == 0 ? 0 : Core.Math.Pow(2f, 10f * value - 10f); }
|
||||||
|
public readonly struct EaseOutExpo : IEasing { public readonly float Evaluate(float value) => value == 1f ? 1f : 1f - Core.Math.Pow(2f, -10f * value); }
|
||||||
|
public readonly struct EaseInOutExpo : IEasing { public readonly float Evaluate(float value) => value == 0 ? 0 : value == 1f ? 1f : value < 0.5f ? Core.Math.Pow(2f, 20 * value - 10f) * .5f : (2f - Core.Math.Pow(2f, -20 * value + 10f)) * .5f; }
|
||||||
|
|
||||||
|
public readonly struct EaseInCirc : IEasing { public readonly float Evaluate(float value) => 1f - Core.Math.Sqrt(1f - Core.Math.Pow(value, 2f)); }
|
||||||
|
public readonly struct EaseOutCirc : IEasing { public readonly float Evaluate(float value) => Core.Math.Sqrt(1f - Core.Math.Pow(value - 1f, 2f)); }
|
||||||
|
public readonly struct EaseInOutCirc : IEasing { public readonly float Evaluate(float value) => value < 0.5f ? (1f - Core.Math.Sqrt(1f - Core.Math.Pow(2f * value, 2f))) * .5f : (Core.Math.Sqrt(1f - Core.Math.Pow(-2f * value + 2f, 2f)) + 1f) * .5f; }
|
||||||
|
|
||||||
|
public readonly struct EaseInBack : IEasing { public readonly float Evaluate(float value) => EaseConstants.c3 * value * value * value - EaseConstants.c1 * value * value; }
|
||||||
|
public readonly struct EaseOutBack : IEasing { public readonly float Evaluate(float value) => 1f + EaseConstants.c3 * Core.Math.Pow(value - 1f, 3) + EaseConstants.c1 * Core.Math.Pow(value - 1f, 2f); }
|
||||||
|
public readonly struct EaseInOutBack : IEasing { public readonly float Evaluate(float value) => value < 0.5f ? Core.Math.Pow(2f * value, 2f) * ((EaseConstants.c2 + 1f) * 2f * value - EaseConstants.c2) * .5f : (Core.Math.Pow(2f * value - 2f, 2f) * ((EaseConstants.c2 + 1f) * (value * 2f - 2f) + EaseConstants.c2) + 2f) * .5f; }
|
||||||
|
|
||||||
|
public readonly struct EaseInElastic : IEasing { public readonly float Evaluate(float value) => value == 0 ? 0 : value == 1f ? 1f : -Core.Math.Pow(2f, 10f * value - 10f) * Core.Math.Sin((value * 10f - 10.75f) * EaseConstants.c4); }
|
||||||
|
public readonly struct EaseOutElastic : IEasing { public readonly float Evaluate(float value) => value == 0 ? 0 : value == 1f ? 1f : Core.Math.Pow(2f, -10f * value) * Core.Math.Sin((value * 10f - 0.75f) * EaseConstants.c4) + 1f; }
|
||||||
|
public readonly struct EaseInOutElastic : IEasing { public readonly float Evaluate(float value) => value == 0 ? 0 : value == 1f ? 1f : value < 0.5f ? -(Core.Math.Pow(2f, 20 * value - 10f) * Core.Math.Sin((20 * value - 11.125f) * EaseConstants.c5)) * .5f : Core.Math.Pow(2f, -20 * value + 10f) * Core.Math.Sin((20 * value - 11.125f) * EaseConstants.c5) * .5f + 1f; }
|
||||||
|
|
||||||
|
public readonly struct EaseInBounce : IEasing { public readonly float Evaluate(float value) => 1f - new EaseOutBounce().Evaluate(1f - value); }
|
||||||
|
public readonly struct EaseOutBounce : IEasing
|
||||||
|
{
|
||||||
|
public readonly float Evaluate(float value)
|
||||||
|
{
|
||||||
|
const float n1 = 7.5625f;
|
||||||
|
const float d1 = 2.75f;
|
||||||
|
|
||||||
|
if (value < 1 / d1)
|
||||||
|
return n1 * value * value;
|
||||||
|
|
||||||
|
if (value < 2 / d1)
|
||||||
|
return n1 * (value -= 1.5f / d1) * value + 0.75f;
|
||||||
|
|
||||||
|
if (value < 2.5 / d1)
|
||||||
|
return n1 * (value -= 2.25f / d1) * value + 0.9375f;
|
||||||
|
|
||||||
|
return n1 * (value -= 2.625f / d1) * value + 0.984375f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public readonly struct EaseInOutBounce : IEasing { public readonly float Evaluate(float value) => value < 0.5f ? (1f - new EaseOutBounce().Evaluate(1f - 2f * value)) * .5f : (1f + new EaseOutBounce().Evaluate(2f * value - 1f)) * .5f; }
|
7
Engine.Systems/Tween/IEasing.cs
Normal file
7
Engine.Systems/Tween/IEasing.cs
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
namespace Syntriax.Engine.Systems.Tween;
|
||||||
|
|
||||||
|
public interface IEasing
|
||||||
|
{
|
||||||
|
float Evaluate(float value);
|
||||||
|
}
|
||||||
|
|
25
Engine.Systems/Tween/ITween.cs
Normal file
25
Engine.Systems/Tween/ITween.cs
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
namespace Syntriax.Engine.Systems.Tween;
|
||||||
|
|
||||||
|
public interface ITween
|
||||||
|
{
|
||||||
|
event TweenEventHandler? OnStarted;
|
||||||
|
event TweenEventHandler? OnPaused;
|
||||||
|
event TweenEventHandler? OnResumed;
|
||||||
|
event TweenEventHandler? OnCancelled;
|
||||||
|
event TweenEventHandler? OnCompleted;
|
||||||
|
event TweenEventHandler? OnEnded;
|
||||||
|
|
||||||
|
event TweenEventHandler? OnUpdated;
|
||||||
|
event TweenDeltaEventHandler? OnDeltaUpdated;
|
||||||
|
|
||||||
|
TweenState State { get; set; }
|
||||||
|
|
||||||
|
float Counter { get; }
|
||||||
|
float Duration { get; }
|
||||||
|
float Progress { get; }
|
||||||
|
float Value { get; }
|
||||||
|
|
||||||
|
delegate void TweenEventHandler(ITween sender);
|
||||||
|
delegate void TweenDeltaEventHandler(ITween sender, float delta);
|
||||||
|
}
|
||||||
|
|
82
Engine.Systems/Tween/Tween.cs
Normal file
82
Engine.Systems/Tween/Tween.cs
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
using Syntriax.Engine.Core;
|
||||||
|
|
||||||
|
namespace Syntriax.Engine.Systems.Tween;
|
||||||
|
|
||||||
|
internal class Tween : ITween
|
||||||
|
{
|
||||||
|
public event ITween.TweenEventHandler? OnStarted = null;
|
||||||
|
public event ITween.TweenEventHandler? OnPaused = null;
|
||||||
|
public event ITween.TweenEventHandler? OnResumed = null;
|
||||||
|
public event ITween.TweenEventHandler? OnCancelled = null;
|
||||||
|
public event ITween.TweenEventHandler? OnCompleted = null;
|
||||||
|
public event ITween.TweenEventHandler? OnEnded = null;
|
||||||
|
public event ITween.TweenEventHandler? OnUpdated = null;
|
||||||
|
public event ITween.TweenDeltaEventHandler? OnDeltaUpdated = null;
|
||||||
|
|
||||||
|
private TweenState _state = TweenState.Idle;
|
||||||
|
public TweenState State
|
||||||
|
{
|
||||||
|
get => _state;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (value == _state)
|
||||||
|
return;
|
||||||
|
|
||||||
|
TweenState previousState = _state;
|
||||||
|
_state = value;
|
||||||
|
switch (value)
|
||||||
|
{
|
||||||
|
case TweenState.Completed: OnCompleted?.Invoke(this); OnEnded?.Invoke(this); break;
|
||||||
|
case TweenState.Cancelled: OnCancelled?.Invoke(this); OnEnded?.Invoke(this); break;
|
||||||
|
case TweenState.Paused: OnPaused?.Invoke(this); break;
|
||||||
|
case TweenState.Playing:
|
||||||
|
if (previousState == TweenState.Idle)
|
||||||
|
OnStarted?.Invoke(this);
|
||||||
|
else
|
||||||
|
OnResumed?.Invoke(this);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public float Duration { get; internal set; } = 1f;
|
||||||
|
public float Progress { get; internal set; } = 0f;
|
||||||
|
private float _counter = 0f;
|
||||||
|
|
||||||
|
public IEasing Easing { get; set; } = new EaseLinear();
|
||||||
|
public float Value => Easing.Evaluate(Progress);
|
||||||
|
|
||||||
|
public float Counter
|
||||||
|
{
|
||||||
|
get => _counter;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (value < _counter)
|
||||||
|
return;
|
||||||
|
|
||||||
|
float previousProgress = Progress;
|
||||||
|
|
||||||
|
_counter = value.Min(Duration).Max(0f);
|
||||||
|
Progress = Counter / Duration;
|
||||||
|
OnUpdated?.Invoke(this);
|
||||||
|
|
||||||
|
OnDeltaUpdated?.Invoke(this, Easing.Evaluate(Progress) - Easing.Evaluate(previousProgress));
|
||||||
|
|
||||||
|
if (_counter >= Duration)
|
||||||
|
State = TweenState.Completed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void Reset()
|
||||||
|
{
|
||||||
|
_counter = 0f;
|
||||||
|
Progress = 0f;
|
||||||
|
State = TweenState.Idle;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Tween() { }
|
||||||
|
public Tween(float duration)
|
||||||
|
{
|
||||||
|
Duration = duration;
|
||||||
|
}
|
||||||
|
}
|
90
Engine.Systems/Tween/TweenExtensions.cs
Normal file
90
Engine.Systems/Tween/TweenExtensions.cs
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
namespace Syntriax.Engine.Systems.Tween;
|
||||||
|
|
||||||
|
public static class TweenExtensions
|
||||||
|
{
|
||||||
|
public static ITween Loop(this ITween tween, int count)
|
||||||
|
{
|
||||||
|
Tween tweenConcrete = (Tween)tween;
|
||||||
|
int counter = count;
|
||||||
|
|
||||||
|
tweenConcrete.OnCompleted += _ =>
|
||||||
|
{
|
||||||
|
if (counter-- <= 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
tweenConcrete.Reset();
|
||||||
|
tweenConcrete.State = TweenState.Playing;
|
||||||
|
};
|
||||||
|
|
||||||
|
return tween;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ITween LoopInfinitely(this ITween tween)
|
||||||
|
{
|
||||||
|
Tween tweenConcrete = (Tween)tween;
|
||||||
|
tweenConcrete.OnCompleted += _ =>
|
||||||
|
{
|
||||||
|
tweenConcrete.Reset();
|
||||||
|
tweenConcrete.State = TweenState.Playing;
|
||||||
|
};
|
||||||
|
|
||||||
|
return tween;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ITween Ease(this ITween tween, IEasing easing)
|
||||||
|
{
|
||||||
|
Tween tweenConcrete = (Tween)tween;
|
||||||
|
tweenConcrete.Easing = easing;
|
||||||
|
|
||||||
|
return tween;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ITween OnStart(this ITween tween, Action callback)
|
||||||
|
{
|
||||||
|
Tween tweenConcrete = (Tween)tween;
|
||||||
|
tweenConcrete.OnStarted += _ => callback.Invoke();
|
||||||
|
return tween;
|
||||||
|
}
|
||||||
|
public static ITween OnPause(this ITween tween, Action callback)
|
||||||
|
{
|
||||||
|
Tween tweenConcrete = (Tween)tween;
|
||||||
|
tweenConcrete.OnPaused += _ => callback.Invoke();
|
||||||
|
return tween;
|
||||||
|
}
|
||||||
|
public static ITween OnResume(this ITween tween, Action callback)
|
||||||
|
{
|
||||||
|
Tween tweenConcrete = (Tween)tween;
|
||||||
|
tweenConcrete.OnResumed += _ => callback.Invoke();
|
||||||
|
return tween;
|
||||||
|
}
|
||||||
|
public static ITween OnCancel(this ITween tween, Action callback)
|
||||||
|
{
|
||||||
|
Tween tweenConcrete = (Tween)tween;
|
||||||
|
tweenConcrete.OnCancelled += _ => callback.Invoke();
|
||||||
|
return tween;
|
||||||
|
}
|
||||||
|
public static ITween OnComplete(this ITween tween, Action callback)
|
||||||
|
{
|
||||||
|
Tween tweenConcrete = (Tween)tween;
|
||||||
|
tweenConcrete.OnCompleted += _ => callback.Invoke();
|
||||||
|
return tween;
|
||||||
|
}
|
||||||
|
public static ITween OnEnd(this ITween tween, Action callback)
|
||||||
|
{
|
||||||
|
Tween tweenConcrete = (Tween)tween;
|
||||||
|
tweenConcrete.OnEnded += _ => callback.Invoke();
|
||||||
|
return tween;
|
||||||
|
}
|
||||||
|
public static ITween OnUpdate(this ITween tween, Action callback)
|
||||||
|
{
|
||||||
|
Tween tweenConcrete = (Tween)tween;
|
||||||
|
tweenConcrete.OnUpdated += _ => callback.Invoke();
|
||||||
|
return tween;
|
||||||
|
}
|
||||||
|
public static ITween OnDeltaUpdate(this ITween tween, Action<float> callback)
|
||||||
|
{
|
||||||
|
Tween tweenConcrete = (Tween)tween;
|
||||||
|
tweenConcrete.OnDeltaUpdated += (_, delta) => callback.Invoke(delta);
|
||||||
|
return tween;
|
||||||
|
}
|
||||||
|
}
|
66
Engine.Systems/Tween/TweenManager.cs
Normal file
66
Engine.Systems/Tween/TweenManager.cs
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
using System.Collections;
|
||||||
|
|
||||||
|
using Syntriax.Engine.Core;
|
||||||
|
using Syntriax.Engine.Core.Abstract;
|
||||||
|
|
||||||
|
namespace Syntriax.Engine.Systems.Tween;
|
||||||
|
|
||||||
|
public class TweenManager : HierarchyObject
|
||||||
|
{
|
||||||
|
private CoroutineManager coroutineManager = null!;
|
||||||
|
|
||||||
|
private readonly Dictionary<ITween, IEnumerator> runningCoroutines = [];
|
||||||
|
|
||||||
|
public ITween StartTween(float duration, TweenSetCallback? setCallback = null)
|
||||||
|
{
|
||||||
|
Tween tween = new(duration);
|
||||||
|
tween.OnUpdated += tween => setCallback?.Invoke(tween.Value);
|
||||||
|
runningCoroutines.Add(tween, coroutineManager.StartCoroutine(RunTween(tween)));
|
||||||
|
return tween;
|
||||||
|
}
|
||||||
|
|
||||||
|
private IEnumerator RunTween(Tween tween)
|
||||||
|
{
|
||||||
|
tween.State = TweenState.Playing;
|
||||||
|
yield return null;
|
||||||
|
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
if (tween.State.CheckFlag(TweenState.Cancelled | TweenState.Completed))
|
||||||
|
break;
|
||||||
|
|
||||||
|
if (tween.State.CheckFlag(TweenState.Paused))
|
||||||
|
{
|
||||||
|
yield return null;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
tween.Counter += GameManager.Time.DeltaTime;
|
||||||
|
yield return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
runningCoroutines.Remove(tween);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void CancelTween(ITween tween)
|
||||||
|
{
|
||||||
|
if (!runningCoroutines.TryGetValue(tween, out IEnumerator? runningCoroutine))
|
||||||
|
return;
|
||||||
|
|
||||||
|
tween.State = TweenState.Cancelled;
|
||||||
|
coroutineManager.StopCoroutine(runningCoroutine);
|
||||||
|
runningCoroutines.Remove(tween);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnEnteringHierarchy(IGameManager gameManager)
|
||||||
|
{
|
||||||
|
coroutineManager = gameManager.FindHierarchyObject<CoroutineManager>() ?? throw new($"No {nameof(CoroutineManager)} was found in the game manager");
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnExitingHierarchy(IGameManager gameManager)
|
||||||
|
{
|
||||||
|
coroutineManager = null!;
|
||||||
|
}
|
||||||
|
|
||||||
|
public delegate void TweenSetCallback(float t);
|
||||||
|
}
|
10
Engine.Systems/Tween/TweenState.cs
Normal file
10
Engine.Systems/Tween/TweenState.cs
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
namespace Syntriax.Engine.Systems.Tween;
|
||||||
|
|
||||||
|
public enum TweenState
|
||||||
|
{
|
||||||
|
Idle = 0b00001,
|
||||||
|
Playing = 0b00010,
|
||||||
|
Paused = 0b00100,
|
||||||
|
Cancelled = 0b01000,
|
||||||
|
Completed = 0b10000,
|
||||||
|
}
|
@ -0,0 +1,5 @@
|
|||||||
|
using Syntriax.Engine.Core;
|
||||||
|
|
||||||
|
namespace Syntriax.Engine.Systems.Tween;
|
||||||
|
|
||||||
|
public class WaitForTweenCompleteCoroutineYield(ITween tween) : CoroutineYield(() => tween.State == TweenState.Completed);
|
@ -0,0 +1,5 @@
|
|||||||
|
using Syntriax.Engine.Core;
|
||||||
|
|
||||||
|
namespace Syntriax.Engine.Systems.Tween;
|
||||||
|
|
||||||
|
public class WaitWhileTweenActiveCoroutineYield(ITween tween) : CoroutineYield(() => tween.State.CheckFlag(TweenState.Completed | TweenState.Cancelled));
|
Loading…
x
Reference in New Issue
Block a user