// Animancer // https://kybernetik.com.au/animancer // Copyright 2022 Kybernetik // using Animancer.Units; using System; using System.Collections.Generic; using UnityEngine; using Object = UnityEngine.Object; namespace Animancer { /// <inheritdoc/> /// https://kybernetik.com.au/animancer/api/Animancer/ManualMixerTransitionAsset [CreateAssetMenu(menuName = Strings.MenuPrefix + "Mixer Transition/Manual", order = Strings.AssetMenuOrder + 2)] [HelpURL(Strings.DocsURLs.APIDocumentation + "/" + nameof(ManualMixerTransitionAsset))] public class ManualMixerTransitionAsset : AnimancerTransitionAsset<ManualMixerTransition> { /// <inheritdoc/> [Serializable] public new class UnShared : UnShared<ManualMixerTransitionAsset, ManualMixerTransition, ManualMixerState>, ManualMixerState.ITransition { } } /// <inheritdoc/> /// https://kybernetik.com.au/animancer/api/Animancer/ManualMixerTransition_1 [Serializable] public abstract class ManualMixerTransition<TMixer> : AnimancerTransition<TMixer>, IMotion, IAnimationClipCollection, ICopyable<ManualMixerTransition<TMixer>> where TMixer : ManualMixerState { /************************************************************************************************************************/ [SerializeField] [Tooltip(Strings.Tooltips.OptionalSpeed)] [AnimationSpeed] [DefaultValue(1f, -1f)] private float _Speed = 1; /// <summary>[<see cref="SerializeField"/>] /// Determines how fast the mixer plays (1x = normal speed, 2x = double speed). /// </summary> public override float Speed { get => _Speed; set => _Speed = value; } /************************************************************************************************************************/ [SerializeField] [UnityEngine.Serialization.FormerlySerializedAs("_Clips")] [UnityEngine.Serialization.FormerlySerializedAs("_States")] private Object[] _Animations; /// <summary>[<see cref="SerializeField"/>] Objects that define how to create each state in the mixer.</summary> /// <remarks>See <see cref="ManualMixerState.Initialize(Object[])"/> for more information.</remarks> public ref Object[] Animations => ref _Animations; /// <summary>The name of the serialized backing field of <see cref="Animations"/>.</summary> public const string AnimationsField = nameof(_Animations); /************************************************************************************************************************/ [SerializeField] [AnimationSpeed] [DefaultValue(1f, -1f)] private float[] _Speeds; /// <summary>[<see cref="SerializeField"/>] /// The <see cref="AnimancerNode.Speed"/> to use for each state in the mixer. /// </summary> /// <remarks>If the size of this array doesn't match the <see cref="Animations"/>, it will be ignored.</remarks> public ref float[] Speeds => ref _Speeds; /// <summary>The name of the serialized backing field of <see cref="Speeds"/>.</summary> public const string SpeedsField = nameof(_Speeds); /// <summary>Are there at least enough <see cref="Speeds"/> for each of the<see cref="Animations"/>?</summary> public bool HasSpeeds => _Speeds != null && _Speeds.Length >= _Animations.Length; /************************************************************************************************************************/ [SerializeField] private bool[] _SynchronizeChildren; /// <summary>[<see cref="SerializeField"/>] /// The flags to be used in <see cref="MixerState.InitializeSynchronizedChildren"/>. /// </summary> /// <remarks>The array can be null or empty. Any elements not in the array will be treated as true.</remarks> public ref bool[] SynchronizeChildren => ref _SynchronizeChildren; /// <summary>The name of the serialized backing field of <see cref="SynchronizeChildren"/>.</summary> public const string SynchronizeChildrenField = nameof(_SynchronizeChildren); /************************************************************************************************************************/ /// <summary>[<see cref="ITransitionDetailed"/>] Are any of the <see cref="Animations"/> looping?</summary> public override bool IsLooping { get { for (int i = _Animations.Length - 1; i >= 0; i--) { if (AnimancerUtilities.TryGetIsLooping(_Animations[i], out var isLooping) && isLooping) return true; } return false; } } /// <inheritdoc/> public override float MaximumDuration { get { if (_Animations == null) return 0; var duration = 0f; var hasSpeeds = HasSpeeds; for (int i = _Animations.Length - 1; i >= 0; i--) { if (!AnimancerUtilities.TryGetLength(_Animations[i], out var length)) continue; if (hasSpeeds) length *= _Speeds[i]; if (duration < length) duration = length; } return duration; } } /// <inheritdoc/> public virtual float AverageAngularSpeed { get { if (_Animations == null) return default; var average = 0f; var hasSpeeds = HasSpeeds; var count = 0; for (int i = _Animations.Length - 1; i >= 0; i--) { if (AnimancerUtilities.TryGetAverageAngularSpeed(_Animations[i], out var speed)) { if (hasSpeeds) speed *= _Speeds[i]; average += speed; count++; } } return average / count; } } /// <inheritdoc/> public virtual Vector3 AverageVelocity { get { if (_Animations == null) return default; var average = new Vector3(); var hasSpeeds = HasSpeeds; var count = 0; for (int i = _Animations.Length - 1; i >= 0; i--) { if (AnimancerUtilities.TryGetAverageVelocity(_Animations[i], out var velocity)) { if (hasSpeeds) velocity *= _Speeds[i]; average += velocity; count++; } } return average / count; } } /************************************************************************************************************************/ /// <summary>Are all <see cref="Animations"/> assigned?</summary> public override bool IsValid { get { if (_Animations == null || _Animations.Length == 0) return false; for (int i = _Animations.Length - 1; i >= 0; i--) if (_Animations[i] == null) return false; return true; } } /************************************************************************************************************************/ /// <summary>Initializes the <see cref="AnimancerTransition{TState}.State"/> immediately after it is created.</summary> public virtual void InitializeState() { var mixer = State; var auto = MixerState.AutoSynchronizeChildren; try { MixerState.AutoSynchronizeChildren = false; mixer.Initialize(_Animations); } finally { MixerState.AutoSynchronizeChildren = auto; } mixer.InitializeSynchronizedChildren(_SynchronizeChildren); if (_Speeds != null) { #if UNITY_ASSERTIONS if (_Speeds.Length != 0 && _Speeds.Length != _Animations.Length) Debug.LogError( $"The number of serialized {nameof(Speeds)} ({_Speeds.Length})" + $" does not match the number of {nameof(Animations)} ({_Animations.Length}).", mixer.Root?.Component as Object); #endif var children = mixer.ChildStates; var count = Math.Min(children.Count, _Speeds.Length); while (--count >= 0) children[count].Speed = _Speeds[count]; } } /************************************************************************************************************************/ /// <inheritdoc/> public override void Apply(AnimancerState state) { base.Apply(state); if (!float.IsNaN(_Speed)) state.Speed = _Speed; for (int i = 0; i < _Animations.Length; i++) if (_Animations[i] is ITransition transition) transition.Apply(state.GetChild(i)); } /************************************************************************************************************************/ /// <summary>Adds the <see cref="Animations"/> to the collection.</summary> void IAnimationClipCollection.GatherAnimationClips(ICollection<AnimationClip> clips) => clips.GatherFromSource(_Animations); /************************************************************************************************************************/ /// <inheritdoc/> public virtual void CopyFrom(ManualMixerTransition<TMixer> copyFrom) { CopyFrom((AnimancerTransition<TMixer>)copyFrom); if (copyFrom == null) { _Speed = 1; _Animations = default; _Speeds = default; _SynchronizeChildren = default; return; } _Speed = copyFrom._Speed; AnimancerUtilities.CopyExactArray(copyFrom._Animations, ref _Animations); AnimancerUtilities.CopyExactArray(copyFrom._Speeds, ref _Speeds); AnimancerUtilities.CopyExactArray(copyFrom._SynchronizeChildren, ref _SynchronizeChildren); } /************************************************************************************************************************/ } }