Skip to content

Instantly share code, notes, and snippets.

@Orixe
Last active December 17, 2023 15:22
Show Gist options
  • Save Orixe/4ce1423800ba63b585d1e00e2f61f466 to your computer and use it in GitHub Desktop.
Save Orixe/4ce1423800ba63b585d1e00e2f61f466 to your computer and use it in GitHub Desktop.
Free Foot IK system for unity
using UnityEngine;
[RequireComponent(typeof(Animator))]
public class FootIK : MonoBehaviour
{
[Header("Main")]
[Range(0, 1)] public float Weight = 1f;
[Header("Settings")]
public float MaxStep = 0.5f;
public float FootRadius = 0.15f;
public LayerMask Ground = 1;
public float Offset = 0f;
[Header("Speed")]
public float HipsPositionSpeed = 1f;
public float FeetPositionSpeed = 2f;
public float FeetRotationSpeed = 90;
[Header("Weight")]
[Range(0, 1)] public float HipsWeight = 0.75f;
[Range(0, 1)] public float FootPositionWeight = 1f;
[Range(0, 1)] public float FootRotationWeight = 1f;
public bool ShowDebug = true;
//Private variables
Vector3 LIKPosition, RIKPosition, LNormal, RNormal;
Quaternion LIKRotation, RIKRotation, LastLeftRotation, LastRightRotation;
float LastRFootHeight, LastLFootHeight;
Animator Anim;
float Velocity;
float FalloffWeight;
float LastHeight;
Vector3 LastPosition;
bool LGrounded, RGrounded, IsGrounded;
// Initialization
void Start()
{
Anim = GetComponent<Animator>();
}
//Updating the position of each foot.
private void FixedUpdate()
{
if (Weight == 0 || !Anim) { return; }
Vector3 Speed = (LastPosition - Anim.transform.position) / Time.fixedDeltaTime;
Velocity = Mathf.Clamp(Speed.magnitude, 1, Speed.magnitude);
LastPosition = Anim.transform.position;
//Raycast to the ground to find positions
FeetSolver(HumanBodyBones.LeftFoot, ref LIKPosition, ref LNormal, ref LIKRotation, ref LGrounded); //Left foot
FeetSolver(HumanBodyBones.RightFoot, ref RIKPosition, ref RNormal, ref RIKRotation, ref RGrounded); //Right foot
//Grounding
GetGrounded();
}
private void OnAnimatorIK(int layerIndex)
{
if (Weight == 0 || !Anim) { return; }
//Pelvis height
MovePelvisHeight();
//Left foot IK
MoveIK(AvatarIKGoal.LeftFoot, LIKPosition, LNormal, LIKRotation, ref LastLFootHeight, ref LastLeftRotation);
//Right foot IK
MoveIK(AvatarIKGoal.RightFoot, RIKPosition, RNormal, RIKRotation, ref LastRFootHeight, ref LastRightRotation);
}
//Set the pelvis height.
private void MovePelvisHeight()
{
//Get height
float LeftOffset = LIKPosition.y - Anim.transform.position.y;
float RightOffset = RIKPosition.y - Anim.transform.position.y;
float TotalOffset = (LeftOffset < RightOffset) ? LeftOffset : RightOffset;
//Get hips position
Vector3 NewPosition = Anim.bodyPosition;
float NewHeight = TotalOffset * (HipsWeight * FalloffWeight);
LastHeight = Mathf.MoveTowards(LastHeight, NewHeight, HipsPositionSpeed * Time.deltaTime);
NewPosition.y += LastHeight + Offset;
//Set position
Anim.bodyPosition = NewPosition;
}
//Feet
void MoveIK(AvatarIKGoal Foot, Vector3 IKPosition, Vector3 Normal, Quaternion IKRotation, ref float LastHeight, ref Quaternion LastRotation)
{
Vector3 Position = Anim.GetIKPosition(Foot);
Quaternion Rotation = Anim.GetIKRotation(Foot);
//Position
Position = Anim.transform.InverseTransformPoint(Position);
IKPosition = Anim.transform.InverseTransformPoint(IKPosition);
LastHeight = Mathf.MoveTowards(LastHeight, IKPosition.y, FeetPositionSpeed * Time.deltaTime);
Position.y += LastHeight;
Position = Anim.transform.TransformPoint(Position);
Position += Normal * Offset;
//Rotation
Quaternion Relative = Quaternion.Inverse(IKRotation * Rotation) * Rotation;
LastRotation = Quaternion.RotateTowards(LastRotation, Quaternion.Inverse(Relative), FeetRotationSpeed * Time.deltaTime);
Rotation *= LastRotation;
//Set IK
Anim.SetIKPosition(Foot, Position);
Anim.SetIKPositionWeight(Foot, FootPositionWeight * FalloffWeight);
Anim.SetIKRotation(Foot, Rotation);
Anim.SetIKRotationWeight(Foot, FootRotationWeight * FalloffWeight);
}
void GetGrounded()
{
//Set Weight
IsGrounded = LGrounded || RGrounded;
//Fading out MainWeight when is not grounded
FalloffWeight = LerpValue(FalloffWeight, IsGrounded ? 1f : 0f, 1f, 10f, Time.fixedDeltaTime) * Weight;
}
public float LerpValue(float Current, float Desired, float IncreaseSpeed, float DecreaseSpeed, float DeltaTime)
{
if (Current == Desired) return Desired;
if (Current < Desired) return Mathf.MoveTowards(Current, Desired, (IncreaseSpeed * Velocity) * DeltaTime);
else return Mathf.MoveTowards(Current, Desired, (DecreaseSpeed * Velocity) * DeltaTime);
}
//Feet solver
private void FeetSolver(HumanBodyBones Foot, ref Vector3 IKPosition, ref Vector3 Normal, ref Quaternion IKRotation, ref bool Grounded)
{
Vector3 Position = Anim.GetBoneTransform(Foot).position;
Position.y = Anim.transform.position.y + MaxStep;
//Raycast section
RaycastHit Hit;
//Add offset
Position -= Normal * Offset;
float FeetHeight = MaxStep;
if (ShowDebug)
Debug.DrawLine(Position, Position + Vector3.down * (MaxStep * 2), Color.yellow);
if (Physics.SphereCast(Position, FootRadius, Vector3.down, out Hit, MaxStep * 2, Ground))
{
//Position (height)
FeetHeight = Anim.transform.position.y - Hit.point.y;
IKPosition = Hit.point;
//Normal (Slope)
Normal = Hit.normal;
if (ShowDebug)
Debug.DrawRay(Hit.point, Hit.normal, Color.blue);
//Rotation (normal)
Vector3 Axis = Vector3.Cross(Vector3.up, Hit.normal);
float Angle = Vector3.Angle(Vector3.up, Hit.normal);
IKRotation = Quaternion.AngleAxis(Angle, Axis);
}
Grounded = FeetHeight < MaxStep;
if (!Grounded)
{
IKPosition.y = Anim.transform.position.y - MaxStep;
IKRotation = Quaternion.identity;
}
}
}
@Orixe
Copy link
Author

Orixe commented Apr 23, 2020

This tool works with unity built-in IK, if you use this system you must activate the IK from Animator, otherwise this code will not work.

Note: this code is also easily adaptable to other custom IK systems

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment