// Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2026 Kybernetik // using System; using System.Collections.Generic; using UnityEngine; using UnityEngine.Playables; using Object = UnityEngine.Object; namespace Animancer { /// /// An which uses the s of s /// so they can be referenced using strings as well as the clips themselves. /// /// /// /// It also has fields to automatically register animations on startup and play the first one automatically without /// needing another script to control it, much like Unity's Legacy component. /// /// Documentation: /// /// Component Types /// /// Sample: /// /// Named Character /// /// /// https://kybernetik.com.au/animancer/api/Animancer/NamedAnimancerComponent /// [AddComponentMenu(Strings.MenuPrefix + "Named Animancer Component")] [AnimancerHelpUrl(typeof(NamedAnimancerComponent))] public class NamedAnimancerComponent : AnimancerComponent { /************************************************************************************************************************/ #region Fields and Properties /************************************************************************************************************************/ /// [Internal] Field names for the custom Inspector. public const string PlayAutomaticallyField = nameof(_PlayAutomatically), NamesField = nameof(_Names), AnimationsField = nameof(_Animations); /************************************************************************************************************************/ [SerializeField, Tooltip("If true, the 'Default Animation' will be automatically played by " + nameof(OnEnable))] private bool _PlayAutomatically = true; /// [] /// If true, the first clip in the array will be automatically played by /// . /// public ref bool PlayAutomatically => ref _PlayAutomatically; /************************************************************************************************************************/ [SerializeField, Tooltip( "Optional names for the Animations." + " If not set, they will use their Animation Clip names.")] private StringAsset[] _Names; /// [] /// Optional names for the . /// If not set, they will use their . /// public StringAsset[] Names { get => _Names; set { _Names = value; Debug.Assert( !IsGraphInitialized, $"{nameof(NamedAnimancerComponent)}.{nameof(Names)}" + $" doesn't support being changed after it has already initialized." + $"\nIf any names aren't specified, they will use their Animation Clip name.", this); // This could potentially be supported by trying to look up the states based on their old names // and changing their keys, but that doesn't seem like a common use case // so it's probably not worth the effort. } } /************************************************************************************************************************/ [SerializeField, Tooltip("Animations in this array will be automatically registered by " + nameof(Awake) + " as states that can be retrieved using their name")] private AnimationClip[] _Animations; /// [] /// Animations in this array will be automatically registered by as states that can be /// retrieved using their name and the first element will be played by if /// is true. /// public AnimationClip[] Animations { get => _Animations; set { _Animations = value; States.CreateIfNew(value); } } /************************************************************************************************************************/ /// /// The first element in the array. It will be automatically played by /// if is true. /// public AnimationClip DefaultAnimation { get => _Animations.IsNullOrEmpty() ? null : _Animations[0]; set { if (_Animations.IsNullOrEmpty()) _Animations = new AnimationClip[] { value }; else _Animations[0] = value; } } /************************************************************************************************************************/ #endregion /************************************************************************************************************************/ #region Methods /************************************************************************************************************************/ #if UNITY_EDITOR /// [Editor-Only] /// Uses to ensure that all of the clips in the /// array are supported by the system and removes any others. /// /// Called in Edit Mode whenever this script is loaded or a value is changed in the Inspector. protected virtual void OnValidate() { if (_Animations == null) return; for (int i = 0; i < _Animations.Length; i++) { var clip = _Animations[i]; if (clip == null) continue; try { Validate.AssertAnimationClip(clip, true, $"add animation to {nameof(NamedAnimancerComponent)}"); continue; } catch (Exception exception) { Debug.LogException(exception, clip); } Array.Copy(_Animations, i + 1, _Animations, i, _Animations.Length - (i + 1)); Array.Resize(ref _Animations, _Animations.Length - 1); i--; } } #endif /************************************************************************************************************************/ /// Creates a state for each clip in the array. protected virtual void Awake() { if (!TryGetAnimator()) return; if (_Names == null || _Names.Length == 0) { States.CreateIfNew(_Animations); } else { var nameCount = _Names.Length; var clipCount = _Animations.Length; for (int i = 0; i < clipCount; i++) { var clip = _Animations[i]; if (clip != null) { var key = i < nameCount ? (object)(StringReference)_Names[i] : null; key ??= GetKey(clip); States.GetOrCreate(key, clip); } } } } /************************************************************************************************************************/ /// /// Plays the first clip in the array if is true. /// /// This method also ensures that the is playing. protected override void OnEnable() { if (!TryGetAnimator()) return; base.OnEnable(); if (_PlayAutomatically && !_Animations.IsNullOrEmpty()) { var clip = _Animations[0]; if (clip != null) Play(clip); } } /************************************************************************************************************************/ /// Returns the clip's name. /// /// This method is used to determine the dictionary key to use for an animation when none is specified by the /// caller, such as in . /// public override object GetKey(AnimationClip clip) => clip.name; /************************************************************************************************************************/ /// public override void GatherAnimationClips(ICollection clips) { base.GatherAnimationClips(clips); clips.Gather(_Animations); } /************************************************************************************************************************/ #endregion /************************************************************************************************************************/ } }