// Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2024 Kybernetik // using System; using System.Collections.Generic; using UnityEngine; namespace Animancer { /// A set of up/right/down/left animations. /// /// Documentation: /// /// Directional Animation Sets /// /// https://kybernetik.com.au/animancer/api/Animancer/DirectionalAnimationSet /// [CreateAssetMenu( menuName = Strings.MenuPrefix + "Directional Animation Set/4 Directions", order = Strings.AssetMenuOrder + 3)] [AnimancerHelpUrl(typeof(DirectionalAnimationSet))] public class DirectionalAnimationSet : ScriptableObject, IAnimationClipSource { /************************************************************************************************************************/ [SerializeField] private AnimationClip _Up; /// [] The animation facing up (0, 1). /// was not called before setting this value. public AnimationClip Up { get => _Up; set { AssertCanSetClips(); _Up = value; AnimancerUtilities.SetDirty(this); } } /************************************************************************************************************************/ [SerializeField] private AnimationClip _Right; /// [] The animation facing right (1, 0). /// was not called before setting this value. public AnimationClip Right { get => _Right; set { AssertCanSetClips(); _Right = value; AnimancerUtilities.SetDirty(this); } } /************************************************************************************************************************/ [SerializeField] private AnimationClip _Down; /// [] The animation facing down (0, -1). /// was not called before setting this value. public AnimationClip Down { get => _Down; set { AssertCanSetClips(); _Down = value; AnimancerUtilities.SetDirty(this); } } /************************************************************************************************************************/ [SerializeField] private AnimationClip _Left; /// [] The animation facing left (-1, 0). /// was not called before setting this value. public AnimationClip Left { get => _Left; set { AssertCanSetClips(); _Left = value; AnimancerUtilities.SetDirty(this); } } /************************************************************************************************************************/ #if UNITY_ASSERTIONS private bool _AllowSetClips; #endif /// [Assert-Only] /// Determines whether the properties are allowed to be set. /// [System.Diagnostics.Conditional(Strings.Assertions)] public void AllowSetClips(bool allow = true) { #if UNITY_ASSERTIONS _AllowSetClips = allow; #endif } /// [Assert-Only] /// Throws an if wasn't called. /// [System.Diagnostics.Conditional(Strings.Assertions)] public void AssertCanSetClips() { #if UNITY_ASSERTIONS AnimancerUtilities.Assert(_AllowSetClips, $"{nameof(AllowSetClips)}() must be called before attempting to set any of" + $" the animations in a {nameof(DirectionalAnimationSet)}" + $" to ensure that they are not changed accidentally."); #endif } /************************************************************************************************************************/ /// Returns the animation closest to the specified `direction`. public virtual AnimationClip GetClip(Vector2 direction) { if (direction.x >= 0) { if (direction.y >= 0) return direction.x > direction.y ? _Right : _Up; else return direction.x > -direction.y ? _Right : _Down; } else { if (direction.y >= 0) return direction.x < -direction.y ? _Left : _Up; else return direction.x < direction.y ? _Left : _Down; } } /************************************************************************************************************************/ #region Directions /************************************************************************************************************************/ /// The number of animations in this set. public virtual int ClipCount => 4; /************************************************************************************************************************/ /// Up, Right, Down, or Left. /// /// Documentation: /// /// Directional Animation Sets /// /// https://kybernetik.com.au/animancer/api/Animancer/Direction /// public enum Direction { /// . Up, /// . Right, /// . Down, /// . Left, } /************************************************************************************************************************/ /// Returns the name of the specified `direction`. protected virtual string GetDirectionName(int direction) => ((Direction)direction).ToString(); /************************************************************************************************************************/ /// Returns the animation associated with the specified `direction`. public AnimationClip GetClip(Direction direction) => direction switch { Direction.Up => _Up, Direction.Right => _Right, Direction.Down => _Down, Direction.Left => _Left, _ => throw AnimancerUtilities.CreateUnsupportedArgumentException(direction), }; /// Returns the animation associated with the specified `direction`. public virtual AnimationClip GetClip(int direction) => GetClip((Direction)direction); /************************************************************************************************************************/ /// Sets the animation associated with the specified `direction`. public void SetClip(Direction direction, AnimationClip clip) { switch (direction) { case Direction.Up: Up = clip; break; case Direction.Right: Right = clip; break; case Direction.Down: Down = clip; break; case Direction.Left: Left = clip; break; default: throw AnimancerUtilities.CreateUnsupportedArgumentException(direction); } } /// Sets the animation associated with the specified `direction`. public virtual void SetClip(int direction, AnimationClip clip) => SetClip((Direction)direction, clip); /************************************************************************************************************************/ /// [Editor-Only] /// Attempts to assign the `clip` to one of this set's fields based on its name and /// returns the direction index of that field (or -1 if it was unable to determine the direction). /// public virtual int SetClipByName(AnimationClip clip) { var name = clip.name; int bestDirection = -1; int bestDirectionIndex = -1; var directionCount = ClipCount; for (int i = 0; i < directionCount; i++) { var index = name.LastIndexOf(GetDirectionName(i)); if (bestDirectionIndex < index) { bestDirectionIndex = index; bestDirection = i; } } if (bestDirection >= 0) SetClip(bestDirection, clip); return bestDirection; } /************************************************************************************************************************/ #region Conversion /************************************************************************************************************************/ /// Returns a vector representing the specified `direction`. public static Vector2 DirectionToVector(Direction direction) => direction switch { Direction.Up => Vector2.up, Direction.Right => Vector2.right, Direction.Down => Vector2.down, Direction.Left => Vector2.left, _ => throw AnimancerUtilities.CreateUnsupportedArgumentException(direction), }; /// Returns a vector representing the specified `direction`. public virtual Vector2 GetDirection(int direction) => DirectionToVector((Direction)direction); /************************************************************************************************************************/ /// Returns the direction closest to the specified `vector`. public static Direction VectorToDirection(Vector2 vector) { if (vector.x >= 0) { if (vector.y >= 0) return vector.x > vector.y ? Direction.Right : Direction.Up; else return vector.x > -vector.y ? Direction.Right : Direction.Down; } else { if (vector.y >= 0) return vector.x < -vector.y ? Direction.Left : Direction.Up; else return vector.x < vector.y ? Direction.Left : Direction.Down; } } /************************************************************************************************************************/ /// Returns a copy of the `vector` pointing in the closest direction this set type has an animation for. public static Vector2 SnapVectorToDirection(Vector2 vector) { var magnitude = vector.magnitude; var direction = VectorToDirection(vector); vector = DirectionToVector(direction) * magnitude; return vector; } /// Returns a copy of the `vector` pointing in the closest direction this set has an animation for. public virtual Vector2 Snap(Vector2 vector) => SnapVectorToDirection(vector); /************************************************************************************************************************/ #endregion /************************************************************************************************************************/ #region Collections /************************************************************************************************************************/ /// Adds all animations from this set to the `clips`, starting from the specified `index`. public void AddClips(AnimationClip[] clips, int index) { var count = ClipCount; for (int i = 0; i < count; i++) clips[index + i] = GetClip(i); } /// [] Adds all animations from this set to the `clips`. public void GetAnimationClips(List clips) { var count = ClipCount; for (int i = 0; i < count; i++) clips.Add(GetClip(i)); } /************************************************************************************************************************/ /// /// Adds unit vectors corresponding to each of the animations in this set to the `directions`, starting from /// the specified `index`. /// public void AddDirections(Vector2[] directions, int index) { var count = ClipCount; for (int i = 0; i < count; i++) directions[index + i] = GetDirection(i); } /************************************************************************************************************************/ /// Calls and . public void AddClipsAndDirections(AnimationClip[] clips, Vector2[] directions, int index) { AddClips(clips, index); AddDirections(directions, index); } /************************************************************************************************************************/ #endregion /************************************************************************************************************************/ #endregion /************************************************************************************************************************/ } }