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(); } } }