// Copyright (c) 2015 - 2023 Doozy Entertainment. All Rights Reserved. // This code can only be used under the standard Unity Asset Store End User License Agreement // A Copy of the EULA APPENDIX 1 is available at http://unity3d.com/company/legal/as_terms using System; using Doozy.Runtime.Reactor.Internal; using Doozy.Runtime.Reactor.Reactions; using UnityEngine; namespace Doozy.Runtime.Reactor.Animations { /// /// Reactor animation that works with a RectTransform. /// It works as a combined animation for position, rotation, scale and alpha (transparency). /// [Serializable] public class UIAnimation : ReactorAnimation { /// /// Target RectTransform for the animation /// (animates the position, rotation and scale via the Move, Rotate and Scale animations) /// public RectTransform rectTransform { get; internal set; } /// Checks if the target RectTransform is not null public override bool hasTarget => rectTransform != null; /// /// Target CanvasGroup for the animation /// (animates group alpha via the Fade animation) /// public CanvasGroup canvasGroup { get; internal set; } [SerializeField] private UIAnimationType AnimationType; /// Type of animation. Used to show specialized settings and to sava/load dedicated presets public UIAnimationType animationType { get => AnimationType; set { AnimationType = value; Move.animationType = value; } } /// Move reaction designed to change the position of a target RectTransform public UIMoveReaction Move; /// Rotate reaction designed to change the rotation of a target RectTransform public UIRotateReaction Rotate; /// Scale reaction designed to change the scale of a target RectTransform public UIScaleReaction Scale; /// Fade reaction designed to change the alpha value of a target CanvasGroup public UIFadeReaction Fade; /// Move start position value (RectTransform.anchoredPosition3D) public Vector3 startPosition { get => Move.startPosition; set => Move.startPosition = value; } /// Rotate start rotation value (RectTransform.localEulerAngles) public Vector3 startRotation { get => Rotate.startRotation; set => Rotate.startRotation = value; } /// Scale start scale value (RectTransform.localScale) public Vector3 startScale { get => Scale.startScale; set => Scale.startScale = value; } /// Fade start alpha value (CanvasGroup.alpha) public float startAlpha { get => Fade.startAlpha; set => Fade.startAlpha = value; } /// Is the animation enabled public override bool isEnabled => Move.enabled | Rotate.enabled | Scale.enabled | Fade.enabled; /// Is the animation in the idle state (is enabled, but not active) public override bool isIdle => Move.isIdle | Rotate.isIdle | Scale.isIdle | Fade.isIdle; /// Is the animation in the active state (is enabled and either playing or paused) public override bool isActive => Move.isActive | Rotate.isActive | Scale.isActive | Fade.isActive; /// Is the animation in the paused state (is enabled, started playing and then paused) public override bool isPaused => Move.isPaused | Rotate.isPaused | Scale.isPaused | Fade.isPaused; /// Is the animation in the playing state (is enabled and started playing) public override bool isPlaying => Move.isPlaying || Rotate.isPlaying || Scale.isPlaying || Fade.isPlaying; /// Is the animation in the start delay state (is enabled, started playing and waiting to start running after the start delay duration has passed) public override bool inStartDelay => Move.inStartDelay | Rotate.inStartDelay | Scale.inStartDelay | Fade.inStartDelay; /// Is the animation in the loop delay state (is enabled, started playing and is between loops waiting to continue running after the loop delay duration has passed) public override bool inLoopDelay => Move.inLoopDelay | Rotate.inLoopDelay | Scale.inLoopDelay | Fade.inLoopDelay; /// Construct a new UIAnimation with the given RectTransform target and CanvasGroup target /// Target RectTransform /// Target CanvasGroup public UIAnimation(RectTransform targetRectTransform, CanvasGroup targetCanvasGroup = null) => SetTarget(targetRectTransform, targetCanvasGroup); /// /// Set the RectTransform target and CanvasGroup target. /// If a CanvasGroup reference is not set, this method will try to get the reference by trying to get the component from the RectTransform. /// If the CanvasGroup component is not found, one will be added and used as a reference. /// /// RectTransform target /// CanvasGroup target public void SetTarget(RectTransform targetRectTransform, CanvasGroup targetCanvasGroup = null) { rectTransform = null; canvasGroup = null; _ = targetRectTransform ? targetRectTransform : throw new NullReferenceException(nameof(targetRectTransform)); rectTransform = targetRectTransform; if (targetCanvasGroup == null) targetCanvasGroup = targetRectTransform.gameObject.GetComponent(); canvasGroup = targetCanvasGroup == null ? targetRectTransform.gameObject.AddComponent() : targetCanvasGroup; Initialize(); } /// Initialize the animation public void Initialize() { Move?.Stop(true); Move ??= Reaction.Get(); Move.SetTarget(rectTransform); Move.animationType = animationType; Rotate?.Stop(true); Rotate ??= Reaction.Get(); Rotate.SetTarget(rectTransform); Scale?.Stop(true); Scale ??= Reaction.Get(); Scale.SetTarget(rectTransform); Fade?.Stop(true); Fade ??= Reaction.Get(); Fade.SetTarget(rectTransform, canvasGroup); UpdateValues(); } /// /// Recycle the reactions controlled by this animation. /// Reactions are pooled can (and should) be recycled to improve overall performance. /// public override void Recycle() { Move?.Recycle(); Rotate?.Recycle(); Scale?.Recycle(); Fade?.Recycle(); } /// /// Update the initial values for the reactions controlled by this animation /// public override void UpdateValues() { if (canvasGroup != null) Fade.UpdateValues(); //calculate fade Scale.UpdateValues(); //calculate scale Rotate.UpdateValues(); // calculate rotation //update move settings after calculating scale { Move.UseCustomLocalScale = Scale.enabled; Move.CustomFromLocalScale = Scale.enabled ? Scale.fromValue : startScale; Move.CustomToLocalScale = Scale.enabled ? Scale.toValue : startScale; } //update move settings after rotation { Move.UseCustomLocalRotation = Rotate.enabled; Move.CustomFromLocalRotation = Rotate.enabled ? Rotate.fromValue : startRotation; Move.CustomToLocalRotation = Rotate.enabled ? Rotate.toValue : startRotation; } Move.animationType = animationType; //enforce animation type Move.UpdateValues(); //calculate position } /// /// Stop all the reactions controlled by this animation /// public override void StopAllReactionsOnTarget() => Reaction.StopAllReactionsByTargetObject(rectTransform, true); /// Set the animation at the given progress value /// Target progress [0,1] public override void SetProgressAt(float targetProgress) { base.SetProgressAt(targetProgress); if (Fade.enabled) Fade.SetProgressAt(targetProgress); if (Scale.enabled) Scale.SetProgressAt(targetProgress); if (Rotate.enabled) Rotate.SetProgressAt(targetProgress); if (Move.enabled) Move.SetProgressAt(targetProgress); if (animationType != UIAnimationType.Custom) ResetToStartValues(); } /// Play the animation at the given progress value from the current value /// To progress [0,1] public override void PlayToProgress(float toProgress) { base.PlayToProgress(toProgress); if (Fade.enabled) Fade.PlayToProgress(toProgress); if (Scale.enabled) Scale.PlayToProgress(toProgress); if (Rotate.enabled) Rotate.PlayToProgress(toProgress); if (Move.enabled) Move.PlayToProgress(toProgress); if (animationType != UIAnimationType.Custom) ResetToStartValues(); } /// Play the animation from the given progress value to the current value /// From progress [0,1] public override void PlayFromProgress(float fromProgress) { base.PlayFromProgress(fromProgress); if (Move.enabled) Move.PlayFromProgress(fromProgress); if (Rotate.enabled) Rotate.PlayFromProgress(fromProgress); if (Scale.enabled) Scale.PlayFromProgress(fromProgress); if (Fade.enabled) Fade.PlayFromProgress(fromProgress); if (animationType != UIAnimationType.Custom) ResetToStartValues(); } /// Play the animation from the given progress value to the given progress value /// From progress [0,1] /// To progress [0,1] public override void PlayFromToProgress(float fromProgress, float toProgress) { base.PlayFromToProgress(fromProgress, toProgress); if (Move.enabled) Move.PlayFromToProgress(fromProgress, toProgress); if (Rotate.enabled) Rotate.PlayFromToProgress(fromProgress, toProgress); if (Scale.enabled) Scale.PlayFromToProgress(fromProgress, toProgress); if (Fade.enabled) Fade.PlayFromToProgress(fromProgress, toProgress); if (animationType != UIAnimationType.Custom) ResetToStartValues(); } /// Play the animation all the way /// Play the animation in reverse? public override void Play(bool inReverse = false) { if (rectTransform == null) return; RegisterCallbacks(); if (!isActive) { StopAllReactionsOnTarget(); ResetToStartValues(); } if (Move.enabled) Move.Play(inReverse); if (Rotate.enabled) Rotate.Play(inReverse); if (Scale.enabled) Scale.Play(inReverse); if (Fade.enabled) Fade.Play(inReverse); // if (!isPlaying && animationType != UIAnimationType.Custom) // ResetToStartValues(); } /// Reset all the reactions to their initial values (if the animation is enabled) /// If true, forced will ignore if the animation is enabled or not public override void ResetToStartValues(bool forced = false) { if (rectTransform == null) return; if (forced || !Move.enabled) Move.SetValue(startPosition); if (forced || !Rotate.enabled) Rotate.SetValue(startRotation); if (forced || !Scale.enabled) Scale.SetValue(startScale); if (forced || !Fade.enabled) Fade.SetValue(startAlpha); } /// /// Stop the animation. /// Called every time the animation is stopped. Also called before calling Finish() /// public override void Stop() { if (Move.isActive || Move.enabled) Move.Stop(); if (Rotate.isActive || Rotate.enabled) Rotate.Stop(); if (Scale.isActive || Scale.enabled) Scale.Stop(); if (Fade.isActive || Fade.enabled) Fade.Stop(); base.Stop(); } /// /// Finish the animation. /// Called to mark that that animation completed playing. /// public override void Finish() { if (Move.isActive || Move.enabled) Move.Finish(); if (Rotate.isActive || Rotate.enabled) Rotate.Finish(); if (Scale.isActive || Scale.enabled) Scale.Finish(); if (Fade.isActive || Fade.enabled) Fade.Finish(); base.Finish(); } /// /// Reverse the animation's direction while playing. /// Works only if the animation is active (it either playing or paused) /// public override void Reverse() { if (Move.isActive) Move.Reverse(); else if (Move.enabled) Move.Play(PlayDirection.Reverse); if (Rotate.isActive) Rotate.Reverse(); else if (Rotate.enabled) Rotate.Play(PlayDirection.Reverse); if (Scale.isActive) Scale.Reverse(); else if (Scale.enabled) Scale.Play(PlayDirection.Reverse); if (Fade.isActive) Fade.Reverse(); else if (Fade.enabled) Fade.Play(PlayDirection.Reverse); } /// /// Rewind the animation to the start values /// public override void Rewind() { if (Move.enabled) Move.Rewind(); if (Rotate.enabled) Rotate.Rewind(); if (Scale.enabled) Scale.Rewind(); if (Fade.enabled) Fade.Rewind(); } /// /// Pause the animation. /// Works only if the animation is playing. /// public override void Pause() { Move.Pause(); Rotate.Pause(); Scale.Pause(); Fade.Pause(); } /// /// Resume a paused animation. /// Works only if the animation is paused. /// public override void Resume() { Move.Resume(); Rotate.Resume(); Scale.Resume(); Fade.Resume(); } public float GetStartDelay() { float move = Move.enabled ? Move.isActive ? Move.startDelay : Move.settings.GetStartDelay() : 0; float rotate = Rotate.enabled ? Rotate.isActive ? Rotate.startDelay : Rotate.settings.GetStartDelay() : 0; float scale = Scale.enabled ? Scale.isActive ? Scale.startDelay : Scale.settings.GetStartDelay() : 0; float fade = Fade.enabled ? Fade.isActive ? Fade.startDelay : Fade.settings.GetStartDelay() : 0; return move + rotate + scale + fade; } public float GetDuration() { float move = Move.enabled ? Move.isActive ? Move.duration : Move.settings.GetDuration() : 0; float rotate = Rotate.enabled ? Rotate.isActive ? Rotate.duration : Rotate.settings.GetDuration() : 0; float scale = Scale.enabled ? Scale.isActive ? Scale.duration : Scale.settings.GetDuration() : 0; float fade = Fade.enabled ? Fade.isActive ? Fade.duration : Fade.settings.GetDuration() : 0; return move + rotate + scale + fade; } public float GetTotalDuration() => GetStartDelay() + GetDuration(); /// /// Register all callbacks to the animation /// protected override void RegisterCallbacks() { base.RegisterCallbacks(); if (Move.enabled) { startedReactionsCount++; Move.OnPlayCallback += InvokeOnPlay; Move.OnStopCallback += InvokeOnStop; Move.OnFinishCallback += InvokeOnFinish; } if (Rotate.enabled) { startedReactionsCount++; Rotate.OnPlayCallback += InvokeOnPlay; Rotate.OnStopCallback += InvokeOnStop; Rotate.OnFinishCallback += InvokeOnFinish; } if (Scale.enabled) { startedReactionsCount++; Scale.OnPlayCallback += InvokeOnPlay; Scale.OnStopCallback += InvokeOnStop; Scale.OnFinishCallback += InvokeOnFinish; } if (Fade.enabled) { startedReactionsCount++; Fade.OnPlayCallback += InvokeOnPlay; Fade.OnStopCallback += InvokeOnStop; Fade.OnFinishCallback += InvokeOnFinish; } } /// Unregister OnPlayCallback protected override void UnregisterOnPlayCallbacks() { if (Move.enabled) Move.OnPlayCallback -= InvokeOnPlay; if (Rotate.enabled) Rotate.OnPlayCallback -= InvokeOnPlay; if (Scale.enabled) Scale.OnPlayCallback -= InvokeOnPlay; if (Fade.enabled) Fade.OnPlayCallback -= InvokeOnPlay; } /// Unregister OnStopCallback protected override void UnregisterOnStopCallbacks() { if (Move.enabled) Move.OnStopCallback -= InvokeOnStop; if (Rotate.enabled) Rotate.OnStopCallback -= InvokeOnStop; if (Scale.enabled) Scale.OnStopCallback -= InvokeOnStop; if (Fade.enabled) Fade.OnStopCallback -= InvokeOnStop; } /// Unregister OnFinishCallback protected override void UnregisterOnFinishCallbacks() { if (Move.enabled) Move.OnFinishCallback -= InvokeOnFinish; if (Rotate.enabled) Rotate.OnFinishCallback -= InvokeOnFinish; if (Scale.enabled) Scale.OnFinishCallback -= InvokeOnFinish; if (Fade.enabled) Fade.OnFinishCallback -= InvokeOnFinish; } } }