144 lines
6.0 KiB
C#
144 lines
6.0 KiB
C#
// Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2024 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.Samples.InverseKinematics
|
|
{
|
|
/// <summary>
|
|
/// Demonstrates how to use Unity's Inverse Kinematics (IK) system to
|
|
/// adjust a character's feet according to the terrain they are moving over.
|
|
/// </summary>
|
|
///
|
|
/// <remarks>
|
|
/// <strong>Sample:</strong>
|
|
/// <see href="https://kybernetik.com.au/animancer/docs/samples/ik/uneven-ground">
|
|
/// Uneven Ground</see>
|
|
/// </remarks>
|
|
///
|
|
/// https://kybernetik.com.au/animancer/api/Animancer.Samples.InverseKinematics/RaycastFootIK
|
|
///
|
|
[AddComponentMenu(Strings.SamplesMenuPrefix + "Inverse Kinematics - Raycast Foot IK")]
|
|
[AnimancerHelpUrl(typeof(RaycastFootIK))]
|
|
public class RaycastFootIK : MonoBehaviour
|
|
{
|
|
/************************************************************************************************************************/
|
|
#if UNITY_PHYSICS_3D
|
|
/************************************************************************************************************************/
|
|
|
|
[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;
|
|
|
|
/************************************************************************************************************************/
|
|
|
|
/// <summary>Public property for a UI Toggle to enable or disable the IK.</summary>
|
|
public bool ApplyAnimatorIK
|
|
{
|
|
get => _Animancer.Layers[0].ApplyAnimatorIK;
|
|
set => _Animancer.Layers[0].ApplyAnimatorIK = value;
|
|
}
|
|
|
|
/************************************************************************************************************************/
|
|
|
|
protected virtual void Awake()
|
|
{
|
|
_LeftFoot = _Animancer.Animator.GetBoneTransform(HumanBodyBones.LeftFoot);
|
|
_RightFoot = _Animancer.Animator.GetBoneTransform(HumanBodyBones.RightFoot);
|
|
|
|
_FootWeights = new(_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.
|
|
protected virtual 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)
|
|
{
|
|
Animator animator = _Animancer.Animator;
|
|
animator.SetIKPositionWeight(goal, weight);
|
|
animator.SetIKRotationWeight(goal, weight);
|
|
|
|
if (weight == 0)
|
|
return;
|
|
|
|
// Get the local up direction of the foot.
|
|
Quaternion rotation = animator.GetIKRotation(goal);
|
|
Vector3 localUp = rotation * Vector3.up;
|
|
|
|
Vector3 position = footTransform.position;
|
|
position += localUp * _RaycastOriginY;
|
|
|
|
float distance = _RaycastOriginY - _RaycastEndY;
|
|
|
|
if (Physics.Raycast(position, -localUp, out RaycastHit 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.
|
|
Vector3 rotationAxis = Vector3.Cross(localUp, hit.normal);
|
|
float angle = Vector3.Angle(localUp, hit.normal);
|
|
rotation = Quaternion.AngleAxis(angle, rotationAxis) * 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);
|
|
}
|
|
}
|
|
|
|
/************************************************************************************************************************/
|
|
#else
|
|
/************************************************************************************************************************/
|
|
|
|
protected virtual void Awake()
|
|
{
|
|
SampleReadMe.LogMissingPhysics3DModuleError(this);
|
|
}
|
|
|
|
/************************************************************************************************************************/
|
|
#endif
|
|
/************************************************************************************************************************/
|
|
}
|
|
}
|