namespace Fusion.Addons.AnimationController { using System; using System.Collections.Generic; using Unity.Profiling; using UnityEngine; using UnityEngine.Animations; using UnityEngine.Playables; public interface IAnimationStateOwner { AnimationMixerPlayable Mixer { get; } bool IsActive(bool self); bool IsPlaying(bool self); bool IsFadingIn(bool self); bool IsFadingOut(bool self); void Activate(AnimationState source, float fadeDuration); void Deactivate(AnimationState source, float fadeDuration); } /// /// Animation state component. /// public abstract unsafe partial class AnimationState : MonoBehaviour, IAnimationWeightProvider, IAnimationFadingProvider { // PUBLIC MEMBERS /// /// Reference to animation controller. /// public AnimationController Controller => _controller; /// /// List of animation states (direct children). /// public IList States => _states; /// /// Input port used to identify connection to owner (layer/state) mixer. /// public int Port => _port; /// /// Weight of the state. /// public float Weight { get { return _weight; } protected set { _weight = value; } } /// /// Fading speed of the state. Represents transition and controls calculation of Weight over time. /// public float FadingSpeed { get { return _fadingSpeed; } protected set { _fadingSpeed = value; } } /// /// Interpolated weight used in render update. /// public float InterpolatedWeight { get { return _interpolatedWeight; } protected set { _interpolatedWeight = value; } } /// /// Interpolated fading speed used in render update. /// public float InterpolatedFadingSpeed { get { return _interpolatedFadingSpeed; } protected set { _interpolatedFadingSpeed = value; } } // PROTECTED MEMBERS /// /// Reference to state owner (layer/state). /// protected IAnimationStateOwner Owner => _owner; // PRIVATE MEMBERS private AnimationController _controller; private AnimationState[] _states; private IAnimationStateOwner _owner; private int _port; private float _weight; private float _fadingSpeed; private float _cachedWeight; private float _playableWeight; private float _interpolatedWeight; private float _interpolatedFadingSpeed; private ProfilerMarker _profilerMarker; // PUBLIC METHODS /// /// Explicitly set weight of the state. /// Valid range is 0.0f - 1.0f. /// public void SetWeight(float weight) { _weight = Mathf.Clamp01(weight); } /// /// Returns true if the state is fading in or if the Weight is greater than zero and the state is not fading out. /// There is only one state active at the same time. /// If true only this state is considered, otherwise whole owner hierarchy must be active. /// public bool IsActive(bool self = false) { return ((_fadingSpeed == 0.0f && _weight > 0.0f) || _fadingSpeed > 0.0f) && (self == true || _owner.IsActive(false) == true); } /// /// Returns true if the state is fading in or if the Weight is greater than zero. /// There might be more states playing at the same time (while transitioning from one state to another). /// If true only this state is considered, otherwise whole owner hierarchy must be playing. /// public bool IsPlaying(bool self = false) { return (_fadingSpeed > 0.0f || _weight > 0.0f) && (self == true || _owner.IsPlaying(false) == true); } /// /// Returns true if the state is fading in (fading speed greater than zero). /// If true only this state is considered, otherwise whole owner hierarchy must be playing and not fading out. /// public bool IsFadingIn(bool self = false) { return _fadingSpeed > 0.0f && (self == true || (_owner.IsPlaying(false) == true && _owner.IsFadingOut(false) == false)); } /// /// Returns true if the state is fading out (fading speed lower than zero). /// If true only this state is considered, otherwise whole owner hierarchy must be playing and not fading in. /// public bool IsFadingOut(bool self = false) { return _fadingSpeed < 0.0f && (self == true || (_owner.IsPlaying(false) == true && _owner.IsFadingIn(false) == false)); } /// /// Activate the state. /// How long it takes to reach full Weight from zero. /// If true only this state is activated, otherwise also whole owner hierarchy (except layer) is activated and other states on same level deactivated. /// public void Activate(float fadeDuration, bool self = false) { if ((_fadingSpeed == 0.0f && _weight >= 1.0f) || _fadingSpeed > 0.0f) return; if (fadeDuration <= 0.0f) { _weight = 1.0f; _fadingSpeed = 0.0f; } else { _fadingSpeed = 1.0f / fadeDuration; } AnimationProfiler.Log(Controller, $"{nameof(AnimationState)}.{nameof(Activate)} ({name}), Fade Duration: {fadeDuration:F3}, Self: {self}", gameObject); OnActivate(); if (self == false) { _owner.Activate(this, fadeDuration); } } /// /// Deactivate the state. /// How long it takes to reach zero Weight from full. /// If true only this state is deactivated, otherwise also whole owner hierarchy (except layer) is deactivated. /// public void Deactivate(float fadeDuration, bool self = false) { if ((_fadingSpeed == 0.0f && _weight <= 0.0f) || _fadingSpeed < 0.0f) return; if (fadeDuration <= 0.0f) { _weight = 0.0f; _fadingSpeed = 0.0f; } else { _fadingSpeed = 1.0f / -fadeDuration; } AnimationProfiler.Log(Controller, $"{nameof(AnimationState)}.{nameof(Deactivate)} ({name}), Fade Duration: {fadeDuration:F3}, Self: {self}", gameObject); OnDeactivate(); if (self == false) { _owner.Deactivate(this, fadeDuration); } } /// /// Returns true if the state has an active sub-state. This call is non-recursive. /// public bool HasActiveState() { AnimationState[] states = _states; for (int i = 0, count = states.Length; i < count; ++i) { AnimationState state = states[i]; if (state.IsActive(true) == true) return true; } return false; } /// /// Returns true if the state has an active sub-state of type T. This call is non-recursive. /// public bool HasActiveState() where T : class { AnimationState[] states = _states; for (int i = 0, count = states.Length; i < count; ++i) { AnimationState state = states[i]; if (state.IsActive(true) == true && state is T stateAsT) return true; } return false; } /// /// Returns active sub-state. This call is non-recursive. /// public AnimationState GetActiveState() { AnimationState[] states = _states; for (int i = 0, count = states.Length; i < count; ++i) { AnimationState state = states[i]; if (state.IsActive(true) == true) return state; } return null; } /// /// Returns active sub-state of type T. This call is non-recursive. /// public bool GetActiveState(out T activeState) where T : class { AnimationState[] states = _states; for (int i = 0, count = states.Length; i < count; ++i) { AnimationState state = states[i]; if (state.IsActive(true) == true && state is T stateAsT) { activeState = stateAsT; return true; } } activeState = default; return false; } /// /// Deactivate all states within this state. /// How long it takes to reach zero Weight from full. /// If true all sub-states will be deactivated as well. /// public void DeactivateAllStates(float fadeDuration, bool recursive) { AnimationState[] states = _states; for (int i = 0, count = states.Length; i < count; ++i) { AnimationState state = states[i]; if (recursive == true) { state.DeactivateAllStates(fadeDuration, true); } state.Deactivate(fadeDuration, true); } } /// /// Reset to default state. Calls SetDefaults() on sub-states. /// public void SetDefaults() { _weight = 0.0f; _fadingSpeed = 0.0f; for (int i = 0, count = _states.Length; i < count; ++i) { _states[i].SetDefaults(); } OnSetDefaults(); } /// /// Called when Animation Controller is initialized. /// public void Initialize(AnimationController controller, IAnimationStateOwner owner) { if (owner == null) throw new ArgumentNullException(nameof(owner)); _controller = controller; _owner = owner; #if ANIMATIONS_PROFILE _profilerMarker = new ProfilerMarker(GetType().Name); #endif InitializeStates(); OnInitialize(); } /// /// Called when Animation Controller is deinitialized. /// public void Deinitialize() { OnDeinitialize(); DeinitializeStates(); _owner = default; _controller = default; } /// /// Called when Animation Controller is spawned. /// public void Spawned() { _port = -1; _weight = 0.0f; _fadingSpeed = 0.0f; _cachedWeight = 0.0f; _playableWeight = 0.0f; _interpolatedWeight = 0.0f; _interpolatedFadingSpeed = 0.0f; CreatePlayable(); if (_port < 0) throw new NotSupportedException(); AnimationState[] states = _states; for (int i = 0, count = states.Length; i < count; ++i) { AnimationState state = states[i]; state.Spawned(); } OnSpawned(); } /// /// Called when Animation Controller is despawned. /// public void Despawned() { OnDespawned(); AnimationState[] states = _states; for (int i = 0, count = states.Length; i < count; ++i) { AnimationState state = states[i]; if (state != null) { state.Despawned(); } } } /// /// Manual fixed update execution, called internally by Animation Controller. /// public void ManualFixedUpdate() { if (_fadingSpeed <= 0.0f && _weight <= 0.0f) { SetDefaults(); return; } #if ANIMATIONS_PROFILE _profilerMarker.Begin(); #endif AnimationState[] states = _states; for (int i = 0, count = states.Length; i < count; ++i) { AnimationState state = states[i]; state.ManualFixedUpdate(); } if (_fadingSpeed != 0.0f) { _weight += _fadingSpeed * _controller.DeltaTime; if (_weight <= 0.0f) { _weight = 0.0f; _fadingSpeed = 0.0f; } else if (_weight >= 1.0f) { _weight = 1.0f; _fadingSpeed = 0.0f; } } OnFixedUpdate(); #if ANIMATIONS_PROFILE _profilerMarker.End(); #endif } /// /// Manual interpolation, called internally by Animation Controller. /// public void Interpolate() { if (_interpolatedWeight <= 0.0f) return; #if ANIMATIONS_PROFILE _profilerMarker.Begin(); #endif AnimationState[] states = _states; for (int i = 0, count = states.Length; i < count; ++i) { AnimationState state = states[i]; state.Interpolate(); } OnInterpolate(); #if ANIMATIONS_PROFILE _profilerMarker.End(); #endif } /// /// Returns input weight used in playable graph. Used internally by owner layers and states. /// public float GetPlayableInputWeight() { return _owner.Mixer.GetInputWeight(_port); } /// /// Explicitly set weight to playable graph. Used to set specific values. /// Playable weight (absolute value). /// public void SetPlayableInputWeight(float weight) { _playableWeight = weight; if (weight == _cachedWeight) return; _cachedWeight = weight; _owner.Mixer.SetInputWeight(_port, weight); } /// /// Applies pre-calculated playable weight to playable graph. /// public void SetPlayableInputWeight() { float weight = _playableWeight; if (weight == _cachedWeight) return; _cachedWeight = weight; _owner.Mixer.SetInputWeight(_port, weight); } /// /// Calculates weight for playable graph. Output requires normalization, final graph input weights can be different than weights stores in layers/states. /// If true interpolated properties will be used. /// Maximum children weight, used to calculate maximum playable weight of the parent. /// public float CalculatePlayableWeights(bool interpolated, out float maxWeight) { float stateWeight = interpolated == true ? _interpolatedWeight : _weight; if (stateWeight <= 0.0f) { _playableWeight = 0.0f; maxWeight = 0.0f; return 0.0f; } AnimationState[] states = _states; int stateCount = states.Length; if (stateCount == 0) { _playableWeight = stateWeight; maxWeight = stateWeight; return stateWeight; } if (stateCount == 1) { AnimationState state = states[0]; _playableWeight = state.CalculatePlayableWeights(interpolated, out float maxChildWeight); state.SetPlayableInputWeight(_playableWeight > 0.0f ? 1.0f : 0.0f); maxWeight = maxChildWeight; return stateWeight; } maxWeight = 0.0f; float childrenWeight = 0.0f; for (int i = 0; i < stateCount; ++i) { childrenWeight += states[i].CalculatePlayableWeights(interpolated, out float maxChildWeight); if (maxChildWeight > maxWeight) { maxWeight = maxChildWeight; } } if (childrenWeight < 0.001f) { childrenWeight = 0.0f; for (int i = 0; i < stateCount; ++i) { states[i].SetPlayableInputWeight(0.0f); } } else { float weightMultiplier = 1.0f / childrenWeight; float multipliedWeightSum = 0.0f; for (int i = 0; i < stateCount; ++i) { multipliedWeightSum += states[i].RecalculatePlayableWeight(weightMultiplier); } if (multipliedWeightSum < 0.001f) { weightMultiplier = 0.0f; multipliedWeightSum = 0.0f; } else { weightMultiplier = 1.0f / multipliedWeightSum; } for (int i = 0; i < stateCount; ++i) { AnimationState state = states[i]; state.RecalculatePlayableWeight(weightMultiplier); state.SetPlayableInputWeight(); } if (childrenWeight > 1.0f) { childrenWeight = 1.0f; } } if (childrenWeight > maxWeight) { maxWeight = childrenWeight; } _playableWeight = childrenWeight; return stateWeight; } /// /// Modifies weight for playable graph. Used for normalization. /// Pending playable graph input weight is multiplied by this. /// public float RecalculatePlayableWeight(float multiplier) { _playableWeight *= multiplier; return _playableWeight; } /// /// Returns first animation state with the given name. /// /// Name of the state. /// Search through children states, using Breadth-First Search. public AnimationState FindState(string stateName, bool recursive = true) { return FindState(stateName, out AnimationState state, recursive) == true ? state : default; } /// /// Returns first animation state of type T. /// /// Search through children states, using Breadth-First Search. public T FindState(bool recursive = true) where T : class { return FindState(default, out T state, recursive) == true ? state : default; } /// /// Returns first animation state of type T with the given name. /// /// Name of the state. /// Search through children states, using Breadth-First Search. public T FindState(string stateName, bool recursive = true) where T : class { return FindState(stateName, out T state, recursive) == true ? state : default; } /// /// Returns first animation state of type T. /// /// Reference to the state. /// Search through children states, using Breadth-First Search. public bool FindState(out T state, bool recursive = true) where T : class { return FindState(default, out state, recursive); } /// /// Returns first animation state of type T with the given name. /// /// Name of the state. /// Reference to the state. /// Search through children states, using Breadth-First Search. public bool FindState(string stateName, out T state, bool recursive = true) where T : class { AnimationState[] states = _states; for (int i = 0, count = states.Length; i < count; ++i) { AnimationState stateState = states[i]; if (stateState is T stateStateAsT && (stateName == default || string.CompareOrdinal(stateState.name, stateName) == 0)) { state = stateStateAsT; return true; } } if (recursive == true) { for (int i = 0, count = states.Length; i < count; ++i) { AnimationState stateState = states[i]; if (stateState.FindState(stateName, out T innerState, true) == true) { state = innerState; return true; } } } state = default; return false; } public virtual void OnInspectorGUI() { #if UNITY_EDITOR //UnityEditor.EditorGUILayout.LabelField("", .ToString("0.00")); #endif } // AnimationState INTERFACE protected abstract void CreatePlayable(); protected virtual void OnInitialize() {} protected virtual void OnDeinitialize() {} protected virtual void OnSpawned() {} protected virtual void OnDespawned() {} protected virtual void OnFixedUpdate() {} protected virtual void OnInterpolate() {} protected virtual void OnActivate() {} protected virtual void OnDeactivate() {} protected virtual void OnSetDefaults() {} // IAnimationWeightProvider INTERFACE float IAnimationWeightProvider.Weight { get { return _weight; } set { _weight = value; } } float IAnimationWeightProvider.InterpolatedWeight { get { return _interpolatedWeight; } set { _interpolatedWeight = value; } } // IAnimationFadingProvider INTERFACE float IAnimationFadingProvider.FadingSpeed { get { return _fadingSpeed; } set { _fadingSpeed = value; } } float IAnimationFadingProvider.InterpolatedFadingSpeed { get { return _interpolatedFadingSpeed; } set { _interpolatedFadingSpeed = value; } } // PROTECTED METHODS protected void AddPlayable(T playable, int sourceOutputIndex) where T : struct, IPlayable { if (_port >= 0) throw new NotSupportedException(); _port = _owner.Mixer.AddInput(playable, sourceOutputIndex, 0.0f); } // PRIVATE METHODS private void InitializeStates() { if (_states != null) return; List activeStates = new List(8); Transform root = transform; for (int i = 0, count = root.childCount; i < count; ++i) { Transform child = root.GetChild(i); AnimationState state = child.GetComponentNoAlloc(); if (state != null && state.enabled == true && state.gameObject.activeSelf == true) { activeStates.Add(state); } } _states = activeStates.ToArray(); IAnimationStateOwner animationStateOwner = this as IAnimationStateOwner; if (_states.Length > 0 && animationStateOwner == null) { throw new NotImplementedException($"State {name}({GetType().FullName}) doesn't implement {nameof(IAnimationStateOwner)}, sub-states are not supported!"); } AnimationState[] states = _states; for (int i = 0, count = states != null ? states.Length : 0; i < count; ++i) { AnimationState state = states[i]; state.Initialize(_controller, animationStateOwner); } } private void DeinitializeStates() { AnimationState[] states = _states; for (int i = 0, count = states != null ? states.Length : 0; i < count; ++i) { AnimationState state = states[i]; if (state != null) { state.Deinitialize(); } } _states = null; } } }