// Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2023 Kybernetik // #if ! UNITY_EDITOR #pragma warning disable CS0618 // Type or member is obsolete (for ControllerState in Animancer Lite). #endif using System; using System.Collections.Generic; using System.Runtime.CompilerServices; using UnityEngine; using UnityEngine.Animations; using UnityEngine.Playables; namespace Animancer { /// [Pro-Only] /// A which plays a main with the /// ability to play other individual s separately. /// /// /// Documentation: Hybrid /// /// https://kybernetik.com.au/animancer/api/Animancer/HybridAnimancerComponent /// #if !UNITY_EDITOR [System.Obsolete(Validate.ProOnlyMessage)] #endif [AddComponentMenu(Strings.MenuPrefix + "Hybrid Animancer Component")] [HelpURL(Strings.DocsURLs.APIDocumentation + "/" + nameof(HybridAnimancerComponent))] public class HybridAnimancerComponent : NamedAnimancerComponent { /************************************************************************************************************************/ #region Controller /************************************************************************************************************************/ [SerializeField, Tooltip("The main Animator Controller that this object will play")] private ControllerTransition _Controller; /// [] /// The transition containing the main that this object plays. /// public ref ControllerTransition Controller => ref _Controller; /************************************************************************************************************************/ /// /// Transitions to the over its specified /// and returns the /// . /// public ControllerState PlayController() { if (!_Controller.IsValid()) return null; Play(_Controller); return _Controller.State; } /************************************************************************************************************************/ /// The of the . public AnimatorControllerPlayable ControllerPlayable => _Controller.State.Playable; /************************************************************************************************************************/ #endregion /************************************************************************************************************************/ #region Initialization /************************************************************************************************************************/ #if UNITY_EDITOR /// [Editor-Only] /// Sets = false by default so that will play the /// instead of the first animation in the /// array. /// /// /// Called by the Unity Editor when this component is first added (in Edit Mode) and whenever the Reset command /// is executed from its context menu. /// protected override void Reset() { base.Reset(); if (Animator != null) { Controller = Animator.runtimeAnimatorController; Animator.runtimeAnimatorController = null; } PlayAutomatically = false; } #endif /************************************************************************************************************************/ /// /// Plays the if is false (otherwise it plays the /// first animation in the array). /// protected override void OnEnable() { if (!TryGetAnimator()) return; PlayController(); base.OnEnable(); #if UNITY_ASSERTIONS if (Animator != null && Animator.runtimeAnimatorController != null) OptionalWarning.NativeControllerHybrid.Log($"An Animator Controller is assigned to the" + $" {nameof(Animator)} component while also using a {nameof(HybridAnimancerComponent)}." + $" Most likely only one of them is being used so the other should be removed." + $" See the documentation for more information: {Strings.DocsURLs.AnimatorControllers}", this); #endif } /************************************************************************************************************************/ /// /// Sets to true in order to avoid some /// undesirable behaviours caused by disconnecting s from the graph. /// protected override void OnInitializePlayable() { base.OnInitializePlayable(); Playable.KeepChildrenConnected = true; } /************************************************************************************************************************/ /// public override void GatherAnimationClips(ICollection clips) { base.GatherAnimationClips(clips); clips.GatherFromSource(_Controller); } /************************************************************************************************************************/ #endregion /************************************************************************************************************************/ #region Animator Wrappers /************************************************************************************************************************/ #region Properties /************************************************************************************************************************/ /// public PlayableGraph playableGraph => Playable.Graph; /// public RuntimeAnimatorController runtimeAnimatorController { get => Controller.Controller; set => Controller.Controller = value; } /// [Warning] doesn't support speed control. /// /// If you don't need this feature, you can use #pragma warning disable CS0618 to disable this warning. /// Otherwise, check https://kybernetik.com.au/animancer/docs/manual/animator-controllers for other options. /// #if UNITY_ASSERTIONS [Obsolete(nameof(HybridAnimancerComponent) + " doesn't support speed control." + " If you don't need this feature, you can use `#pragma warning disable CS0618` to disable this warning." + " Otherwise, check " + Strings.DocsURLs.AnimatorControllers + " for other options.")] #endif public float speed { get => Animator.speed; set => Animator.speed = value; } /************************************************************************************************************************/ // Root Motion. /************************************************************************************************************************/ /// public bool applyRootMotion { get => Animator.applyRootMotion; set => Animator.applyRootMotion = value; } /// public Quaternion bodyRotation { get => Animator.bodyRotation; set => Animator.bodyRotation = value; } /// public Vector3 bodyPosition { get => Animator.bodyPosition; set => Animator.bodyPosition = value; } /// public float gravityWeight => Animator.gravityWeight; /// public bool hasRootMotion => Animator.hasRootMotion; /// public bool layersAffectMassCenter { get => Animator.layersAffectMassCenter; set => Animator.layersAffectMassCenter = value; } /// public Vector3 pivotPosition => Animator.pivotPosition; /// public float pivotWeight => Animator.pivotWeight; /// public Quaternion rootRotation { get => Animator.rootRotation; set => Animator.rootRotation = value; } /// public Vector3 rootPosition { get => Animator.rootPosition; set => Animator.rootPosition = value; } /// public Vector3 angularVelocity => Animator.angularVelocity; /// public Vector3 velocity => Animator.velocity; /// public Quaternion deltaRotation => Animator.deltaRotation; /// public Vector3 deltaPosition => Animator.deltaPosition; /// public void ApplyBuiltinRootMotion() => Animator.ApplyBuiltinRootMotion(); /************************************************************************************************************************/ // Feet. /************************************************************************************************************************/ /// public float feetPivotActive { get => Animator.feetPivotActive; set => Animator.feetPivotActive = value; } /// public bool stabilizeFeet { get => Animator.stabilizeFeet; set => Animator.stabilizeFeet = value; } /// public float rightFeetBottomHeight => Animator.rightFeetBottomHeight; /// public float leftFeetBottomHeight => Animator.leftFeetBottomHeight; /************************************************************************************************************************/ #endregion /************************************************************************************************************************/ #region Cross Fade /************************************************************************************************************************/ /// Starts a transition from the current state to the specified state using normalized times. /// If `fadeDuration` is negative, it uses the . public void CrossFade( int stateNameHash, float fadeDuration = ControllerState.DefaultFadeDuration, int layer = -1, float normalizedTime = float.NegativeInfinity) { fadeDuration = ControllerState.GetFadeDuration(fadeDuration); var controllerState = PlayController(); controllerState.Playable.CrossFade(stateNameHash, fadeDuration, layer, normalizedTime); } /************************************************************************************************************************/ /// Starts a transition from the current state to the specified state using normalized times. /// If `fadeDuration` is negative, it uses the . public AnimancerState CrossFade( string stateName, float fadeDuration = ControllerState.DefaultFadeDuration, int layer = -1, float normalizedTime = float.NegativeInfinity) { fadeDuration = ControllerState.GetFadeDuration(fadeDuration); if (States.TryGet(name, out var state)) { Play(state, fadeDuration); if (layer >= 0) state.LayerIndex = layer; if (normalizedTime != float.NegativeInfinity) state.NormalizedTime = normalizedTime; return state; } else { var controllerState = PlayController(); controllerState.Playable.CrossFade(stateName, fadeDuration, layer, normalizedTime); return controllerState; } } /************************************************************************************************************************/ /// Starts a transition from the current state to the specified state using times in seconds. /// If `fadeDuration` is negative, it uses the . public void CrossFadeInFixedTime( int stateNameHash, float fadeDuration = ControllerState.DefaultFadeDuration, int layer = -1, float fixedTime = 0) { fadeDuration = ControllerState.GetFadeDuration(fadeDuration); var controllerState = PlayController(); controllerState.Playable.CrossFadeInFixedTime(stateNameHash, fadeDuration, layer, fixedTime); } /************************************************************************************************************************/ /// Starts a transition from the current state to the specified state using times in seconds. /// If `fadeDuration` is negative, it uses the . public AnimancerState CrossFadeInFixedTime( string stateName, float fadeDuration = ControllerState.DefaultFadeDuration, int layer = -1, float fixedTime = 0) { fadeDuration = ControllerState.GetFadeDuration(fadeDuration); if (States.TryGet(name, out var state)) { Play(state, fadeDuration); if (layer >= 0) state.LayerIndex = layer; state.Time = fixedTime; return state; } else { var controllerState = PlayController(); controllerState.Playable.CrossFadeInFixedTime(stateName, fadeDuration, layer, fixedTime); return controllerState; } } /************************************************************************************************************************/ #endregion /************************************************************************************************************************/ #region Play /************************************************************************************************************************/ /// Plays the specified state immediately, starting from a particular normalized time. public void Play( int stateNameHash, int layer = -1, float normalizedTime = float.NegativeInfinity) { var controllerState = PlayController(); controllerState.Playable.Play(stateNameHash, layer, normalizedTime); } /************************************************************************************************************************/ /// Plays the specified state immediately, starting from a particular normalized time. public AnimancerState Play( string stateName, int layer = -1, float normalizedTime = float.NegativeInfinity) { if (States.TryGet(name, out var state)) { Play(state); if (layer >= 0) state.LayerIndex = layer; if (normalizedTime != float.NegativeInfinity) state.NormalizedTime = normalizedTime; return state; } else { var controllerState = PlayController(); controllerState.Playable.Play(stateName, layer, normalizedTime); return controllerState; } } /************************************************************************************************************************/ /// Plays the specified state immediately, starting from a particular time (in seconds). public void PlayInFixedTime( int stateNameHash, int layer = -1, float fixedTime = 0) { var controllerState = PlayController(); controllerState.Playable.PlayInFixedTime(stateNameHash, layer, fixedTime); } /************************************************************************************************************************/ /// Plays the specified state immediately, starting from a particular time (in seconds). public AnimancerState PlayInFixedTime( string stateName, int layer = -1, float fixedTime = 0) { if (States.TryGet(name, out var state)) { Play(state); if (layer >= 0) state.LayerIndex = layer; state.Time = fixedTime; return state; } else { var controllerState = PlayController(); controllerState.Playable.PlayInFixedTime(stateName, layer, fixedTime); return controllerState; } } /************************************************************************************************************************/ #endregion /************************************************************************************************************************/ #region Parameters /************************************************************************************************************************/ /// Gets the value of the specified boolean parameter. public bool GetBool(int id) => ControllerPlayable.GetBool(id); /// Gets the value of the specified boolean parameter. public bool GetBool(string name) => ControllerPlayable.GetBool(name); /// Sets the value of the specified boolean parameter. public void SetBool(int id, bool value) => ControllerPlayable.SetBool(id, value); /// Sets the value of the specified boolean parameter. public void SetBool(string name, bool value) => ControllerPlayable.SetBool(name, value); /// Gets the value of the specified float parameter. public float GetFloat(int id) => ControllerPlayable.GetFloat(id); /// Gets the value of the specified float parameter. public float GetFloat(string name) => ControllerPlayable.GetFloat(name); /// Sets the value of the specified float parameter. public void SetFloat(int id, float value) => ControllerPlayable.SetFloat(id, value); /// Sets the value of the specified float parameter. public void SetFloat(string name, float value) => ControllerPlayable.SetFloat(name, value); /// Sets the value of the specified float parameter with smoothing. public float SetFloat(string name, float value, float dampTime, float deltaTime, float maxSpeed = float.PositiveInfinity) => _Controller.State.SetFloat(name, value, dampTime, deltaTime, maxSpeed); /// Sets the value of the specified float parameter with smoothing. public float SetFloat(int id, float value, float dampTime, float deltaTime, float maxSpeed = float.PositiveInfinity) => _Controller.State.SetFloat(name, value, dampTime, deltaTime, maxSpeed); /// Gets the value of the specified integer parameter. public int GetInteger(int id) => ControllerPlayable.GetInteger(id); /// Gets the value of the specified integer parameter. public int GetInteger(string name) => ControllerPlayable.GetInteger(name); /// Sets the value of the specified integer parameter. public void SetInteger(int id, int value) => ControllerPlayable.SetInteger(id, value); /// Sets the value of the specified integer parameter. public void SetInteger(string name, int value) => ControllerPlayable.SetInteger(name, value); /// Sets the specified trigger parameter to true. public void SetTrigger(int id) => ControllerPlayable.SetTrigger(id); /// Sets the specified trigger parameter to true. public void SetTrigger(string name) => ControllerPlayable.SetTrigger(name); /// Resets the specified trigger parameter to false. public void ResetTrigger(int id) => ControllerPlayable.ResetTrigger(id); /// Resets the specified trigger parameter to false. public void ResetTrigger(string name) => ControllerPlayable.ResetTrigger(name); /// Indicates whether the specified parameter is controlled by an . public bool IsParameterControlledByCurve(int id) => ControllerPlayable.IsParameterControlledByCurve(id); /// Indicates whether the specified parameter is controlled by an . public bool IsParameterControlledByCurve(string name) => ControllerPlayable.IsParameterControlledByCurve(name); /// Gets the details of one of the 's parameters. public AnimatorControllerParameter GetParameter(int index) => ControllerPlayable.GetParameter(index); /// Gets the number of parameters in the . public int GetParameterCount() => ControllerPlayable.GetParameterCount(); /************************************************************************************************************************/ /// The number of parameters in the . public int parameterCount => ControllerPlayable.GetParameterCount(); /// The parameters in the . /// /// This property allocates a new array when first accessed. To avoid that, you can use /// and instead. /// public AnimatorControllerParameter[] parameters => _Controller.State.parameters; /************************************************************************************************************************/ #endregion /************************************************************************************************************************/ #region Other /************************************************************************************************************************/ // Clips. /************************************************************************************************************************/ /// Gets information about the s currently being played. public AnimatorClipInfo[] GetCurrentAnimatorClipInfo(int layerIndex = 0) => ControllerPlayable.GetCurrentAnimatorClipInfo(layerIndex); /// Gets information about the s currently being played. public void GetCurrentAnimatorClipInfo(int layerIndex, List clips) => ControllerPlayable.GetCurrentAnimatorClipInfo(layerIndex, clips); /// Gets the number of s currently being played. public int GetCurrentAnimatorClipInfoCount(int layerIndex = 0) => ControllerPlayable.GetCurrentAnimatorClipInfoCount(layerIndex); /// Gets information about the s currently being transitioned towards. public AnimatorClipInfo[] GetNextAnimatorClipInfo(int layerIndex = 0) => ControllerPlayable.GetNextAnimatorClipInfo(layerIndex); /// Gets information about the s currently being transitioned towards. public void GetNextAnimatorClipInfo(int layerIndex, List clips) => ControllerPlayable.GetNextAnimatorClipInfo(layerIndex, clips); /// Gets the number of s currently being transitioned towards. public int GetNextAnimatorClipInfoCount(int layerIndex = 0) => ControllerPlayable.GetNextAnimatorClipInfoCount(layerIndex); /************************************************************************************************************************/ // Humanoid. /************************************************************************************************************************/ /// public float humanScale => Animator.humanScale; /// public bool isHuman => Animator.isHuman; /// public Transform GetBoneTransform(HumanBodyBones humanBoneId) => Animator.GetBoneTransform(humanBoneId); /// public void SetBoneLocalRotation(HumanBodyBones humanBoneId, Quaternion rotation) => Animator.SetBoneLocalRotation(humanBoneId, rotation); /************************************************************************************************************************/ // Layers. /************************************************************************************************************************/ /// Gets the number of layers in the . public int GetLayerCount() => ControllerPlayable.GetLayerCount(); /// The number of layers in the . public int layerCount => ControllerPlayable.GetLayerCount(); /// Gets the index of the layer with the specified name. public int GetLayerIndex(string layerName) => ControllerPlayable.GetLayerIndex(layerName); /// Gets the name of the layer with the specified index. public string GetLayerName(int layerIndex) => ControllerPlayable.GetLayerName(layerIndex); /// Gets the weight of the layer at the specified index. public float GetLayerWeight(int layerIndex) => ControllerPlayable.GetLayerWeight(layerIndex); /// Sets the weight of the layer at the specified index. public void SetLayerWeight(int layerIndex, float weight) => ControllerPlayable.SetLayerWeight(layerIndex, weight); /************************************************************************************************************************/ // StateMachineBehaviours. /************************************************************************************************************************/ /// public T GetBehaviour() where T : StateMachineBehaviour => Animator.GetBehaviour(); /// public T[] GetBehaviours() where T : StateMachineBehaviour => Animator.GetBehaviours(); /// public StateMachineBehaviour[] GetBehaviours(int fullPathHash, int layerIndex) => Animator.GetBehaviours(fullPathHash, layerIndex); /************************************************************************************************************************/ // States. /************************************************************************************************************************/ /// Returns information about the current state. public AnimatorStateInfo GetCurrentAnimatorStateInfo(int layerIndex = 0) => ControllerPlayable.GetCurrentAnimatorStateInfo(layerIndex); /// Returns information about the next state being transitioned towards. public AnimatorStateInfo GetNextAnimatorStateInfo(int layerIndex = 0) => ControllerPlayable.GetNextAnimatorStateInfo(layerIndex); /// Indicates whether the specified layer contains the specified state. public bool HasState(int layerIndex, int stateID) => ControllerPlayable.HasState(layerIndex, stateID); /************************************************************************************************************************/ // Transitions. /************************************************************************************************************************/ /// Indicates whether the specified layer is currently executing a transition. public bool IsInTransition(int layerIndex = 0) => ControllerPlayable.IsInTransition(layerIndex); /// Gets information about the current transition. public AnimatorTransitionInfo GetAnimatorTransitionInfo(int layerIndex = 0) => ControllerPlayable.GetAnimatorTransitionInfo(layerIndex); /************************************************************************************************************************/ // Other. /************************************************************************************************************************/ /// public Avatar avatar { get => Animator.avatar; set => Animator.avatar = value; } /// public AnimatorCullingMode cullingMode { get => Animator.cullingMode; set => Animator.cullingMode = value; } /// public bool fireEvents { get => Animator.fireEvents; set => Animator.fireEvents = value; } /// public bool hasBoundPlayables => Animator.hasBoundPlayables; /// public bool hasTransformHierarchy => Animator.hasTransformHierarchy; /// public bool isInitialized => Animator.isInitialized; /// public bool isOptimizable => Animator.isOptimizable; /// public bool logWarnings { get => Animator.logWarnings; set => Animator.logWarnings = value; } /// /// Changing this at runtime doesn't work when using the Playables API. public AnimatorUpdateMode updateMode { get => Animator.updateMode; set => Animator.updateMode = value; } /************************************************************************************************************************/ #if UNITY_2022_2_OR_NEWER /// public bool keepAnimatorStateOnDisable { get => Animator.keepAnimatorStateOnDisable; set => Animator.keepAnimatorStateOnDisable = value; } #else /// public bool keepAnimatorControllerStateOnDisable { get => Animator.keepAnimatorControllerStateOnDisable; set => Animator.keepAnimatorControllerStateOnDisable = value; } #endif /************************************************************************************************************************/ /// public void Rebind() => Animator.Rebind(); /************************************************************************************************************************/ #endregion /************************************************************************************************************************/ #endregion /************************************************************************************************************************/ } /// Extension methods for . /// https://kybernetik.com.au/animancer/api/Animancer/HybridAnimancerComponentExtensions /// public static class HybridAnimancerComponentExtensions { /************************************************************************************************************************/ /// /// Advances time by the specified value (in seconds) and immediately applies the current states of all /// animations to the animated objects. /// /// /// This is an extension method to avoid being treated as a Update /// message and getting called every frame. /// public static void Update(this HybridAnimancerComponent animancer, float deltaTime) => animancer.Evaluate(deltaTime); /************************************************************************************************************************/ } }