namespace Fusion.Addons.AnimationController { using System.Collections.Generic; using Unity.Profiling; using UnityEngine; using UnityEngine.Animations; using UnityEngine.Playables; /// /// Animation layer component. /// public unsafe partial class AnimationLayer : MonoBehaviour, IAnimationStateOwner, IAnimationWeightProvider, IAnimationFadingProvider { // PUBLIC MEMBERS /// /// Reference to animation controller. /// public AnimationController Controller => _controller; /// /// List of animation states (direct children). /// public IList States => _states; /// /// Playable mixer which represents this layer and mixes outputs from states. /// public AnimationMixerPlayable Mixer => _mixer; /// /// Input port used to identify connection to owner (controller) mixer. /// public int Port => _port; /// /// Weight of the layer. /// public float Weight { get { return _weight; } protected set { _weight = value; } } /// /// Fading speed of the layer. 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; } } // PRIVATE MEMBERS [SerializeField] private AvatarMask _avatarMask; [SerializeField] private bool _isAdditive; [SerializeField] public float _initialWeight = 1.0f; private AnimationController _controller; private AnimationState[] _states; private AnimationMixerPlayable _mixer; private int _port; private float _weight; private float _fadingSpeed; private float _cachedWeight; private float _interpolatedWeight; private float _interpolatedFadingSpeed; private ProfilerMarker _profilerMarker; // PUBLIC METHODS /// /// Returns true if the layer is fading in or if the Weight is greater than zero and the layer is not fading out. /// public bool IsActive() { return (_fadingSpeed == 0.0f && _weight > 0.0f) || _fadingSpeed > 0.0f; } /// /// Returns true if the layer is fading in or if the Weight is greater than zero. /// public bool IsPlaying() { return _fadingSpeed > 0.0f || _weight > 0.0f; } /// /// Returns true if the layer is fading in (fading speed greater than zero). /// public bool IsFadingIn() { return _fadingSpeed > 0.0f; } /// /// Returns true if the layer is fading out (fading speed lower than zero). /// public bool IsFadingOut() { return _fadingSpeed < 0.0f; } /// /// Activate the layer. /// How long it takes to reach full Weight from zero. /// public void Activate(float fadeDuration) { 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(AnimationLayer)}.{nameof(Activate)} ({name}), Fade Duration: {fadeDuration:F3}", gameObject); OnActivate(); } /// /// Deactivate the layer. /// How long it takes to reach zero Weight from full. /// public void Deactivate(float fadeDuration) { 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(AnimationLayer)}.{nameof(Deactivate)} ({name}), Fade Duration: {fadeDuration:F3}", gameObject); OnDeactivate(); } /// /// Returns true if the layer has an active 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 layer has an active 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 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 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 layer. /// 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); } } /// /// Called when Animation Controller is initialized. /// public void Initialize(AnimationController controller) { _controller = controller; #if ANIMATIONS_PROFILE _profilerMarker = new ProfilerMarker(GetType().Name); #endif InitializeStates(); OnInitialize(); } /// /// Called when Animation Controller is deinitialized. /// public void Deinitialize() { OnDeinitialize(); DeinitializeStates(); _controller = default; } /// /// Called when Animation Controller is spawned. /// public void Spawned() { _mixer = AnimationMixerPlayable.Create(_controller.Graph); _port = _controller.Mixer.AddInput(_mixer, 0, _initialWeight); _weight = _initialWeight; _fadingSpeed = 0.0f; _cachedWeight = _initialWeight; _interpolatedWeight = _initialWeight; _interpolatedFadingSpeed = _fadingSpeed; _controller.Mixer.SetLayerAdditive((uint)_port, _isAdditive); if (_avatarMask != null) { _controller.Mixer.SetLayerMaskFromAvatarMask((uint)_port, _avatarMask); } 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 != null ? states.Length : 0; i < count; ++i) { AnimationState state = states[i]; if (state != null) { state.Despawned(); } } if (_mixer.IsValid() == true) { _mixer.Destroy(); } } /// /// Manual fixed update execution, called internally by Animation Controller. /// public void ManualFixedUpdate() { if (_fadingSpeed <= 0.0f && _weight <= 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.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 Animation Controller. /// public float GetPlayableInputWeight() { return _controller.Mixer.GetInputWeight(_port); } /// /// Calculates and applies weights in playable graph. Requires normalization, final weights can be different than weights stores in layers/states. /// If true interpolated properties will be used. /// public void SetPlayableInputWeights(bool interpolated) { float layerWeight = interpolated == true ? _interpolatedWeight : _weight; if (layerWeight <= 0.0f) { SetPlayableInputWeight(0.0f); return; } AnimationState[] states = _states; int stateCount = states.Length; if (stateCount == 0) { SetPlayableInputWeight(layerWeight); return; } if (stateCount == 1) { AnimationState state = states[0]; float playableWeight = state.CalculatePlayableWeights(interpolated, out float maxChildWeight); state.SetPlayableInputWeight(playableWeight > 0.0f ? 1.0f : 0.0f); SetPlayableInputWeight(maxChildWeight * layerWeight); return; } float 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; } SetPlayableInputWeight(maxWeight * layerWeight); } /// /// 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 layerState = states[i]; if (layerState is T layerStateAsT && (stateName == default || string.CompareOrdinal(layerState.name, stateName) == 0)) { state = layerStateAsT; return true; } } if (recursive == true) { for (int i = 0, count = states.Length; i < count; ++i) { AnimationState layerState = states[i]; if (layerState.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 } // AnimationLayer INTERFACE 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() {} // IAnimationStateOwner INTERFACE AnimationMixerPlayable IAnimationStateOwner.Mixer => _mixer; bool IAnimationStateOwner.IsActive(bool self) { return (_fadingSpeed == 0.0f && _weight > 0.0f) || _fadingSpeed > 0.0f; } bool IAnimationStateOwner.IsPlaying(bool self) { return _fadingSpeed > 0.0f || _weight > 0.0f; } bool IAnimationStateOwner.IsFadingIn(bool self) { return _fadingSpeed > 0.0f; } bool IAnimationStateOwner.IsFadingOut(bool self) { return _fadingSpeed < 0.0f; } void IAnimationStateOwner.Activate(AnimationState source, float fadeDuration) { IList states = States; for (int i = 0, count = states.Count; i < count; ++i) { AnimationState state = states[i]; if (state.Port != source.Port) { state.Deactivate(fadeDuration, true); } } } void IAnimationStateOwner.Deactivate(AnimationState source, float fadeDuration) { } // 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; } } // PRIVATE METHODS private void SetPlayableInputWeight(float weight) { if (weight == _cachedWeight) return; _cachedWeight = weight; _controller.Mixer.SetInputWeight(_port, weight); } 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(); AnimationState[] states = _states; for (int i = 0, count = states != null ? states.Length : 0; i < count; ++i) { AnimationState state = states[i]; state.Initialize(_controller, this); } } 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; } } }