// Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2023 Kybernetik //
#pragma warning disable CS0414 // Field is assigned but its value is never used.
#pragma warning disable CS0649 // Field is never assigned to, and will always have its default value.
using Animancer.Units;
using UnityEngine;
namespace Animancer.Examples.InverseKinematics
{
///
/// Demonstrates how to use Unity's Inverse Kinematics (IK) system to adjust a character's feet according to the
/// terrain they are moving over.
///
/// Uneven Ground
/// https://kybernetik.com.au/animancer/api/Animancer.Examples.InverseKinematics/RaycastFootIK
///
[AddComponentMenu(Strings.ExamplesMenuPrefix + "Inverse Kinematics - Raycast Foot IK")]
[HelpURL(Strings.DocsURLs.ExampleAPIDocumentation + nameof(InverseKinematics) + "/" + nameof(RaycastFootIK))]
public sealed class RaycastFootIK : MonoBehaviour
{
/************************************************************************************************************************/
[SerializeField] private AnimancerComponent _Animancer;
[SerializeField, Meters] private float _RaycastOriginY = 0.5f;
[SerializeField, Meters] private float _RaycastEndY = -0.2f;
/************************************************************************************************************************/
private Transform _LeftFoot;
private Transform _RightFoot;
private AnimatedFloat _FootWeights;
/************************************************************************************************************************/
/// Public property for a UI Toggle to enable or disable the IK.
public bool ApplyAnimatorIK
{
get => _Animancer.Layers[0].ApplyAnimatorIK;
set => _Animancer.Layers[0].ApplyAnimatorIK = value;
}
/************************************************************************************************************************/
private void Awake()
{
_LeftFoot = _Animancer.Animator.GetBoneTransform(HumanBodyBones.LeftFoot);
_RightFoot = _Animancer.Animator.GetBoneTransform(HumanBodyBones.RightFoot);
_FootWeights = new AnimatedFloat(_Animancer, "LeftFootIK", "RightFootIK");
// Tell Unity that OnAnimatorIK needs to be called every frame.
ApplyAnimatorIK = true;
}
/************************************************************************************************************************/
// Note that due to limitations in the Playables API, Unity will always call this method with layerIndex = 0.
private void OnAnimatorIK(int layerIndex)
{
// _FootWeights[0] is the first property we specified in Awake: "LeftFootIK".
// _FootWeights[1] is the second property we specified in Awake: "RightFootIK".
UpdateFootIK(_LeftFoot, AvatarIKGoal.LeftFoot, _FootWeights[0], _Animancer.Animator.leftFeetBottomHeight);
UpdateFootIK(_RightFoot, AvatarIKGoal.RightFoot, _FootWeights[1], _Animancer.Animator.rightFeetBottomHeight);
}
/************************************************************************************************************************/
private void UpdateFootIK(Transform footTransform, AvatarIKGoal goal, float weight, float footBottomHeight)
{
var animator = _Animancer.Animator;
animator.SetIKPositionWeight(goal, weight);
animator.SetIKRotationWeight(goal, weight);
if (weight == 0)
return;
// Get the local up direction of the foot.
var rotation = animator.GetIKRotation(goal);
var localUp = rotation * Vector3.up;
var position = footTransform.position;
position += localUp * _RaycastOriginY;
var distance = _RaycastOriginY - _RaycastEndY;
if (Physics.Raycast(position, -localUp, out var hit, distance))
{
// Use the hit point as the desired position.
position = hit.point;
position += localUp * footBottomHeight;
animator.SetIKPosition(goal, position);
// Use the hit normal to calculate the desired rotation.
var rotAxis = Vector3.Cross(localUp, hit.normal);
var angle = Vector3.Angle(localUp, hit.normal);
rotation = Quaternion.AngleAxis(angle, rotAxis) * rotation;
animator.SetIKRotation(goal, rotation);
}
else// Otherwise simply stretch the leg out to the end of the ray.
{
position += localUp * (footBottomHeight - distance);
animator.SetIKPosition(goal, position);
}
}
/************************************************************************************************************************/
}
}