Files
BABA_YAGA/Assets/Opsive/UltimateCharacterController/Scripts/Character/CharacterIK.cs
2026-06-09 02:05:00 +07:00

1048 lines
60 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/// ---------------------------------------------
/// Ultimate Character Controller
/// Copyright (c) Opsive. All Rights Reserved.
/// https://www.opsive.com
/// ---------------------------------------------
namespace Opsive.UltimateCharacterController.Character
{
using Opsive.Shared.Events;
using Opsive.Shared.Game;
using Opsive.UltimateCharacterController.Game;
using Opsive.UltimateCharacterController.Inventory;
using Opsive.UltimateCharacterController.Items;
using Opsive.UltimateCharacterController.Motion;
using Opsive.UltimateCharacterController.Utility;
using UnityEngine;
/// <summary>
/// Allows the character to stand on uneven surfaces and rotates and positions the character's limbs to face in the look direction.
/// </summary>
public class CharacterIK : CharacterIKBase
{
#if UNITY_EDITOR
[Tooltip("Draw a debug line to see the direction that the character is facing (editor only).")]
[SerializeField] protected bool m_DebugDrawLookRay;
#endif
[Tooltip("The index of the base layer within the Animator Controller.")]
[SerializeField] protected int m_BaseLayerIndex = 0;
[Tooltip("The index of the upper body layer within the Animator Controller.")]
[SerializeField] protected int m_UpperBodyLayerIndex = 4;
[Tooltip("The layers that the component should use when determining the objects to test against.")]
[SerializeField] protected LayerMask m_LayerMask = ~(1 << LayerManager.IgnoreRaycast | 1 << LayerManager.TransparentFX | 1 << LayerManager.Overlay |
1 << LayerManager.Water | 1 << LayerManager.UI | 1 << LayerManager.VisualEffect |
1 << LayerManager.SubCharacter | 1 << LayerManager.Character);
[Tooltip("An offset to apply to the look at direction for the body and arms.")]
[SerializeField] protected Vector3 m_LookAtOffset;
[InspectorFoldout("Body")]
[Tooltip("Determines how much weight is applied to the body when looking at the target. (0-1).")]
[Range(0, 1)] [SerializeField] protected float m_LookAtBodyWeight = 0.05f;
[Tooltip("Determines how much weight is applied to the head when looking at the target. (0-1).")]
[Range(0, 1)] [SerializeField] protected float m_LookAtHeadWeight = 0.425f;
[Tooltip("Determines how much weight is applied to the eyes when looking at the target. (0-1).")]
[Range(0, 1)] [SerializeField] protected float m_LookAtEyesWeight = 1;
[Tooltip("A value of 0 means the character is completely unrestrained in motion, 1 means the character motion completely clamped (look at becomes impossible) (0-1).")]
[Range(0, 1)] [SerializeField] protected float m_LookAtClampWeight = 0.35f;
[Tooltip("The speed at which the look at weight should adjust.")]
[SerializeField] protected float m_LookAtAdjustmentSpeed = 0.2f;
[Tooltip("The speed at which the hips position should adjust to using IK and not using IK.")]
[SerializeField] protected float m_HipsPositionAdjustmentSpeed = 4;
[InspectorFoldout("Feet")]
[Tooltip("The offset of the foot between the foot bone and the base of the foot.")]
[SerializeField] protected float m_FootOffsetAdjustment = 0.005f;
[Tooltip("The speed at which the foot weight should adjust to when foot IK is active.")]
[SerializeField] protected float m_FootWeightActiveAdjustmentSpeed = 10;
[Tooltip("The speed at which the foot weight should adjust to when foot IK is inactive.")]
[SerializeField] protected float m_FootWeightInactiveAdjustmentSpeed = 2;
[InspectorFoldout("Upper Arm")]
[Tooltip("Determines how much weight is applied to the upper arms when looking at the target (0-1).")]
[Range(0, 1)] [SerializeField] protected float m_UpperArmWeight = 1;
[Tooltip("The speed at which the upper arm rotation should adjust to using IK and not using IK.")]
[SerializeField] protected float m_UpperArmAdjustmentSpeed = 10;
[InspectorFoldout("Hands")]
[Tooltip("Determines how much weight is applied to the hands when looking at the target (0-1).")]
[Range(0, 1)] [SerializeField] protected float m_HandWeight = 1;
[Tooltip("The speed at which the hand position/rotation should adjust to using IK and not using IK.")]
[SerializeField] protected float m_HandAdjustmentSpeed = 10;
[Tooltip("Specifies a local offset to add to the position of the hands.")]
[SerializeField] protected Vector3 m_HandPositionOffset;
[Tooltip("The left hand positional spring used for IK movement.")]
[SerializeField] protected Spring m_LeftHandPositionSpring = new Spring();
[Tooltip("The left hand rotational spring used for IK movement.")]
[SerializeField] protected Spring m_LeftHandRotationSpring = new Spring(0.2f, 0.05f);
[Tooltip("The right hand positional spring used for IK movement.")]
[SerializeField] protected Spring m_RightHandPositionSpring = new Spring();
[Tooltip("The right hand rotational spring used for IK movement.")]
[SerializeField] protected Spring m_RightHandRotationSpring = new Spring(0.2f, 0.05f);
public LayerMask LayerMask { get { return m_LayerMask; } set { m_LayerMask = value; } }
public Vector3 LookAtOffset { get { return m_LookAtOffset; } set { m_LookAtOffset = value; } }
public float LookAtBodyWeight { get { return m_LookAtBodyWeight; } set { m_LookAtBodyWeight = value; } }
public float LookAtHeadWeight { get { return m_LookAtHeadWeight; } set { m_LookAtHeadWeight = value; } }
public float LookAtEyesWeight { get { return m_LookAtEyesWeight; } set { m_LookAtEyesWeight = value; } }
public float LookAtClampWeight { get { return m_LookAtClampWeight; } set { m_LookAtClampWeight = value; } }
public float LookAtAdjustmentSpeed { get { return m_LookAtAdjustmentSpeed; } set { m_LookAtAdjustmentSpeed = value; } }
public float HipsPositionAdjustmentSpeed { get { return m_HipsPositionAdjustmentSpeed; }
set { m_HipsPositionAdjustmentSpeed = value;
if (m_HipsPositionAdjustmentSpeed == 0) { m_HipsOffset = 0; }
}
}
public float FootOffsetAdjustment { get { return m_FootOffsetAdjustment; } set { m_FootOffsetAdjustment = value; } }
public float FootWeightActiveAdjustmentSpeed { get { return m_FootWeightActiveAdjustmentSpeed; } set { m_FootWeightActiveAdjustmentSpeed = value; } }
public float FootWeightInactiveAdjustmentSpeed { get { return m_FootWeightInactiveAdjustmentSpeed; } set { m_FootWeightInactiveAdjustmentSpeed = value; } }
public float UpperArmWeight { get { return m_UpperArmWeight; } set { m_UpperArmWeight = value; } }
public float UpperArmAdjustmentSpeed { get { return m_UpperArmAdjustmentSpeed; } set { m_UpperArmAdjustmentSpeed = value; } }
public float HandWeight { get { return m_HandWeight; } set { m_HandWeight = value; } }
public float HandAdjustmentSpeed { get { return m_HandAdjustmentSpeed; } set { m_HandAdjustmentSpeed = value; } }
public Vector3 HandPositionOffset { get { return m_HandPositionOffset; } set { m_HandPositionOffset = value; } }
public Spring LeftHandPositionSpring { get { return m_LeftHandPositionSpring; }
set {
m_LeftHandPositionSpring = value;
if (m_LeftHandPositionSpring != null) { m_LeftHandPositionSpring.Initialize(false, true); }
}
}
public Spring LeftHandRotationSpring { get { return m_LeftHandRotationSpring; }
set {
m_LeftHandRotationSpring = value;
if (m_LeftHandRotationSpring != null) { m_LeftHandRotationSpring.Initialize(true, true); }
}
}
public Spring RightHandPositionSpring
{
get { return m_RightHandPositionSpring; }
set
{
m_RightHandPositionSpring = value;
if (m_RightHandPositionSpring != null) { m_RightHandPositionSpring.Initialize(false, true); }
}
}
public Spring RightHandRotationSpring
{
get { return m_RightHandPositionSpring; }
set
{
m_RightHandPositionSpring = value;
if (m_RightHandPositionSpring != null) { m_RightHandPositionSpring.Initialize(true, true); }
}
}
private GameObject m_GameObject;
private Transform m_Transform;
private Animator m_Animator;
private UltimateCharacterLocomotion m_CharacterLocomotion;
private ILookSource m_LookSource;
private InventoryBase m_Inventory;
private RaycastHit m_RaycastHit;
private Transform m_Head;
private Transform m_Hips;
private Transform m_LeftFoot;
private Transform m_RightFoot;
private Transform m_LeftToes;
private Transform m_RightToes;
private Transform m_LeftLowerLeg;
private Transform m_RightLowerLeg;
private Transform m_LeftHand;
private Transform m_RightHand;
private Transform m_LeftUpperArm;
private Transform m_RightUpperArm;
private bool m_ImmediatePosition;
private bool m_Aiming;
private bool m_ItemInUse;
private float m_LookAtBodyIKWeight;
private float m_LookAtHeadIKWeight;
private float m_LookAtEyesIKWeight;
private Vector3 m_HipsPosition;
private float m_HipsOffset;
private float[] m_FootOffset = new float[2];
private float[] m_FootIKWeight = new float[2];
private float[] m_MaxLegLength = new float[2];
private float[] m_RaycastDistance = new float[2];
private float[] m_GroundDistance = new float[2];
private Vector3[] m_GroundPoint = new Vector3[2];
private Vector3[] m_GroundNormal = new Vector3[2];
private float[] m_HandRotationIKWeight = new float[2];
private float[] m_HandPositionIKWeight = new float[2];
private int[] m_HandSlotID = new int[2];
private Transform m_DominantHand;
private Transform m_NonDominantHand;
private float m_DominantUpperArmWeight;
private Transform m_DominantUpperArm;
private Vector3 m_DominantHandPosition;
private Vector3 m_NonDominantHandOffset;
private Vector3 m_NonDominantHandPosition;
private int m_DominantSlotID;
private Vector3 m_HandOffset;
private bool m_Unequipping;
private Transform m_LeftHandItemIKTarget;
private Transform m_RightHandItemIKTarget;
private Transform m_LeftHandItemIKHintTarget;
private Transform m_RightHandItemIKHintTarget;
private Transform[] m_IKTarget;
private Transform[] m_AbilityIKTarget;
private Transform[] m_InterpolationTarget;
private float[] m_StartInterpolation;
private float[] m_InterpolationDuration;
private bool m_InterpolateIKTargets;
private bool m_RequireSecondHandPositioning;
private Vector3 m_PrevLeftHandPositionSpringValue;
private Vector3 m_PrevLeftHandPositionSpringVelocity;
private Vector3 m_PrevLeftHandRotationSpringValue;
private Vector3 m_PrevLeftHandRotationSpringVelocity;
private Vector3 m_PrevRightHandPositionSpringValue;
private Vector3 m_PrevRightHandPositionSpringVelocity;
private Vector3 m_PrevRightHandRotationSpringValue;
private Vector3 m_PrevRightHandRotationSpringVelocity;
private bool m_Enable;
/// <summary>
/// Initialize the default values.
/// </summary>
protected override void Awake()
{
base.Awake();
m_GameObject = gameObject;
m_Transform = transform;
m_Animator = m_GameObject.GetCachedComponent<Animator>();
// Assign the humanoid limbs. If the character is not a humanoid then the component will stay disabled because Unity's IK system
// only works with humanoids.
if (!m_Animator.isHuman) {
Debug.LogError("Error: The CharacterIK component only works with humanoid models.");
m_Enable = enabled = false;
return;
}
m_Head = m_Animator.GetBoneTransform(HumanBodyBones.Head);
if (m_Head == null) {
Debug.LogError("Error: The Head bone is not assigned to the character " + m_GameObject.name + ".");
m_Enable = enabled = false;
return;
}
m_Hips = m_Animator.GetBoneTransform(HumanBodyBones.Hips);
m_LeftFoot = m_Animator.GetBoneTransform(HumanBodyBones.LeftFoot);
m_RightFoot = m_Animator.GetBoneTransform(HumanBodyBones.RightFoot);
if (m_LeftFoot == null || m_RightFoot == null) {
Debug.LogError("Error: The Left or Right foot bone is not assigned to the character " + m_GameObject.name + ".");
m_Enable = enabled = false;
return;
}
m_LeftToes = m_Animator.GetBoneTransform(HumanBodyBones.LeftToes);
m_RightToes = m_Animator.GetBoneTransform(HumanBodyBones.RightToes);
m_LeftLowerLeg = m_Animator.GetBoneTransform(HumanBodyBones.LeftLowerLeg);
m_RightLowerLeg = m_Animator.GetBoneTransform(HumanBodyBones.RightLowerLeg);
m_LeftHand = m_Animator.GetBoneTransform(HumanBodyBones.LeftHand);
m_RightHand = m_Animator.GetBoneTransform(HumanBodyBones.RightHand);
if (m_LeftHand == null || m_RightHand == null) {
Debug.LogError("Error: The Left or Right hand bone is not assigned to the character " + m_GameObject.name + ".");
m_Enable = enabled = false;
return;
}
m_LeftUpperArm = m_Animator.GetBoneTransform(HumanBodyBones.LeftUpperArm);
m_RightUpperArm = m_Animator.GetBoneTransform(HumanBodyBones.RightUpperArm);
m_CharacterLocomotion = m_GameObject.GetCachedComponent<UltimateCharacterLocomotion>();
// The limbs should snap into position at the start.
m_ImmediatePosition = true;
// Perform measurements during initialization while in a T-Pose so they can be compared against during the IK pass.
for (int i = 0; i < 2; ++i) {
var foot = i == 0 ? m_LeftFoot : m_RightFoot;
m_FootOffset[i] = m_Transform.InverseTransformPoint(foot.position).y - m_FootOffsetAdjustment;
m_MaxLegLength[i] = m_Transform.InverseTransformPoint(i == 0 ? m_LeftLowerLeg.position : m_RightLowerLeg.position).y - m_FootOffsetAdjustment;
}
m_HipsPosition = m_Transform.InverseTransformPoint(m_Hips.position);
// The slot IDs can be populated programmatically by finding a reference to the ItemSlot component.
var itemSlot = m_LeftHand.GetComponentInChildren<ItemSlot>();
if (itemSlot != null) {
m_HandSlotID[0] = itemSlot.ID;
}
itemSlot = m_RightHand.GetComponentInChildren<ItemSlot>();
if (itemSlot != null) {
m_HandSlotID[1] = itemSlot.ID;
}
// Initialize the target ik arrays.
var count = (int)IKGoal.Last;
m_IKTarget = new Transform[count];
m_AbilityIKTarget = new Transform[count];
m_InterpolationTarget = new Transform[count];
m_StartInterpolation = new float[count];
m_InterpolationDuration = new float[count];
for (int i = 0; i < m_StartInterpolation.Length; ++i) {
m_StartInterpolation[i] = -1;
}
m_LeftHandPositionSpring.Initialize(false, true);
m_LeftHandRotationSpring.Initialize(true, true);
m_RightHandPositionSpring.Initialize(false, true);
m_RightHandRotationSpring.Initialize(true, true);
EventHandler.RegisterEvent<ILookSource>(m_GameObject, "OnCharacterAttachLookSource", OnAttachLookSource);
EventHandler.RegisterEvent<Item, int>(m_GameObject, "OnInventoryEquipItem", OnEquipItem);
EventHandler.RegisterEvent<Item, int>(m_GameObject, "OnInventoryUnequipItem", OnUnequipItem);
EventHandler.RegisterEvent<Item, int>(m_GameObject, "OnInventoryRemoveItem", OnUnequipItem);
EventHandler.RegisterEvent<bool>(m_GameObject, "OnAimAbilityAim", OnAim);
EventHandler.RegisterEvent<bool, Abilities.Items.Use>(m_GameObject, "OnUseAbilityStart", OnUseStart);
EventHandler.RegisterEvent<int, Vector3, Vector3, bool>(m_GameObject, "OnAddSecondaryForce", OnAddForce);
EventHandler.RegisterEvent(m_GameObject, "OnAnimatorWillSnap", ImmediatePosition);
EventHandler.RegisterEvent(m_GameObject, "OnAnimatorSnapped", AnimatorSnapped);
EventHandler.RegisterEvent<Vector3, Vector3, GameObject>(m_GameObject, "OnDeath", OnDeath);
EventHandler.RegisterEvent(m_GameObject, "OnRespawn", OnRespawn);
m_Enable = enabled;
// Disable the component until the LookSource has been attached.
enabled = false;
}
#if UNITY_EDITOR
/// <summary>
/// Implement start so the enable/disable toggle appears up in the inspector.
/// </summary>
private void Start() { }
#endif
/// <summary>
/// A new ILookSource object has been attached to the character.
/// </summary>
/// <param name="lookSource">The ILookSource object attached to the character.</param>
private void OnAttachLookSource(ILookSource lookSource)
{
var prevEnabled = enabled;
m_LookSource = lookSource;
enabled = m_Enable && m_LookSource != null;
// If the component is enabled update the animator so OnAnimatorIK will run.
if (enabled && !prevEnabled) {
m_Animator.Update(0);
}
}
/// <summary>
/// Specifies the location of the left or right hand IK target and IK hint target.
/// </summary>
/// <param name="itemTransform">The transform of the item.</param>
/// <param name="itemHand">The hand that the item is parented to.</param>
/// <param name="nonDominantHandTarget">The target of the left or right hand. Can be null.</param>
/// <param name="nonDominantHandElbowTarget">The target of the left or right elbow. Can be null.</param>
public override void SetItemIKTargets(Transform itemTransform, Transform itemHand, Transform nonDominantHandTarget, Transform nonDominantHandElbowTarget)
{
// If the item is parented to the right hand, then the left hand should use the IK target (and visa-versa).
if (itemHand == m_RightHand) {
m_LeftHandItemIKTarget = nonDominantHandTarget;
m_LeftHandItemIKHintTarget = nonDominantHandElbowTarget;
} else {
m_RightHandItemIKTarget = nonDominantHandTarget;
m_RightHandItemIKHintTarget = nonDominantHandElbowTarget;
}
UpdateIKTargets();
}
/// <summary>
/// Specifies the target location of the limb.
/// </summary>
/// <param name="target">The target location of the limb.</param>
/// <param name="ikGoal">The limb affected by the target location.</param>
/// <param name="duration">The amount of time it takes to reach the goal.</param>
public override void SetAbilityIKTarget(Transform target, IKGoal ikGoal, float duration)
{
if (m_InterpolationTarget[(int)ikGoal] == null) {
var interpTarget = new GameObject("IK Interpolation " + ikGoal);
m_InterpolationTarget[(int)ikGoal] = interpTarget.transform;
m_InterpolationTarget[(int)ikGoal].SetParentOrigin(m_Transform);
}
m_StartInterpolation[(int)ikGoal] = Time.time;
m_InterpolationDuration[(int)ikGoal] = duration;
SetAbilityIKTarget(target, ikGoal);
}
/// <summary>
/// Specifies the target location of the limb.
/// </summary>
/// <param name="target">The target location of the limb.</param>
/// <param name="ikGoal">The limb affected by the target location.</param>
private void SetAbilityIKTarget(Transform target, IKGoal ikGoal)
{
m_AbilityIKTarget[(int)ikGoal] = target;
m_InterpolateIKTargets = true;
UpdateIKTargets();
}
/// <summary>
/// Updates the IK target references.
/// </summary>
private void UpdateIKTargets()
{
// The ability ik targets override the item ik targets.
m_IKTarget[(int)IKGoal.LeftHand] = m_StartInterpolation[(int)IKGoal.LeftHand] != -1 ? m_InterpolationTarget[(int)IKGoal.LeftHand] :
(m_AbilityIKTarget[(int)IKGoal.LeftHand] != null ? m_AbilityIKTarget[(int)IKGoal.LeftHand] : m_LeftHandItemIKTarget);
m_IKTarget[(int)IKGoal.LeftElbow] = m_StartInterpolation[(int)IKGoal.LeftElbow] != -1 ? m_InterpolationTarget[(int)IKGoal.LeftElbow] :
(m_AbilityIKTarget[(int)IKGoal.LeftElbow] != null ? m_AbilityIKTarget[(int)IKGoal.LeftElbow] : m_LeftHandItemIKHintTarget);
m_IKTarget[(int)IKGoal.RightHand] = m_StartInterpolation[(int)IKGoal.RightHand] != -1 ? m_InterpolationTarget[(int)IKGoal.RightHand] :
(m_AbilityIKTarget[(int)IKGoal.RightHand] != null ? m_AbilityIKTarget[(int)IKGoal.RightHand] : m_RightHandItemIKTarget);
m_IKTarget[(int)IKGoal.RightElbow] = m_StartInterpolation[(int)IKGoal.RightElbow] != -1 ? m_InterpolationTarget[(int)IKGoal.RightElbow] :
(m_AbilityIKTarget[(int)IKGoal.RightElbow] != null ? m_AbilityIKTarget[(int)IKGoal.RightElbow] : m_RightHandItemIKHintTarget);
for (int i = 0; i < (int)IKGoal.LeftFoot; ++i) {
m_RequireSecondHandPositioning = m_IKTarget[i] != null;
if (m_RequireSecondHandPositioning) {
break;
}
}
// The feet targets are not affected by items so they can easily be iterated.
for (int i = (int)IKGoal.LeftFoot; i < (int)IKGoal.Last; ++i) {
m_IKTarget[i] = m_StartInterpolation[i] != -1 ? m_InterpolationTarget[i] : m_AbilityIKTarget[i];
}
}
/// <summary>
/// An item has been equipped.
/// </summary>
/// <param name="item">The equipped item.</param>
/// <param name="slotID">The slot that the item now occupies.</param>
private void OnEquipItem(Item item, int slotID)
{
DetermineDominantHand();
}
/// <summary>
/// An item has been unequipped.
/// </summary>
/// <param name="item">The item that was unequipped.</param>
/// <param name="slotID">The slot that the item was unequipped from.</param>
private void OnUnequipItem(Item item, int slotID)
{
DetermineDominantHand();
}
/// <summary>
/// An item was equipepd or unequipped. Determine the new dominant hand.
/// </summary>
private void DetermineDominantHand()
{
if (m_Inventory == null) {
m_Inventory = m_GameObject.GetCachedComponent<InventoryBase>();
}
Item dominantItem = null;
for (int i = 0; i < m_Inventory.SlotCount; ++i) {
var item = m_Inventory.GetActiveItem(i);
if (item != null && item.DominantItem) {
dominantItem = item;
break;
}
}
// The hands should act independently if there are no items.
if (dominantItem == null) {
m_Unequipping = true;
// Do not reset the variables immediately - the upper arm weight first needs to interpolate back to 0 for a smooth unequip.
if (m_DominantUpperArmWeight == 0) {
m_DominantHand = null;
m_NonDominantHand = null;
m_DominantUpperArm = null;
m_DominantSlotID = -1;
}
} else {
m_Unequipping = false;
if (dominantItem.SlotID == m_HandSlotID[0]) { // Left Hand.
m_DominantHand = m_LeftHand;
m_NonDominantHand = m_RightHand;
m_DominantUpperArm = m_LeftUpperArm;
m_DominantSlotID = dominantItem.SlotID;
} else if (dominantItem.SlotID == m_HandSlotID[1]) { // Right Hand.
m_DominantHand = m_RightHand;
m_NonDominantHand = m_LeftHand;
m_DominantUpperArm = m_RightUpperArm;
m_DominantSlotID = dominantItem.SlotID;
}
}
}
/// <summary>
/// The Aim ability has started or stopped.
/// </summary>
/// <param name="start">Has the Aim ability started?</param>
/// <param name="inputStart">Was the ability started from input?</param>
private void OnAim(bool aim)
{
m_Aiming = aim;
}
/// <summary>
/// The Use ability has started or stopped using an item.
/// </summary>
/// <param name="start">Has the Use ability started?</param>
/// <param name="useAbility">The Use ability that has started or stopped.</param>
private void OnUseStart(bool start, Abilities.Items.Use useAbility)
{
if (useAbility.SlotID == -1 || useAbility.SlotID == m_DominantSlotID) {
m_ItemInUse = start;
}
}
/// <summary>
/// Adds a positional and rotational force to the ik target.
/// </summary>
/// <param name="slotID">The Slot ID that is adding the secondary force.</param>
/// <param name="positionalForce">The positional force to add.</param>
/// <param name="rotationalForce">The rotational force to add.</param>
/// <param name="globalForce">Is the force applied to the entire character?</param>
private void OnAddForce(int slotID, Vector3 positionalForce, Vector3 rotationalForce, bool globalForce)
{
if (globalForce || slotID == m_HandSlotID[0]) { // Left Hand.
m_LeftHandPositionSpring.AddForce(positionalForce);
m_LeftHandRotationSpring.AddForce(rotationalForce);
}
if (globalForce || slotID == m_HandSlotID[1]) { // Right Hand.
m_RightHandPositionSpring.AddForce(positionalForce);
m_RightHandRotationSpring.AddForce(rotationalForce);
}
}
/// <summary>
/// Updates the IK component after the animator has updated.
/// </summary>
/// <param name="fixedUpdate">Is the IK being updated within the FixedUpdate loop?</param>
public override void Move(bool fixedUpdate)
{
m_Hips.position = m_Transform.TransformPoint(m_HipsPosition);
// After the IK has finished positioning the limbs for the first time reset the immediate position. It should smoothly blend
// during runtime.
if (m_ImmediatePosition) {
m_ImmediatePosition = false;
}
}
/// <summary>
/// Update the IK position and weights.
/// </summary>
/// <param name="layerIndex">The animator layer that is affected by IK.</param>
private void OnAnimatorIK(int layerIndex)
{
m_CharacterLocomotion.EnableColliderCollisionLayer(false);
if (layerIndex == m_BaseLayerIndex) { // Base layer.
// Any target interpolations should first be updated before ik is run.
UpdateTargetInterpolations();
// Position the legs to stand on the ground.
PositionLowerBody();
// The upper body should look in the direction of the LookSource.
LookAtTarget();
} else if (layerIndex == m_UpperBodyLayerIndex) { // Upper body.
// If the character is aiming the hands should be rotated towards the target.
RotateHands();
// The upper arms should look in the direction of the target.
RotateUpperArms();
// If the character is aiming the hands should be positioned towards the target.
PositionHands();
} else if (m_RequireSecondHandPositioning) { // Full body layer.
// If an IK target is set the hands need to be positioned again so they match the upper body rotation.
PositionHands();
}
m_CharacterLocomotion.EnableColliderCollisionLayer(true);
}
/// <summary>
/// Positions the lower body so the legs are always on the ground.
/// </summary>
private void PositionLowerBody()
{
var hipsOffset = m_CharacterLocomotion.ColliderSpacing;
if (m_CharacterLocomotion.Grounded && m_CharacterLocomotion.UsingVerticalCollisionDetection) {
// There are two passes for positioning the feet. The hips need to be positioned first and then the feet can be positioned.
for (int i = 0; i < 2; ++i) {
// If a foot ik target is set then the feet are positioned manually.
if (m_IKTarget[(int)(i == 0 ? IKGoal.LeftFoot : IKGoal.RightFoot)] != null) {
m_GroundDistance[i] = float.MaxValue;
continue;
}
// Fire the first raycast from the foot.
float distance;
var target = (i == 0 ? m_LeftFoot : m_RightFoot);
var lowerLeg = (i == 0 ? m_LeftLowerLeg : m_RightLowerLeg);
if (Physics.Raycast(GetFootRaycastPosition(target, lowerLeg, out distance), -m_CharacterLocomotion.Up, out m_RaycastHit,
distance + m_FootOffset[i] + m_MaxLegLength[i], m_LayerMask, QueryTriggerInteraction.Ignore) &&
m_Transform.InverseTransformPoint(m_RaycastHit.point).y < m_CharacterLocomotion.MaxStepHeight) {
m_RaycastDistance[i] = distance * m_Transform.lossyScale.y;
m_GroundDistance[i] = m_RaycastHit.distance;
m_GroundPoint[i] = m_RaycastHit.point;
m_GroundNormal[i] = m_RaycastHit.normal;
} else {
m_GroundDistance[i] = float.MaxValue;
}
// Fire the second raycast from the toe. If a closer object is hit then the toe raycast results should be used. This prevent the toe from clipping objects
// if the object isn't at the same height as the foot.
target = (i == 0 ? m_LeftToes : m_RightToes);
if (target != null && Physics.Raycast(GetFootRaycastPosition(target, lowerLeg, out distance), -m_CharacterLocomotion.Up, out m_RaycastHit,
distance + m_FootOffset[i] + m_MaxLegLength[i], m_LayerMask, QueryTriggerInteraction.Ignore) &&
m_Transform.InverseTransformPoint(m_RaycastHit.point).y < m_CharacterLocomotion.MaxStepHeight) {
// In addition to checking the distance also ensure the normal is the same as the up direction as the character. This will prevent the toes from
// positioning the IK while on a slope.
if (m_RaycastHit.distance + m_CharacterLocomotion.ColliderSpacing < m_GroundDistance[i] && m_RaycastHit.normal == m_CharacterLocomotion.Up) {
m_RaycastDistance[i] = distance * m_Transform.lossyScale.y;
m_GroundDistance[i] = m_RaycastHit.distance;
m_GroundPoint[i] = m_RaycastHit.point;
m_GroundNormal[i] = m_RaycastHit.normal;
}
}
if (m_GroundDistance[i] != float.MaxValue) {
// If the foot is at the same relative height then the hip offset should be set. This is most useful when the character is standing on uneven ground.
// As an example, imagine that the character is standing on a set of stairs. The stairs have two sets of colliders: one collider which covers each step
// and another plane collider at the same slope as the stairs. The characters collider is going to be resting on the plane collider while standing on the
// stairs and the IK system will be trying to ensure the feet are resting on the stairs collider. In some cases the plane collider may be relatively far
// above the stair collider so the hip needs to be moved down to allow the characters foot to hit the stair collider.
float offset;
var foot = (i == 0 ? m_LeftFoot : m_RightFoot);
if ((offset = m_GroundDistance[i] - m_RaycastDistance[i] - m_Transform.InverseTransformPoint(foot.position).y) > hipsOffset) {
hipsOffset = offset;
}
}
}
}
// Smoothly position the hips.
m_HipsOffset = m_ImmediatePosition ? hipsOffset : Mathf.Lerp(m_HipsOffset, hipsOffset, m_HipsPositionAdjustmentSpeed * Time.deltaTime);
m_HipsPosition = m_Transform.InverseTransformPoint(m_Hips.position);
m_HipsPosition.y -= m_HipsOffset;
// Move the feet into the correct position/rotation.
for (int i = 0; i < 2; ++i) {
var ikGoal = (i == 0 ? AvatarIKGoal.LeftFoot : AvatarIKGoal.RightFoot);
var position = m_Animator.GetIKPosition(ikGoal);
var rotation = m_Animator.GetIKRotation(ikGoal);
var targetWeight = 0f;
var adjustmentSpeed = m_FootWeightInactiveAdjustmentSpeed;
Transform target;
// If an IK target is specified then the target should be used.
if ((target = m_IKTarget[(int)(i == 0 ? IKGoal.LeftFoot : IKGoal.RightFoot)]) != null) {
position = target.position;
rotation = target.rotation;
targetWeight = 1f;
adjustmentSpeed = m_FootWeightActiveAdjustmentSpeed;
} else {
// Determine the position and rotation of the foot if on the ground.
if (m_CharacterLocomotion.Grounded) {
// IK should only be used if the foot position would be underneath the ground position.
if (m_GroundDistance[i] != float.MaxValue && m_GroundDistance[i] > 0 && m_Transform.InverseTransformDirection(position - m_GroundPoint[i]).y - m_FootOffset[i] - m_HipsOffset < 0) {
var localFootPosition = m_Transform.InverseTransformPoint(position);
localFootPosition.y = m_Transform.InverseTransformPoint(m_GroundPoint[i]).y;
position = m_Transform.TransformPoint(localFootPosition) + m_CharacterLocomotion.Up * (m_FootOffset[i] + m_HipsOffset);
rotation = Quaternion.LookRotation(Vector3.Cross(m_GroundNormal[i], rotation * -Vector3.right), m_CharacterLocomotion.Up);
targetWeight = 1f;
adjustmentSpeed = m_FootWeightActiveAdjustmentSpeed;
}
}
}
if (adjustmentSpeed == 0) {
m_FootIKWeight[i] = 0;
} else {
m_FootIKWeight[i] = Mathf.Clamp01(m_ImmediatePosition ? targetWeight : Mathf.MoveTowards(m_FootIKWeight[i], targetWeight, adjustmentSpeed * Time.deltaTime));
}
// Other objects have the chance of modifying the final position and rotation value.
if (m_OnUpdateIKPosition != null) {
position = m_OnUpdateIKPosition(i == 0 ? IKGoal.LeftFoot : IKGoal.RightFoot, position, rotation);
}
if (m_OnUpdateIKRotation != null) {
rotation = m_OnUpdateIKRotation(i == 0 ? IKGoal.LeftFoot : IKGoal.RightFoot, rotation, position);
}
// Apply the IK position and rotation.
m_Animator.SetIKPosition(ikGoal, position);
m_Animator.SetIKRotation(ikGoal, rotation);
m_Animator.SetIKPositionWeight(ikGoal, m_FootIKWeight[i]);
m_Animator.SetIKRotationWeight(ikGoal, m_FootIKWeight[i]);
}
// The knees can be positioned manually.
if (m_IKTarget[(int)IKGoal.LeftKnee] != null) {
m_Animator.SetIKHintPosition(AvatarIKHint.LeftKnee, m_IKTarget[(int)IKGoal.LeftKnee].position);
m_Animator.SetIKHintPositionWeight(AvatarIKHint.LeftKnee, m_FootIKWeight[0]);
}
if (m_IKTarget[(int)IKGoal.RightKnee] != null) {
m_Animator.SetIKHintPosition(AvatarIKHint.RightKnee, m_IKTarget[(int)IKGoal.RightKnee].position);
m_Animator.SetIKHintPositionWeight(AvatarIKHint.RightKnee, m_FootIKWeight[1]);
}
}
/// <summary>
/// Returns the position that the raycast should start at when determining if the foot is near the ground.
/// </summary>
/// <param name="targetTransform">The Transform of the foot or toe.</param>
/// <param name="lowerLeg">The Transform of the lower leg.</param>
/// <param name="distance">The vertical distance between the hip and target Transform.</param>
/// <returns>The position that the raycast should start at when determining if the foot is near the ground.</returns>
private Vector3 GetFootRaycastPosition(Transform targetTransform, Transform lowerLeg, out float distance)
{
// The relative y position should be the same as the lower leg so the raycast can detect any objects between the lower leg position and current foot position.
var raycastPosition = m_Transform.InverseTransformPoint(targetTransform.position);
var localHipPosition = m_Transform.InverseTransformPoint(lowerLeg.position);
distance = (localHipPosition.y - raycastPosition.y);
raycastPosition.y = localHipPosition.y;
return m_Transform.TransformPoint(raycastPosition);
}
/// <summary>
/// Rotates the upper body to look at the target specified by the LookSource.
/// </summary>
private void LookAtTarget()
{
var lookDirection = m_LookSource.LookDirection(m_Head.position, false, 0, true);
// Multiply the local offset by the distance so the same relative offset will be applied for both the upper body and head.
var localOffset = m_LookAtOffset * m_LookSource.LookDirectionDistance;
localOffset.z += m_LookSource.LookDirectionDistance;
var position = MathUtility.TransformPoint(m_Head.position, Quaternion.LookRotation(lookDirection), localOffset);
m_Animator.SetLookAtPosition(position);
m_LookAtBodyIKWeight = m_ImmediatePosition ? m_LookAtBodyWeight :
Mathf.Lerp(m_LookAtBodyIKWeight, m_LookAtBodyWeight, m_LookAtAdjustmentSpeed);
m_LookAtHeadIKWeight = m_ImmediatePosition ? m_LookAtHeadWeight :
Mathf.Lerp(m_LookAtHeadIKWeight, m_LookAtHeadWeight, m_LookAtAdjustmentSpeed);
m_LookAtEyesIKWeight = m_ImmediatePosition ? m_LookAtHeadWeight :
Mathf.Lerp(m_LookAtEyesIKWeight, m_LookAtHeadWeight, m_LookAtAdjustmentSpeed);
m_Animator.SetLookAtWeight(1, m_LookAtBodyIKWeight, m_LookAtHeadIKWeight, m_LookAtEyesIKWeight, m_LookAtClampWeight);
#if UNITY_EDITOR
// Visualize the direction of the target look position.
if (m_DebugDrawLookRay) {
Debug.DrawRay(m_Animator.GetBoneTransform(HumanBodyBones.Head).position, lookDirection * m_LookSource.LookDirectionDistance, Color.green);
}
#endif
}
/// <summary>
/// Rotates the hands to look at the target specified by the LookSource.
/// </summary>
private void RotateHands()
{
var dominantHandGoal = m_DominantHand == m_RightHand ? AvatarIKGoal.RightHand : AvatarIKGoal.LeftHand;
var nonDominantHandGoal = m_DominantHand == m_RightHand ? AvatarIKGoal.LeftHand : AvatarIKGoal.RightHand;
m_NonDominantHandOffset = MathUtility.InverseTransformPoint(m_Animator.GetIKPosition(dominantHandGoal), m_Animator.GetIKRotation(dominantHandGoal), m_Animator.GetIKPosition(nonDominantHandGoal));
Transform distantHand = null;
for (int i = 0; i < 2; ++i) {
var ikGoal = (i == 0 ? AvatarIKGoal.LeftHand : AvatarIKGoal.RightHand);
var ikTarget = (i == 0 ? m_IKTarget[(int)IKGoal.LeftHand] : m_IKTarget[(int)IKGoal.RightHand]);
// If an IK target is specified for the hand then it should use the Transform for the location. This for example allows an item to specify
// the location of the non-dominant hand. The hands should also rotate towards the look direction if the character is aiming or the item
// is being used.
var targetWeight = 0f;
if (m_HandWeight > 0 && (ikTarget != null || m_Aiming || m_ItemInUse || m_CharacterLocomotion.FirstPersonPerspective || m_OnUpdateIKRotation != null)) {
targetWeight = m_HandWeight;
}
m_HandRotationIKWeight[i] = Mathf.Clamp01(m_ImmediatePosition ? targetWeight :
Mathf.MoveTowards(m_HandRotationIKWeight[i], targetWeight, m_HandAdjustmentSpeed * Time.deltaTime));
// Set the IK rotation after the weight has been set. This is done after the weight is set because the rotation should be set at any time
// the weight is greater than zero (such as when the hands are transitioning from aiming to no aiming).
var targetRotation = m_Animator.GetIKRotation(ikGoal);
if (m_HandRotationIKWeight[i] > 0) {
if (ikTarget != null) {
targetRotation = m_Transform.rotation * m_IKTarget[(int)(i == 0 ? IKGoal.LeftHand : IKGoal.RightHand)].localRotation;
} else {
// Use the distant hand so the hands are always pointing in the same direction.
if (distantHand == null) {
if (m_Transform.InverseTransformPoint(m_RightHand.position).z < m_Transform.InverseTransformPoint(m_LeftHand.position).z) {
distantHand = m_LeftHand;
} else {
distantHand = m_RightHand;
}
}
var lookDirection = (m_LookSource.LookDirection(distantHand.position, false, 0, true) + m_Transform.TransformDirection(m_LookAtOffset)).normalized;
targetRotation = Quaternion.LookRotation(lookDirection, m_CharacterLocomotion.Up) * Quaternion.Inverse(m_Transform.rotation) *
Quaternion.Euler(m_Transform.TransformDirection(i == 0 ? m_LeftHandRotationSpring.Value : m_RightHandRotationSpring.Value)) *
targetRotation;
}
}
// Other objects have the chance of modifying the final rotation value.
targetWeight = m_HandRotationIKWeight[i];
if (m_OnUpdateIKRotation != null) {
targetWeight = 1;
targetRotation = m_OnUpdateIKRotation(i == 0 ? IKGoal.LeftHand : IKGoal.RightHand, targetRotation, m_Animator.GetIKPosition(ikGoal));
}
m_Animator.SetIKRotation(ikGoal, targetRotation);
m_Animator.SetIKRotationWeight(ikGoal, targetWeight);
}
}
/// <summary>
/// Rotates the upper arms to face the target.
/// </summary>
private void RotateUpperArms()
{
var targetWeight = 0f;
if (m_DominantUpperArm != null && m_UpperArmWeight > 0 && !m_Unequipping) {
targetWeight = m_UpperArmWeight;
}
var prevUpperArmWeight = m_DominantUpperArmWeight;
m_DominantUpperArmWeight = Mathf.Clamp01(m_ImmediatePosition ? targetWeight : Mathf.MoveTowards(m_DominantUpperArmWeight, targetWeight, m_UpperArmAdjustmentSpeed * Time.deltaTime));
if (prevUpperArmWeight > 0 && m_DominantUpperArmWeight == 0) {
DetermineDominantHand();
}
if (m_DominantUpperArm != null) {
if (m_DominantUpperArmWeight > 0) {
// The dominant upper arm should rotate to face the target.
var localLookDirection = m_Transform.InverseTransformDirection(m_LookSource.LookDirection(m_DominantUpperArm.position, false, 0, true));
var lookDirection = m_Transform.InverseTransformDirection(m_Transform.forward);
lookDirection.y = localLookDirection.y;
lookDirection = m_Transform.TransformDirection(lookDirection).normalized;
// Prevent the upper arm from moving too far behind the character.
if (localLookDirection.y < 0) {
lookDirection = Vector3.Lerp(m_Transform.forward, lookDirection, 1 - Mathf.Abs(localLookDirection.y));
}
var targetRotation = Quaternion.FromToRotation(m_Transform.forward, lookDirection) * m_DominantUpperArm.rotation;
targetRotation = Quaternion.Slerp(m_DominantUpperArm.rotation, targetRotation, m_DominantUpperArmWeight);
// When the hand IK positions are set they should use the updated rotation.
var offset = Vector3.Scale(m_DominantUpperArm.InverseTransformPoint(m_DominantHand.position), m_DominantHand.lossyScale);
m_DominantHandPosition = MathUtility.TransformPoint(m_DominantUpperArm.position, targetRotation, offset);
// The non-dominant hand position is determined by the dominant hand's rotation as well as the upper arm's rotation.
if (m_HandRotationIKWeight[m_DominantHand == m_RightHand ? 0 : 1] > 0) {
m_NonDominantHandPosition = MathUtility.TransformPoint(m_DominantHandPosition, m_Animator.GetIKRotation(m_DominantHand == m_RightHand ?
AvatarIKGoal.RightHand : AvatarIKGoal.LeftHand), m_NonDominantHandOffset);
} else {
offset = Vector3.Scale(m_DominantUpperArm.InverseTransformPoint(m_NonDominantHand.position), m_NonDominantHand.lossyScale);
m_NonDominantHandPosition = MathUtility.TransformPoint(m_DominantUpperArm.position, targetRotation, offset);
}
} else if (m_DominantHand != null) {
// If the upper arm does not rotate at all then the hand positions can be determined based off of the original upper arm rotation.
m_HandOffset = Vector3.Scale(m_DominantHand.InverseTransformPoint(m_NonDominantHand.position), m_NonDominantHand.lossyScale);
}
}
}
/// <summary>
/// Updates the interpolation transform to move closer towards the target.
/// </summary>
private void UpdateTargetInterpolations()
{
if (!m_InterpolateIKTargets) {
return;
}
m_InterpolateIKTargets = false;
var updateIKTargets = false;
for (int i = 0; i < (int)IKGoal.Last; ++i) {
if (m_StartInterpolation[i] != -1) {
Vector3 ikPosition;
var ikRotation = Quaternion.identity;
// Convert the IKGoal to an AvatarIKGoal/AvatarIKHint.
if (i == (int)IKGoal.LeftHand) {
ikPosition = m_Animator.GetIKPosition(AvatarIKGoal.LeftHand);
ikRotation = m_Animator.GetIKRotation(AvatarIKGoal.LeftHand);
} else if (i == (int)IKGoal.LeftElbow) {
ikPosition = m_Animator.GetIKHintPosition(AvatarIKHint.LeftElbow);
} else if (i == (int)IKGoal.RightHand) {
ikPosition = m_Animator.GetIKPosition(AvatarIKGoal.RightHand);
ikRotation = m_Animator.GetIKRotation(AvatarIKGoal.RightHand);
} else if (i == (int)IKGoal.RightElbow) {
ikPosition = m_Animator.GetIKHintPosition(AvatarIKHint.RightElbow);
} else if (i == (int)IKGoal.LeftFoot) {
ikPosition = m_Animator.GetIKPosition(AvatarIKGoal.LeftFoot);
ikRotation = m_Animator.GetIKRotation(AvatarIKGoal.LeftFoot);
} else if (i == (int)IKGoal.LeftKnee) {
ikPosition = m_Animator.GetIKHintPosition(AvatarIKHint.LeftKnee);
} else if (i == (int)IKGoal.RightFoot) {
ikPosition = m_Animator.GetIKPosition(AvatarIKGoal.RightFoot);
ikRotation = m_Animator.GetIKRotation(AvatarIKGoal.RightFoot);
} else { // Right Knee.
ikPosition = m_Animator.GetIKHintPosition(AvatarIKHint.RightKnee);
}
// If the target is not null then the transform should interpolate towards the target. If the target is null then
// the interpolation should move back towards the original ik position.
var time = m_InterpolationDuration[i] > 0 ? Mathf.Clamp01((Time.time - m_StartInterpolation[i]) / m_InterpolationDuration[i]) : 1;
if (m_AbilityIKTarget[i] == null) {
m_InterpolationTarget[i].position = Vector3.Lerp(m_InterpolationTarget[i].position, ikPosition, time);
m_InterpolationTarget[i].rotation = Quaternion.Slerp(m_InterpolationTarget[i].rotation, ikRotation, time);
if (time == 1) {
m_StartInterpolation[i] = -1;
updateIKTargets = true;
}
} else {
m_InterpolationTarget[i].position = Vector3.Lerp(ikPosition, m_AbilityIKTarget[i].position, time);
m_InterpolationTarget[i].rotation = Quaternion.Slerp(ikRotation, m_AbilityIKTarget[i].rotation, time);
}
m_InterpolateIKTargets = true;
}
}
if (updateIKTargets) {
UpdateIKTargets();
}
}
/// <summary>
/// Position the hands to face the look direction.
/// </summary>
private void PositionHands()
{
for (int i = 0; i < 2; ++i) {
var ikGoal = (i == 0 ? AvatarIKGoal.LeftHand : AvatarIKGoal.RightHand);
var targetWeight = 0f;
var hand = (i == 0 ? m_LeftHand : m_RightHand);
var ikTarget = (i == 0 ? m_IKTarget[(int)IKGoal.LeftHand] : m_IKTarget[(int)IKGoal.RightHand]);
var hintTarget = (i == 0 ? m_IKTarget[(int)IKGoal.LeftElbow] : m_IKTarget[(int)IKGoal.RightElbow]);
var hintGoal = (i == 0 ? AvatarIKHint.LeftElbow : AvatarIKHint.RightElbow);
// If an IK target is specified for the hand then it should use the Transform for the location. This for example allows an item to specify
// the location of the non-dominant hand. The hands should also be positioned towards the look direction if the character is aiming or the item
// is being used.
if (ikTarget != null || m_Aiming || m_ItemInUse || m_CharacterLocomotion.FirstPersonPerspective || m_UpperArmWeight > 0 || m_OnUpdateIKPosition != null) {
targetWeight = m_HandWeight;
}
m_HandPositionIKWeight[i] = Mathf.Clamp01(m_ImmediatePosition ? targetWeight :
Mathf.MoveTowards(m_HandPositionIKWeight[i], targetWeight, m_HandAdjustmentSpeed * Time.deltaTime));
// Set the IK position after the weight has been set. This is done after the weight is set because the position should be set at any time
// the weight is greater than zero (such as when the hands are transitioning from aiming to no aiming).
var targetPosition = m_Animator.GetIKPosition(ikGoal);
if (m_HandPositionIKWeight[i] > 0) {
if (ikTarget != null) {
targetPosition = m_IKTarget[(int)(i == 0 ? IKGoal.LeftHand : IKGoal.RightHand)].position;
if (hintTarget != null) {
m_Animator.SetIKHintPosition(hintGoal, hintTarget.position);
}
} else {
// The RotateUpperArms method will set the dominant and nondominant hand positions if it is being used. Otherwise the offset is set of the nondominant hand.
Vector3 handPosition;
if (m_DominantUpperArmWeight > 0) {
handPosition = (hand == m_DominantHand ? m_DominantHandPosition : m_NonDominantHandPosition);
} else {
handPosition = ((hand == m_DominantHand || m_DominantHand == null) ? hand.position : m_DominantHand.TransformPoint(m_HandOffset));
}
targetPosition = handPosition + m_Transform.TransformDirection(i == 0 ? m_LeftHandPositionSpring.Value : m_RightHandPositionSpring.Value) +
m_Transform.TransformDirection(m_HandPositionOffset);
}
}
// Other objects have the chance of modifying the final position value.
targetWeight = m_HandPositionIKWeight[i];
if (m_OnUpdateIKPosition != null) {
targetWeight = 1;
targetPosition = m_OnUpdateIKPosition(i == 0 ? IKGoal.LeftHand : IKGoal.RightHand, targetPosition, m_Animator.GetIKRotation(ikGoal));
}
m_Animator.SetIKPosition(ikGoal, targetPosition);
m_Animator.SetIKPositionWeight(ikGoal, targetWeight);
m_Animator.SetIKHintPositionWeight(hintGoal, hintTarget != null ? m_HandPositionIKWeight[i] : 0);
}
}
/// <summary>
/// Immediately position the IK limbs.
/// </summary>
private void ImmediatePosition()
{
m_ImmediatePosition = true;
}
/// <summary>
/// The animator has snapped into position.
/// </summary>
private void AnimatorSnapped()
{
if (!enabled) {
return;
}
Move(true);
}
/// <summary>
/// The character has died. Disable the component.
/// </summary>
/// <param name="position">The position of the force.</param>
/// <param name="force">The amount of force which killed the character.</param>
/// <param name="attacker">The GameObject that killed the character.</param>
private void OnDeath(Vector3 position, Vector3 force, GameObject attacker)
{
m_Enable = enabled;
enabled = false;
}
/// <summary>
/// The character has respawned. Enable the component.
/// </summary>
private void OnRespawn()
{
enabled = m_Enable;
m_ImmediatePosition = true;
}
/// <summary>
/// Callback when the StateManager will change the active state on the current object.
/// </summary>
public override void StateWillChange()
{
// Remember the interal spring values so they can be restored if a new spring is applied during the state change.
m_PrevLeftHandPositionSpringValue = m_LeftHandPositionSpring.Value;
m_PrevLeftHandPositionSpringVelocity = m_LeftHandPositionSpring.Velocity;
m_PrevLeftHandRotationSpringValue = m_LeftHandRotationSpring.Value;
m_PrevLeftHandRotationSpringVelocity = m_LeftHandRotationSpring.Velocity;
m_PrevRightHandPositionSpringValue = m_RightHandPositionSpring.Value;
m_PrevRightHandPositionSpringVelocity = m_RightHandPositionSpring.Velocity;
m_PrevRightHandRotationSpringValue = m_RightHandRotationSpring.Value;
m_PrevRightHandRotationSpringVelocity = m_RightHandRotationSpring.Velocity;
}
/// <summary>
/// Callback when the StateManager has changed the active state on the current object.
/// </summary>
public override void StateChange()
{
m_LeftHandPositionSpring.Value = m_PrevLeftHandPositionSpringValue;
m_LeftHandPositionSpring.Velocity = m_PrevLeftHandPositionSpringVelocity;
m_LeftHandRotationSpring.Value = m_PrevLeftHandRotationSpringValue;
m_LeftHandRotationSpring.Velocity = m_PrevLeftHandRotationSpringVelocity;
m_RightHandPositionSpring.Value = m_PrevRightHandPositionSpringValue;
m_RightHandPositionSpring.Velocity = m_PrevRightHandPositionSpringVelocity;
m_RightHandRotationSpring.Value = m_PrevRightHandRotationSpringValue;
m_RightHandRotationSpring.Velocity = m_PrevRightHandRotationSpringVelocity;
}
/// <summary>
/// The GameObject has been destroyed.
/// </summary>
private void OnDestroy()
{
m_LeftHandPositionSpring.Destroy();
m_LeftHandRotationSpring.Destroy();
m_RightHandPositionSpring.Destroy();
m_RightHandRotationSpring.Destroy();
EventHandler.UnregisterEvent<ILookSource>(m_GameObject, "OnCharacterAttachLookSource", OnAttachLookSource);
EventHandler.UnregisterEvent<Item, int>(m_GameObject, "OnInventoryEquipItem", OnEquipItem);
EventHandler.UnregisterEvent<Item, int>(m_GameObject, "OnInventoryUnequipItem", OnUnequipItem);
EventHandler.UnregisterEvent<Item, int>(m_GameObject, "OnInventoryRemoveItem", OnUnequipItem);
EventHandler.UnregisterEvent<bool>(m_GameObject, "OnAimAbilityAim", OnAim);
EventHandler.UnregisterEvent<bool, Abilities.Items.Use>(m_GameObject, "OnUseAbilityStart", OnUseStart);
EventHandler.UnregisterEvent<int, Vector3, Vector3, bool>(m_GameObject, "OnAddSecondaryForce", OnAddForce);
EventHandler.UnregisterEvent(m_GameObject, "OnAnimatorWillSnap", ImmediatePosition);
EventHandler.UnregisterEvent(m_GameObject, "OnAnimatorSnapped", AnimatorSnapped);
EventHandler.UnregisterEvent<Vector3, Vector3, GameObject>(m_GameObject, "OnDeath", OnDeath);
EventHandler.UnregisterEvent(m_GameObject, "OnRespawn", OnRespawn);
}
}
}