Unity Avatar Foot IK – Avatar Foot Placement Resolution


简介

通过Unity内部的Mecanim动画系统实现的FootIK功能,效果如图所示,左右分别为开启和关闭FootIK的效果:

效果图(一)

效果图(二)

效果图(三)

效果图(四)

初版1.0.0代码已上传至SKFramework框架PackageManager中:

SKFramework PackageManager

相关变量说明:

Avatar Foot IK

  • Enable Foot Ik:是否启用FootIK
  • Foot Ik Pass Layer Index:Animator启用IKPass对应的层级
  • Layer Mask:射线检测时所有的层级
  • Body Y Offset:身体Y坐标的偏移量
  • Body Position Lerp Speed:身体坐标插值的速度
  • Foot Position Lerp Speed:脚部坐标插值的速度
  • Raycast Distance:射线检测的最大距离
  • Raycast Origin Height:射线检测的高度

实现

Avatar FBX Import Settings

  • Animation Type: 需要Humanoid人形动画

Animation Type

  • Avatar Configuration:确保配置正确

Avatar Configuration

Animator Settings

  • Foot IK:相应的Animator State中需要开启Foot IK

Foot IK

  • IK Pass:相应的Animator Layer中需要开启IK Pass通道

IK Pass

On Animator IK

动画IK回调函数,Unity Documentation中这样介绍:OnAnimatorIK() 在即将更新其内部反向动力学系统前由动画器组件调用。该回调可用于设置反向动力学目标的位置及其各自的权重。

参数LayerIndex指的是Animator中的Layer层级的索引值。

OnAnimatorIK
如何设置IK目标的位置及其权重?需要用到Animator中的函数:

  • SetIKPosition:设置一个IK Goal的位置
  • SetIKPositionWeight:设置IK Goal的过渡权重(0表示IK之前的原始动画,1表示在goal)
  • SetIKRotation:设置一个IK Goal的旋转
  • SetIKRotationWeight:设置IK Goal的旋转权重

Calculate IK Position & Rotation

如何获取IK目标位置及旋转?可以通过在脚部加上一定单位的高度上向下进行Raycast射线检测,RaycastHit中的point碰撞点即是IK的目标位置,并且通过normal法线方向获得IK的目标旋转,代码如下所示:

#region 计算左脚IK
//左脚坐标
leftFootPosition = animator.GetBoneTransform(HumanBodyBones.LeftFoot).position;
leftFootPosition.y = transform.position.y + raycastOriginHeight;

//左脚 射线检测
leftFootRaycast = Physics.Raycast(leftFootPosition, Vector3.down, out RaycastHit hit, raycastDistance + raycastOriginHeight, layerMask);
if (leftFootRaycast)
{
    leftFootIkPosition = leftFootPosition;
    leftFootIkPosition.y = hit.point.y + bodyYOffset;
    leftFootIkRotation = Quaternion.FromToRotation(transform.up, hit.normal);
#if UNITY_EDITOR
    //射线
    Debug.DrawLine(leftFootPosition, leftFootPosition + Vector3.down * (raycastDistance + raycastOriginHeight), Color.yellow);
    //法线
    Debug.DrawLine(hit.point, hit.point + hit.normal * .5f, Color.cyan);
#endif
}
else
{
    leftFootIkPosition = Vector3.zero;
}
#endregion

Body Position

在设置IK目标位置之前,需要先计算和调整身体的高度,原因如下图所示,当射线检测到的IK Position,腿的长度达不到时,需要将身体的Y坐标减去相应距离。

身体高度通过Animator中的bodyPosition去调整:

Animator bodyPosition

代码如下所示:

#region 身体
if (leftFootRaycast && rightFootRaycast)
{
    //左脚坐标Y差值
    float leftPosYDelta = leftFootIkPosition.y - transform.position.y;
    //右脚坐标Y差值
    float rightPosYDelta = rightFootIkPosition.y - transform.position.y;
    //身体坐标Y差值取二者最小值
    float bodyPosYDelta = Mathf.Min(leftPosYDelta, rightPosYDelta);
    //目标身体坐标
    Vector3 targetBodyPosition = animator.bodyPosition + Vector3.up * bodyPosYDelta;
    //插值运算
    targetBodyPosition.y = Mathf.Lerp(lastBodyPositionY, targetBodyPosition.y, bodyPositionLerpSpeed);
    //设置身体坐标
    animator.bodyPosition = targetBodyPosition;
}
//缓存身体Y坐标
lastBodyPositionY = animator.bodyPosition.y;
#endregion

Apply IK Position & Rotation

求得目标位置和旋转并调整完身体高度后,应用目标位置和旋转即可:

#region 应用左脚IK
//权重
animator.SetIKPositionWeight(AvatarIKGoal.LeftFoot, 1f);
animator.SetIKRotationWeight(AvatarIKGoal.LeftFoot, 1f);

Vector3 targetIkPosition = animator.GetIKPosition(AvatarIKGoal.LeftFoot);
if (leftFootRaycast)
{
    //转局部坐标
    targetIkPosition = transform.InverseTransformPoint(targetIkPosition);
    Vector3 world2Local = transform.InverseTransformPoint(leftFootIkPosition);
    //插值计算
    float y = Mathf.Lerp(lastLeftFootPositionY, world2Local.y, footPositionLerpSpeed);
    targetIkPosition.y += y;
    lastLeftFootPositionY = y;
    //转全局坐标
    targetIkPosition = transform.TransformPoint(targetIkPosition);
    //当前旋转
    Quaternion currRotation = animator.GetIKRotation(AvatarIKGoal.LeftFoot);
    //目标旋转
    Quaternion nextRotation = leftFootIkRotation * currRotation;
    animator.SetIKRotation(AvatarIKGoal.LeftFoot, nextRotation);
}
animator.SetIKPosition(AvatarIKGoal.LeftFoot, targetIkPosition);
#endregion

本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
THE END
分享
二维码
< <上一篇
下一篇>>