// Animancer // Copyright 2020 Kybernetik // using System; using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.Playables; using Object = UnityEngine.Object; namespace Animancer { /// <summary> /// An <see cref="AnimancerComponent"/> which uses the <see cref="Object.name"/>s of <see cref="AnimationClip"/>s /// so they can be referenced using strings as well as the clips themselves. /// <para></para> /// 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 <see cref="Animation"/> component. /// </summary> [AddComponentMenu(Strings.MenuPrefix + "Named Animancer Component")] [HelpURL(Strings.APIDocumentationURL + "/NamedAnimancerComponent")] public class NamedAnimancerComponent : AnimancerComponent { /************************************************************************************************************************/ #region Fields and Properties /************************************************************************************************************************/ [SerializeField, Tooltip("If true, the 'Default Animation' will be automatically played by OnEnable")] private bool _PlayAutomatically = true; /// <summary>[<see cref="SerializeField"/>] /// If true, the first clip in the <see cref="Animations"/> array will be automatically played by /// <see cref="OnEnable"/>. /// </summary> public bool PlayAutomatically { get { return _PlayAutomatically; } set { _PlayAutomatically = value; } } /************************************************************************************************************************/ [SerializeField, Tooltip("Animations in this array will be automatically registered by Awake" + " as states that can be retrieved using their name")] private AnimationClip[] _Animations; /// <summary>[<see cref="SerializeField"/>] /// Animations in this array will be automatically registered by <see cref="Awake"/> as states that can be /// retrieved using their name and the first element will be played by <see cref="OnEnable"/> if /// <see cref="PlayAutomatically"/> is true. /// </summary> public AnimationClip[] Animations { get { return _Animations; } set { _Animations = value; if (value != null && value.Length > 0) States.CreateIfNew(value); } } /************************************************************************************************************************/ /// <summary> /// The first element in the <see cref="Animations"/> array. It will be automatically played by /// <see cref="OnEnable"/> if <see cref="PlayAutomatically"/> is true. /// </summary> public AnimationClip DefaultAnimation { get { if (_Animations == null || _Animations.Length == 0) return null; else return _Animations[0]; } set { if (_Animations == null || _Animations.Length == 0) _Animations = new AnimationClip[] { value }; else _Animations[0] = value; } } /************************************************************************************************************************/ #endregion /************************************************************************************************************************/ #region Initialisation /************************************************************************************************************************/ #if UNITY_EDITOR /// <summary>[Editor-Only] /// Called by the Unity Editor in Edit Mode whenever an instance of this script is loaded or a value is changed /// in the Inspector. /// <para></para> /// Uses <see cref="ClipState.ValidateClip"/> to ensure that all of the clips in the <see cref="Animations"/> /// array are supported by the <see cref="Animancer"/> system and removes any others. /// </summary> 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.NotLegacy(clip); continue; } catch (Exception ex) { Debug.LogException(ex, clip); } Array.Copy(_Animations, i + 1, _Animations, i, _Animations.Length - (i + 1)); Array.Resize(ref _Animations, _Animations.Length - 1); i--; } } #endif /************************************************************************************************************************/ /// <summary> /// Called by Unity when this component is being loaded. /// <para></para> /// Creates a state for each clip in the <see cref="Animations"/> array. /// </summary> protected virtual void Awake() { if (_Animations != null && _Animations.Length > 0) States.CreateIfNew(_Animations); } /************************************************************************************************************************/ /// <summary> /// Called by Unity when this component becomes enabled and active. /// <para></para> /// Plays the first clip in the <see cref="Animations"/> array if <see cref="PlayAutomatically"/> is true. /// <para></para> /// Plays the <see cref="PlayableGraph"/> if it was stopped. /// </summary> protected override void OnEnable() { base.OnEnable(); if (_PlayAutomatically && _Animations != null && _Animations.Length > 0) { var clip = _Animations[0]; if (clip != null) Play(clip); } } /************************************************************************************************************************/ /// <summary>[<see cref="IAnimationClipSource"/>] /// Gathers all the animations in the <see cref="Playable"/> and the <see cref="Animations"/> array. /// </summary> public override void GatherAnimationClips(ICollection<AnimationClip> clips) { base.GatherAnimationClips(clips); clips.Gather(_Animations); } /************************************************************************************************************************/ #endregion /************************************************************************************************************************/ #region Play Management /************************************************************************************************************************/ /// <summary> /// 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 user, such as in <see cref="AnimancerComponent.Play(AnimationClip)"/>. /// </summary> public override object GetKey(AnimationClip clip) { return clip.name; } /************************************************************************************************************************/ /// <summary>[Coroutine] /// Plays each clip in the <see cref="Animations"/> array one after the other. Mainly useful for testing and /// showcasing purposes. /// </summary> public IEnumerator PlayAnimationsInSequence() { for (int i = 0; i < _Animations.Length; i++) { var state = Play(_Animations[i]); if (state != null) yield return state; } Stop(); } /************************************************************************************************************************/ /// <summary>[Pro-Only] [Coroutine] /// Cross fades between each clip in the <see cref="Animations"/> array one after the other. Mainly useful for /// testing and showcasing purposes. /// </summary> public IEnumerator CrossFadeAnimationsInSequence(float fadeDuration = AnimancerPlayable.DefaultFadeDuration) { for (int i = 0; i < _Animations.Length; i++) { var state = Play(_Animations[i], fadeDuration); if (state != null) { state.Time = 0; yield return state; } } Stop(); } /************************************************************************************************************************/ #endregion /************************************************************************************************************************/ } }