123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469 |
- // Animancer // https://kybernetik.com.au/animancer // Copyright 2022 Kybernetik //
- using System;
- using System.Collections.Generic;
- using System.IO;
- using UnityEngine;
- using Object = UnityEngine.Object;
- namespace Animancer
- {
- /// <summary>A set of up/right/down/left animations.</summary>
- /// <remarks>
- /// Documentation: <see href="https://kybernetik.com.au/animancer/docs/manual/playing/directional-sets">Directional Animation Sets</see>
- /// </remarks>
- /// https://kybernetik.com.au/animancer/api/Animancer/DirectionalAnimationSet
- ///
- [CreateAssetMenu(menuName = Strings.MenuPrefix + "Directional Animation Set/4 Directions", order = Strings.AssetMenuOrder + 10)]
- [HelpURL(Strings.DocsURLs.APIDocumentation + "/" + nameof(DirectionalAnimationSet))]
- public class DirectionalAnimationSet : ScriptableObject, IAnimationClipSource
- {
- /************************************************************************************************************************/
- [SerializeField]
- private AnimationClip _Up;
- /// <summary>[<see cref="SerializeField"/>] The animation facing up (0, 1).</summary>
- /// <exception cref="ArgumentException"><see cref="AllowSetClips"/> was not called before setting this value.</exception>
- public AnimationClip Up
- {
- get => _Up;
- set
- {
- AssertCanSetClips();
- _Up = value;
- AnimancerUtilities.SetDirty(this);
- }
- }
- /************************************************************************************************************************/
- [SerializeField]
- private AnimationClip _Right;
- /// <summary>[<see cref="SerializeField"/>] The animation facing right (1, 0).</summary>
- /// <exception cref="ArgumentException"><see cref="AllowSetClips"/> was not called before setting this value.</exception>
- public AnimationClip Right
- {
- get => _Right;
- set
- {
- AssertCanSetClips();
- _Right = value;
- AnimancerUtilities.SetDirty(this);
- }
- }
- /************************************************************************************************************************/
- [SerializeField]
- private AnimationClip _Down;
- /// <summary>[<see cref="SerializeField"/>] The animation facing down (0, -1).</summary>
- /// <exception cref="ArgumentException"><see cref="AllowSetClips"/> was not called before setting this value.</exception>
- public AnimationClip Down
- {
- get => _Down;
- set
- {
- AssertCanSetClips();
- _Down = value;
- AnimancerUtilities.SetDirty(this);
- }
- }
- /************************************************************************************************************************/
- [SerializeField]
- private AnimationClip _Left;
- /// <summary>[<see cref="SerializeField"/>] The animation facing left (-1, 0).</summary>
- /// <exception cref="ArgumentException"><see cref="AllowSetClips"/> was not called before setting this value.</exception>
- public AnimationClip Left
- {
- get => _Left;
- set
- {
- AssertCanSetClips();
- _Left = value;
- AnimancerUtilities.SetDirty(this);
- }
- }
- /************************************************************************************************************************/
- #if UNITY_ASSERTIONS
- private bool _AllowSetClips;
- #endif
- /// <summary>[Assert-Only] Determines whether the <see cref="AnimationClip"/> properties are allowed to be set.</summary>
- [System.Diagnostics.Conditional(Strings.Assertions)]
- public void AllowSetClips(bool allow = true)
- {
- #if UNITY_ASSERTIONS
- _AllowSetClips = allow;
- #endif
- }
- /// <summary>[Assert-Only] Throws an <see cref="ArgumentException"/> if <see cref="AllowSetClips"/> was not called.</summary>
- [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
- }
- /************************************************************************************************************************/
- /// <summary>Returns the animation closest to the specified `direction`.</summary>
- 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
- /************************************************************************************************************************/
- /// <summary>The number of animations in this set.</summary>
- public virtual int ClipCount => 4;
- /************************************************************************************************************************/
- /// <summary>Up, Right, Down, or Left.</summary>
- /// <remarks>
- /// Documentation: <see href="https://kybernetik.com.au/animancer/docs/manual/playing/directional-sets">Directional Animation Sets</see>
- /// </remarks>
- /// https://kybernetik.com.au/animancer/api/Animancer/Direction
- ///
- public enum Direction
- {
- /// <summary><see cref="Vector2.up"/>.</summary>
- Up,
- /// <summary><see cref="Vector2.right"/>.</summary>
- Right,
- /// <summary><see cref="Vector2.down"/>.</summary>
- Down,
- /// <summary><see cref="Vector2.left"/>.</summary>
- Left,
- }
- /************************************************************************************************************************/
- /// <summary>Returns the name of the specified `direction`.</summary>
- protected virtual string GetDirectionName(int direction) => ((Direction)direction).ToString();
- /************************************************************************************************************************/
- /// <summary>Returns the animation associated with the specified `direction`.</summary>
- public AnimationClip GetClip(Direction direction)
- {
- switch (direction)
- {
- case Direction.Up: return _Up;
- case Direction.Right: return _Right;
- case Direction.Down: return _Down;
- case Direction.Left: return _Left;
- default: throw AnimancerUtilities.CreateUnsupportedArgumentException(direction);
- }
- }
- /// <summary>Returns the animation associated with the specified `direction`.</summary>
- public virtual AnimationClip GetClip(int direction) => GetClip((Direction)direction);
- /************************************************************************************************************************/
- /// <summary>Sets the animation associated with the specified `direction`.</summary>
- 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);
- }
- }
- /// <summary>Sets the animation associated with the specified `direction`.</summary>
- public virtual void SetClip(int direction, AnimationClip clip) => SetClip((Direction)direction, clip);
- /************************************************************************************************************************/
- #region Conversion
- /************************************************************************************************************************/
- /// <summary>Returns a vector representing the specified `direction`.</summary>
- public static Vector2 DirectionToVector(Direction direction)
- {
- switch (direction)
- {
- case Direction.Up: return Vector2.up;
- case Direction.Right: return Vector2.right;
- case Direction.Down: return Vector2.down;
- case Direction.Left: return Vector2.left;
- default: throw AnimancerUtilities.CreateUnsupportedArgumentException(direction);
- }
- }
- /// <summary>Returns a vector representing the specified `direction`.</summary>
- public virtual Vector2 GetDirection(int direction) => DirectionToVector((Direction)direction);
- /************************************************************************************************************************/
- /// <summary>Returns the direction closest to the specified `vector`.</summary>
- 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;
- }
- }
- /************************************************************************************************************************/
- /// <summary>Returns a copy of the `vector` pointing in the closest direction this set type has an animation for.</summary>
- public static Vector2 SnapVectorToDirection(Vector2 vector)
- {
- var magnitude = vector.magnitude;
- var direction = VectorToDirection(vector);
- vector = DirectionToVector(direction) * magnitude;
- return vector;
- }
- /// <summary>Returns a copy of the `vector` pointing in the closest direction this set has an animation for.</summary>
- public virtual Vector2 Snap(Vector2 vector) => SnapVectorToDirection(vector);
- /************************************************************************************************************************/
- #endregion
- /************************************************************************************************************************/
- #region Collections
- /************************************************************************************************************************/
- /// <summary>Adds all animations from this set to the `clips`, starting from the specified `index`.</summary>
- public void AddClips(AnimationClip[] clips, int index)
- {
- var count = ClipCount;
- for (int i = 0; i < count; i++)
- clips[index + i] = GetClip(i);
- }
- /// <summary>[<see cref="IAnimationClipSource"/>] Adds all animations from this set to the `clips`.</summary>
- public void GetAnimationClips(List<AnimationClip> clips)
- {
- var count = ClipCount;
- for (int i = 0; i < count; i++)
- clips.Add(GetClip(i));
- }
- /************************************************************************************************************************/
- /// <summary>
- /// Adds unit vectors corresponding to each of the animations in this set to the `directions`, starting from
- /// the specified `index`.
- /// </summary>
- public void AddDirections(Vector2[] directions, int index)
- {
- var count = ClipCount;
- for (int i = 0; i < count; i++)
- directions[index + i] = GetDirection(i);
- }
- /************************************************************************************************************************/
- /// <summary>Calls <see cref="AddClips"/> and <see cref="AddDirections"/>.</summary>
- public void AddClipsAndDirections(AnimationClip[] clips, Vector2[] directions, int index)
- {
- AddClips(clips, index);
- AddDirections(directions, index);
- }
- /************************************************************************************************************************/
- #endregion
- /************************************************************************************************************************/
- #endregion
- /************************************************************************************************************************/
- #region Editor Functions
- /************************************************************************************************************************/
- #if UNITY_EDITOR
- /************************************************************************************************************************/
- [UnityEditor.CustomEditor(typeof(DirectionalAnimationSet), true), UnityEditor.CanEditMultipleObjects]
- private class Editor : Animancer.Editor.ScriptableObjectEditor { }
- /************************************************************************************************************************/
- /// <summary>[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).
- /// </summary>
- 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;
- }
- /************************************************************************************************************************/
- [UnityEditor.MenuItem("CONTEXT/" + nameof(DirectionalAnimationSet) + "/Find Animations")]
- private static void FindSimilarAnimations(UnityEditor.MenuCommand command)
- {
- var set = (DirectionalAnimationSet)command.context;
- UnityEditor.Undo.RecordObject(set, "Find Animations");
- var directory = UnityEditor.AssetDatabase.GetAssetPath(set);
- directory = Path.GetDirectoryName(directory);
- var guids = UnityEditor.AssetDatabase.FindAssets(
- $"{set.name} t:{nameof(AnimationClip)}",
- new string[] { directory });
- for (int i = 0; i < guids.Length; i++)
- {
- var path = UnityEditor.AssetDatabase.GUIDToAssetPath(guids[i]);
- var clip = UnityEditor.AssetDatabase.LoadAssetAtPath<AnimationClip>(path);
- if (clip == null)
- continue;
- set.SetClipByName(clip);
- }
- }
- /************************************************************************************************************************/
- [UnityEditor.MenuItem(Strings.CreateMenuPrefix + "Directional Animation Set/From Selection",
- priority = Strings.AssetMenuOrder + 12)]
- private static void CreateDirectionalAnimationSet()
- {
- var nameToAnimations = new Dictionary<string, List<AnimationClip>>();
- var selection = UnityEditor.Selection.objects;
- for (int i = 0; i < selection.Length; i++)
- {
- var clip = selection[i] as AnimationClip;
- if (clip == null)
- continue;
- var name = clip.name;
- for (Direction direction = 0; direction < (Direction)4; direction++)
- {
- name = name.Replace(direction.ToString(), "");
- }
- if (!nameToAnimations.TryGetValue(name, out var clips))
- {
- clips = new List<AnimationClip>();
- nameToAnimations.Add(name, clips);
- }
- clips.Add(clip);
- }
- if (nameToAnimations.Count == 0)
- throw new InvalidOperationException("No clips are selected");
- var sets = new List<Object>();
- foreach (var nameAndAnimations in nameToAnimations)
- {
- var set = nameAndAnimations.Value.Count <= 4 ?
- CreateInstance<DirectionalAnimationSet>() :
- CreateInstance<DirectionalAnimationSet8>();
- set.AllowSetClips();
- for (int i = 0; i < nameAndAnimations.Value.Count; i++)
- {
- set.SetClipByName(nameAndAnimations.Value[i]);
- }
- var path = UnityEditor.AssetDatabase.GetAssetPath(nameAndAnimations.Value[0]);
- path = $"{Path.GetDirectoryName(path)}/{nameAndAnimations.Key}.asset";
- UnityEditor.AssetDatabase.CreateAsset(set, path);
- sets.Add(set);
- }
- UnityEditor.Selection.objects = sets.ToArray();
- }
- /************************************************************************************************************************/
- [UnityEditor.MenuItem("CONTEXT/" + nameof(DirectionalAnimationSet) + "/Toggle Looping")]
- private static void ToggleLooping(UnityEditor.MenuCommand command)
- {
- var set = (DirectionalAnimationSet)command.context;
- var count = set.ClipCount;
- for (int i = 0; i < count; i++)
- {
- var clip = set.GetClip(i);
- if (clip == null)
- continue;
- var isLooping = !clip.isLooping;
- for (i = 0; i < count; i++)
- {
- clip = set.GetClip(i);
- if (clip == null)
- continue;
- Animancer.Editor.AnimancerEditorUtilities.SetLooping(clip, isLooping);
- }
- break;
- }
- }
- /************************************************************************************************************************/
- #endif
- /************************************************************************************************************************/
- #endregion
- /************************************************************************************************************************/
- }
- }
|