// 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 { /// /// Demonstrates how to use Unity's Inverse Kinematics (IK) system to /// adjust a character's feet according to the terrain they are moving over. /// /// /// /// Sample: /// /// Uneven Ground /// /// /// 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; /************************************************************************************************************************/ /// 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; } /************************************************************************************************************************/ 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 /************************************************************************************************************************/ } }