using System;
using GTweens.Easings;
using GTweens.Enums;
using GTweens.TweenBehaviours;
namespace GTweens.Tweens
{
///
/// Represents the core item for building tweening animations.
///
public sealed class GTween
{
public event Action? OnStartAction;
public event Action? OnTickAction;
public event Action? OnLoopAction;
public event Action? OnResetAction;
public event Action? OnCompleteAction;
public event Action? OnKillAction;
public event Action? OnCompleteOrKillAction;
public event Action? OnTimeScaleChangedAction;
public ITweenBehaviour Behaviour { get; }
public float TimeScale { get; private set; } = 1;
public float Delay { get; private set; }
public int Loops { get; private set; }
public ResetMode LoopResetMode { get; private set; }
public bool IsNested { get; set; }
public bool IsPlaying { get; private set; }
public bool IsPaused { get; private set; }
public bool IsCompleted { get; private set; }
public bool IsKilled { get; private set; }
public bool IsCompletedOrKilled => IsCompleted || IsKilled;
public bool IsAlive { get; set; }
float _delayRemaining;
int _loopsRemaining;
public GTween(ITweenBehaviour behaviour)
{
Behaviour = behaviour;
}
///
/// Starts the tween.
///
/// Determines if the tween should complete instantly.
public void Start(bool isCompletingInstantly = false)
{
if (IsPlaying)
{
Kill();
}
IsPlaying = true;
IsCompleted = false;
IsKilled = false;
_delayRemaining = Delay;
_loopsRemaining = Loops;
Behaviour.Start(isCompletingInstantly);
OnStartAction?.Invoke();
}
///
/// Advances the tween by a given delta time.
///
/// The elapsed time since the last update.
public void Tick(float deltaTime)
{
if (!IsPlaying)
{
return;
}
if (IsPaused)
{
return;
}
float deltaTimeWithTimeScale = TimeScale * deltaTime;
if (_delayRemaining > 0f)
{
_delayRemaining -= deltaTime;
return;
}
Behaviour.Tick(deltaTimeWithTimeScale);
OnTickAction?.Invoke();
bool isFinished = Behaviour.GetFinished();
if (!isFinished)
{
return;
}
bool needsToLoop = _loopsRemaining > 0 && Behaviour.GetLoopable();
if(needsToLoop)
{
Loop(LoopResetMode);
}
else
{
MarkFinished();
}
}
///
/// Sets if the tween can continue updating or not. Has effect even if the tween has not startet playing.
/// If you start a paused tween, it will not update until it's unpaused.
///
public GTween SetPaused(bool paused)
{
IsPaused = paused;
return this;
}
///
/// Instantly reaches the final state of the tween, and stops playing.
///
public void Complete()
{
if (!IsPlaying && !IsCompleted)
{
Start(true);
}
Behaviour.Complete();
_loopsRemaining = 0;
MarkFinished();
}
///
/// Kills the tween. This means that the tween will stop playing, leaving it at its current state.
///
public void Kill()
{
if (!IsPlaying)
{
return;
}
IsPlaying = false;
IsCompleted = false;
IsKilled = true;
Behaviour.Kill();
OnKillAction?.Invoke();
OnCompleteOrKillAction?.Invoke();
}
///
/// Resets the tween, optionally killing it and specifying the reset mode.
///
/// Whether to kill the tween.
/// The reset mode to use.
/// The current GTween instance for method chaining.
public GTween Reset(bool kill, ResetMode resetMode = ResetMode.InitialValues)
{
if (kill)
{
Kill();
IsPlaying = false;
}
IsCompleted = false;
IsKilled = false;
_delayRemaining = Delay;
Behaviour.Reset(kill, resetMode);
OnResetAction?.Invoke();
return this;
}
///
/// Simulates the progress of a GTween animation for a specified duration.
///
/// The simulated time in seconds.
/// The current GTween instance for method chaining.
public GTween Simulate(float time)
{
if (!IsPlaying)
{
Start();
}
bool loops = Loops > 0;
float simulationTime = loops ?
time % Behaviour.GetDuration() :
Math.Min(time, Behaviour.GetDuration());
float progress = Behaviour.GetElapsed();
while (simulationTime > 0)
{
Tick(simulationTime);
float newProgress = Behaviour.GetElapsed();
float tickElapsed = newProgress - progress;
progress = newProgress;
simulationTime -= tickElapsed;
}
return this;
}
///
/// Adds some delay (in seconds) at the begining of the tween.
///
public GTween SetDelay(float delaySeconds)
{
Delay = delaySeconds;
return this;
}
///
/// Sets the time scale for the tween, affecting its speed.
/// By default time scale is 1.0f. If you decrease this number, the tween
/// will update slower. If you increase it, the tween will update faster.
///
/// The time scale to set.
/// The current GTween instance for method chaining.
public GTween SetTimeScale(float timeScale)
{
TimeScale = timeScale;
OnTimeScaleChangedAction?.Invoke(timeScale);
return this;
}
///
/// Sets the easing function for the tween.
///
/// The custom easing function to use.
/// The current GTween instance for method chaining.
public GTween SetEasing(EasingDelegate easingFunction)
{
Behaviour.SetEasing(easingFunction);
return this;
}
///
/// Sets the predefined easing function for the tween.
///
/// The predefined easing to use.
/// The current GTween instance for method chaining.
public GTween SetEasing(Easing easing)
{
return SetEasing(PresetEasingDelegateFactory.GetEaseDelegate(easing));
}
///
/// Sets the number of loops and the reset mode for the tween.
///
/// The number of loops.
/// The reset mode to use when looping.
/// The current GTween instance for method chaining.
public GTween SetLoops(int loops, ResetMode resetMode = ResetMode.InitialValues)
{
Loops = Math.Max(loops, 0);
LoopResetMode = resetMode;
return this;
}
///
/// Sets the tween to have and almost infinite amount of loops with the specified reset mode.
///
/// The reset mode to use when looping.
/// The current GTween instance for method chaining.
public GTween SetMaxLoops(ResetMode resetMode = ResetMode.InitialValues)
{
return SetLoops(int.MaxValue, resetMode);
}
///
/// Calculates the total duration of the tween.
///
public float GetDuration()
{
return Behaviour.GetDuration();
}
///
/// Calculates the time elapsed since the tween started playing.
///
public float GetElapsed()
{
if(!IsPlaying && !IsCompleted)
{
return 0f;
}
if(!IsPlaying && IsCompleted)
{
return GetDuration();
}
return Behaviour.GetElapsed();
}
///
/// Gets the time left remaining on the tween (duration - elapsed).
///
public float GetRemaining()
{
if(!IsPlaying && !IsCompleted)
{
return GetDuration();
}
if(!IsPlaying && IsCompleted)
{
return 0f;
}
return Behaviour.GetRemaining();
}
public GTween OnStart(Action action)
{
OnStartAction += action;
return this;
}
public GTween OnTick(Action action)
{
OnTickAction += action;
return this;
}
public GTween OnLoop(Action action)
{
OnLoopAction += action;
return this;
}
public GTween OnReset(Action action)
{
OnResetAction += action;
return this;
}
public GTween OnComplete(Action action)
{
OnCompleteAction += action;
return this;
}
public GTween OnKill(Action action)
{
OnKillAction += action;
return this;
}
public GTween OnCompleteOrKill(Action action)
{
OnCompleteOrKillAction += action;
return this;
}
public GTween OnTimeScaleChanged(Action action)
{
OnTimeScaleChangedAction += action;
return this;
}
void Loop(ResetMode loopResetMode)
{
bool needsToLoop = _loopsRemaining > 0;
if(!needsToLoop || !Behaviour.GetLoopable())
{
return;
}
--_loopsRemaining;
Reset(kill: false, loopResetMode);
IsPlaying = true;
IsCompleted = false;
IsKilled = false;
Behaviour.Start(false);
OnLoopAction?.Invoke();
}
void MarkFinished()
{
if (!IsPlaying)
{
return;
}
IsPlaying = false;
IsCompleted = true;
IsKilled = false;
Behaviour.Complete();
OnCompleteAction?.Invoke();
OnCompleteOrKillAction?.Invoke();
}
}
}