// 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 System.Collections.Generic; using System.Linq; using Doozy.Runtime.Reactor.Easings; using Doozy.Runtime.Reactor.Internal; using UnityEngine; using Random = UnityEngine.Random; namespace Doozy.Runtime.Reactor.Reactions { [Serializable] public class SpriteReaction : DynamicReaction { public const int DEFAULT_CAPACITY = 100; private List sprites { get; set; } public int firstFrame => 0; public int lastFrame => sprites == null || sprites.Count == 0 ? 0 : sprites.Count - 1; public int currentFrame => CurrentValue; public Sprite current => sprites == null ? null : sprites.Count == 0 ? null : sprites?[Mathf.Clamp(currentFrame, 0, lastFrame)]; public ReactionCallback OnFrameChangedCallback; public SpriteReaction() { this.SetEase(Ease.Linear); FromValue = firstFrame; ToValue = lastFrame; } public SpriteReaction(IEnumerable sprites) : this() { SetSprites(sprites.ToList()); FromValue = firstFrame; ToValue = lastFrame; } public override void Reset() { base.Reset(); this.SetEase(Ease.Linear); if (sprites == null) { sprites = new List(DEFAULT_CAPACITY); } else { sprites.Clear(); } sprites.Add(null); OnFrameChangedCallback = null; } public override float GetProgressAtValue(int value) => Mathf.Clamp01(Mathf.InverseLerp(FromValue, ToValue, value)); public override void UpdateCurrentValue() { // CurrentValue = (int)Mathf.Lerp(FromValue, ToValue, easedProgress); CurrentValue = (int)Mathf.Lerp(cycleFrom, cycleTo, currentCycleEasedProgress); CurrentValue = Mathf.Clamp(CurrentValue, firstFrame, lastFrame); setter?.Invoke(current); OnValueChangedCallback?.Invoke(CurrentValue); OnFrameChangedCallback?.Invoke(current); } /// Set the current frame to the given value (frameNumber) /// Frame Number (value clamped between first and last frames) public sealed override Reaction SetValue(int value) { value = Mathf.Clamp(value, firstFrame, lastFrame); base.SetValue(value); setter?.Invoke(current); return this; } public override Reaction SetFrom(int value, bool relative = false) { FromValue = value; if (relative) FromValue += CurrentValue; FromValue = Mathf.Clamp(FromValue, firstFrame, lastFrame); if (isActive) ComputePlayMode(); return this; } public override Reaction SetTo(int value, bool relative = false) { ToValue = value; if (relative) ToValue += CurrentValue; ToValue = Mathf.Clamp(ToValue, firstFrame, lastFrame); if (isActive) ComputePlayMode(); return this; } public override void Play(bool inReverse = false) { // FromValue = firstFrame; // ToValue = lastFrame; base.Play(inReverse); } public override void PlayFromToProgress(float fromProgress, float toProgress) { FromValue = firstFrame; ToValue = lastFrame; base.PlayFromToProgress(fromProgress, toProgress); } public override void PlayToProgress(float toProgress) { FromValue = firstFrame; ToValue = lastFrame; base.PlayToProgress(toProgress); } public override void PlayFromProgress(float fromProgress) { FromValue = firstFrame; ToValue = lastFrame; base.PlayFromProgress(fromProgress); } /// /// Set the sprite at the given frameNumber. /// If the animation is playing, it will stop. /// /// Value clamped between first and last frames public SpriteReaction SetFrame(int frameNumber) => (SpriteReaction)SetValue(frameNumber); /// Order descending the sprites public SpriteReaction ReverseSpritesOrder() { int count = sprites.Count; for (int i = 0; i < count / 2; i++) (sprites[i], sprites[count - i - 1]) = (sprites[count - i - 1], sprites[i]); // sprites = sprites.OrderByDescending(t => t.name).ToList(); setter?.Invoke(current); return this; } /// Set the sprites for this frame by frame animation reaction /// Sprites /// Set first frame public SpriteReaction SetSprites(List spriteList, bool setFirstFrame = true) { _ = spriteList ?? throw new ArgumentNullException(nameof(spriteList)); if (isActive) Stop(true); int spriteListCount = spriteList.Count; if (sprites != null && sprites.Count > 0) { sprites.Clear(); if (sprites.Capacity != spriteListCount) sprites.Capacity = spriteListCount; } sprites ??= new List(spriteListCount); sprites.AddRange(spriteList); FromValue = firstFrame; ToValue = lastFrame; if (setFirstFrame) SetFirstFrame(); return this; } /// /// Set the frame at the given progress value. /// If the animation is playing, it will stop. /// /// Target progress value public SpriteReaction SetFrameAtProgress(float targetProgress) => SetFrame((int)(lastFrame * targetProgress)); /// Get the frame number at the given progress value /// Target progress value public int GetFrameAtProgress(float targetProgress) => Mathf.Clamp((int)(lastFrame * Mathf.Clamp01(targetProgress)), firstFrame, lastFrame); /// Get the Sprite at the given progress value /// Target progress value public Sprite GetSpriteAtProgress(float targetProgress) => sprites == null || sprites.Count == 0 ? null : sprites[GetFrameAtProgress(targetProgress)]; /// /// Set the frame at the first frame (progress = 0) /// If the animation is playing, it will stop. /// public SpriteReaction SetFirstFrame() => SetFrame(firstFrame); /// Set the frame at the last frame (progress = 1) public SpriteReaction SetLastFrame() => SetFrame(lastFrame); /// Get the progress value for the given frameNumber /// Frame number public float GetProgressAtFrame(int frameNumber) => (float)Mathf.Clamp(frameNumber, firstFrame, lastFrame) / lastFrame; /// Get the progress value for the current frame public float GetCurrentFrameProgress() => GetProgressAtFrame(currentFrame); protected override void ComputeSpring() { base.ComputeSpring(); float springForce = settings.strength; float forceReduction = springForce / (numberOfCycles - 1); for (int i = 0; i < numberOfCycles; i++) { cycleValues[i] = (int)(FromValue + ToValue * (i % 2 == 0 ? springForce : -springForce * settings.elasticity)); cycleValues[i] = Mathf.Clamp(cycleValues[i], firstFrame, lastFrame); springForce -= forceReduction; } cycleValues[numberOfCycles - 1] = FromValue; } protected override void ComputeShake() { base.ComputeShake(); for (int i = 0; i < numberOfCycles; i++) { if (i % 2 == 0) { cycleValues[i] = FromValue; continue; } float random = Random.value; cycleValues[i] = (int)(FromValue + ToValue * random * settings.strength); cycleValues[i] = Mathf.Clamp(cycleValues[i], firstFrame, lastFrame); } cycleValues[numberOfCycles - 1] = FromValue; } } public static class SpriteReactionExtensions { #region getter public static T SetGetter(this T target, PropertyGetter getter) where T : SpriteReaction { target.getter = getter; return target; } public static T ClearGetter(this T target) where T : SpriteReaction => target.SetGetter(null); #endregion #region setter public static T SetSetter(this T target, PropertySetter setter) where T : SpriteReaction { target.setter = setter; return target; } public static T ClearSetter(this T target) where T : SpriteReaction => target.SetSetter(null); #endregion #region OnValueChanged public static T SetOnValueChangedCallback(this T target, ReactionCallback callback) where T : SpriteReaction { if (callback == null) return target; target.OnValueChangedCallback = callback; return target; } public static T AddOnValueChangedCallback(this T target, ReactionCallback callback) where T : SpriteReaction { if (callback == null) return target; target.OnValueChangedCallback += callback; return target; } public static T ClearOnValueChangedCallback(this T target) where T : SpriteReaction { target.OnValueChangedCallback = null; return target; } #endregion #region OnFrameChanged public static T SetOnFrameChangedCallback(this T target, ReactionCallback callback) where T : SpriteReaction { if (callback == null) return target; target.OnFrameChangedCallback = callback; return target; } public static T AddOnFrameChangedCallback(this T target, ReactionCallback callback) where T : SpriteReaction { if (callback == null) return target; target.OnFrameChangedCallback += callback; return target; } public static T ClearOnFrameChangedCallback(this T target) where T : SpriteReaction { target.OnFrameChangedCallback = null; return target; } #endregion } }