// 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);
/************************************************************************************************************************/
}
}