Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Solve simple IK for character legs on differing ground height for Unity3D.
function LateUpdate () {
if ( collision.grounded ) {
IKOn = true;
}
else {
IKOn = false;
}
ik.solveLegIK ();
}
// Define bones.
private var rootBone : Transform;
private var pelvis : Transform;
private var leftThigh : Transform;
private var leftCalf : Transform;
private var leftFoot : Transform;
private var rightThigh : Transform;
private var rightCalf : Transform;
private var rightFoot : Transform;
private var leftThighLength : float;
private var rightThighLength : float;
private var leftCalfLength : float;
private var rightCalfLength : float;
private var leftLegLength : float;
private var rightLegLength : float;
// Define offsets.
private var animationOffset : float = 0.01; // Difference between feet height in the animation that determines a dominant foot (i.e. foot that's taking the load whilst walking).
private var ankleHeightL : float = 0.0;
private var ankleHeightR : float = 0.0;
private var rootOffset : float = 0.0;
private var targetHeight : float = 0.0;
private var prevTargetHeight : float = 0.0;
// Foot states.
private var leftDown : boolean = false;
private var rightDown : boolean = false;
private var leftCollide : boolean = false;
private var rightCollide : boolean = false;
// Components.
private var characterMotor : characterMotor;
function Awake () {
rootBone = transform.Find ( "Bip001" );
pelvis = rootBone.Find ( "Bip001 Pelvis" );
leftThigh = pelvis.Find ( "Bip001 L Thigh" );
leftCalf = leftThigh.Find ( "Bip001 L Calf" );
leftFoot = leftCalf.Find ( "Bip001 L Foot" );
rightThigh = pelvis.Find ( "Bip001 R Thigh" );
rightCalf = rightThigh.Find ( "Bip001 R Calf" );
rightFoot = rightCalf.Find ( "Bip001 R Foot" );
leftThighLength = Vector3.Distance ( leftThigh.position, leftCalf.position );
leftCalfLength = Vector3.Distance ( leftCalf.position, leftFoot.position );
leftLegLength = leftThighLength + leftCalfLength;
rightThighLength = Vector3.Distance ( rightThigh.position, rightCalf.position );
rightCalfLength = Vector3.Distance ( rightCalf.position, rightFoot.position );
rightLegLength = rightThighLength + rightCalfLength;
prevTargetHeight = rootBone.position.y;
characterMotor = transform.GetComponent ( "characterMotor" ) as characterMotor;
}
public function solveLegIK () {
if ( characterMotor.IKOn ) {
rootOffset = rootBone.position.y - transform.position.y;
ankleHeightL = leftFoot.position.y - transform.position.y;
ankleHeightR = rightFoot.position.y - transform.position.y;
// Default feet targets.
var leftGround = leftFoot.position;
var rightGround = rightFoot.position;
var originalLeftGround = leftFoot.position;
var originalRightGround = rightFoot.position;
//Debug.DrawLine(leftGround, rightGround, Color.white);
// Are we walking in the animation?
// i.e. Is there a height difference between the feet in the animation. If so, which foot - if either - should be planted on the ground?
var animationDelta = leftGround.y - rightGround.y;
if ( animationDelta < -animationOffset ) {
leftDown = true;
rightDown = false;
} else if ( animationDelta > animationOffset ) {
leftDown = false;
rightDown = true;
} else {
leftDown = true;
rightDown = true;
}
// Raycast check to see if we're through collision and set the target points accordingly.
// If we're not, then we keep the positions as defined in the animation.
var hit : RaycastHit;
var rayDistance : float = 0.0;
// If right is dominant foot, check to see if the left foot is colliding as well.
if ( ( !leftDown ) && ( rightDown ) ) {
rayDistance = leftCalfLength + ankleHeightL;
} else {
rayDistance = leftCalfLength + leftLegLength;
}
if ( rayDistance > 0 ) {
//Debug.DrawRay(leftFoot.position + Vector3(0,leftCalfLength,0), -Vector3.up * rayDistance, Color.magenta);
if ( Physics.Raycast ( leftFoot.position + Vector3 ( 0, leftCalfLength, 0 ), -Vector3.up, hit, rayDistance ) ) {
if ( hit.point.y + ankleHeightL < leftFoot.position.y ) {
//Debug.DrawRay(leftFoot.position + Vector3(0,leftCalfLength,0), -Vector3.up * hit.distance, Color.blue);
leftGround = hit.point + Vector3 ( 0, ankleHeightL, 0 );
}
}
}
// If left is dominant foot, check to see if the right foot is colliding as well.
if ( ( leftDown ) && ( !rightDown ) ) {
rayDistance = rightCalfLength + ankleHeightR;
} else {
rayDistance = rightCalfLength + rightLegLength;
}
if ( rayDistance > 0 ) {
//Debug.DrawRay(rightFoot.position + Vector3(0,leftCalfLength,0), -Vector3.up * rayDistance, Color.magenta);
if ( Physics.Raycast ( rightFoot.position + Vector3 ( 0, rightCalfLength, 0 ), -Vector3.up, hit, rayDistance ) ) {
if ( hit.point.y + ankleHeightR < rightFoot.position.y ) {
//Debug.DrawRay(rightFoot.position + Vector3(0,rightCalfLength,0), -Vector3.up * hit.distance, Color.blue);
rightGround = hit.point + Vector3( 0, ankleHeightR, 0 );
}
}
}
// Work out which legs are grounded, or if the distance to floor is such that both should be.
// Run IK on those legs.
if ( ( leftDown ) && ( !rightDown ) ) { // Left foot is down.
targetHeight = leftGround.y - ankleHeightL + rootOffset;
}
else if ( ( !leftDown ) && ( rightDown ) ) { // Right foot is down.
targetHeight = rightGround.y - ankleHeightR + rootOffset;
}
else {
// Both feet are more or less equal in the animation - both should be considered as being on the ground.
// So move down to lowest foot and do the IK from there.
if ( leftGround.y < rightGround.y ) {
targetHeight = leftGround.y - ankleHeightL + rootOffset;
}
else {
targetHeight = rightGround.y - ankleHeightR + rootOffset;
}
}
// Move the root bone - if we move the transform, it'll fall through the world.
// Quickly lerp it's movement for a smoother transition - looks very snappy otherwise.
// Currently 1/8 second.
targetHeight = Mathf.Lerp ( prevTargetHeight, targetHeight, Time.deltaTime * 8 );
var heightDiff = targetHeight - rootBone.position.y;
rootBone.position.y = targetHeight;
prevTargetHeight = rootBone.position.y;
// Fix position for unmoved foot, as we've moved the root bone down.
// Otherwise the foot tries to go to it's original point in worldspace, not it's new one.
if ( ( !leftDown ) && ( rightDown ) && ( leftGround == originalLeftGround ) ) {
leftGround.y += heightDiff;
}
if ( ( leftDown ) && ( !rightDown ) && ( rightGround == originalRightGround ) ) {
rightGround.y += heightDiff;
}
// Solve IK.
// Expects 3DS Max FBX bones that are rotated 270 on X.
IKSolve ( leftThigh, leftCalf, leftFoot, leftGround );
IKSolve ( rightThigh, rightCalf, rightFoot, rightGround );
}
else {
prevTargetHeight = rootBone.position.y;
}
}
function IKSolve ( start : Transform, mid : Transform, end : Transform, target : Vector3 ) {
var startEndAxis = Vector3.Normalize ( end.position - start.position );
var startEndDist = Vector3.Distance ( start.position, end.position );
var startTargetAxis = Vector3.Normalize ( target - start.position );
var startTargetDist = Vector3.Distance ( start.position, target );
var startMidDist = Vector3.Distance ( start.position, mid.position );
var midEndDist = Vector3.Distance ( mid.position, end.position );
var midAngle = Mathf.Acos ( ( startMidDist * startMidDist + midEndDist * midEndDist - startEndDist * startEndDist ) / ( 2 * startMidDist * midEndDist ) ) * Mathf.Rad2Deg;
if ( startTargetDist > ( startMidDist + midEndDist ) ) {
// If the target's too far away, aim to reach as far as the leg can go.
// Minus a tiny amount to account for floating point inaccuracy. It'll start throwing NaNs if you don't have this.
startTargetDist = startMidDist + midEndDist - 0.01;
}
var newMidAngle = Mathf.Acos ( ( startMidDist * startMidDist + midEndDist * midEndDist - startTargetDist * startTargetDist ) / ( 2 * startMidDist * midEndDist ) ) * Mathf.Rad2Deg;
var rotateAngle = ( newMidAngle - midAngle ) * 0.5;
// Check it's not using rubbish data, otherwise it'll throw errors.
if ( !float.IsNaN ( rotateAngle ) ) {
start.RotateAround ( start.position, start.forward, rotateAngle );
mid.RotateAround ( mid.position, mid.forward, -1 * rotateAngle * 2 );
}
}
@StephenBrooks187

This comment has been minimized.

Copy link

@StephenBrooks187 StephenBrooks187 commented Jun 28, 2017

Is there any way to see how this is supposed to be set up on a character? I cannot seem to get it working properly.

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