|
这次为大家带来的是一个超~~~~~~~~实用功能。
当角色站在斜坡或者楼梯上的时候,为了让游戏表现得更加真实,会有让脚贴合在地面上的需求,这个需求可以通过反向动力学IK(Inverse kinematics)来实现。
一 简介
1.反向动力学IK(Inverse kinematics)可以依据某些子关节的最终位置、角度来反推节点链上其他节点的合理位置,Unity中设置了Avatar的人形角色都支持IK功能。
2.使用IK功能需要在Animator视窗对应的层里勾选IK Pass,勾选后每帧会调用脚本中的OnAnimationIK()。有五个节点进行设置:左手、右手、左脚、右脚、头部。
3.本文涉及API
(1)设置Positon及其Weight(权重)
Animator.SetIKPosition(AvatarIKGoal goal, Vector3 goalPosition);
Animator.SetIKPositionWeight(AvatarIKGoal goal, float value);
(2)设置Rotation及其Weight(权重)
Animator.SetIKRotation(AvatarIKGoal goal, Quaternion goalRotation);
Animator.SetIKRotationWeight(AvatarIKGoal goal, float value);
AvatarIKGoal:需要匹配的Avatar节点。
二 目标与实现思路
1.目标
(1)Idle状态站立时脚能贴合斜面。
(2)跑步时脚往下踩时尽可能贴合斜面。
2.思路
(1)通过往脚底射线检测是否在上斜坡/楼梯。
(2)根据射线命中的面的法线设置旋转Rotation。
(3)求命中平面上离Foot节点最近的点,根据该点设置位置Position。
(4)进一步优化:通过动画曲线调节权重。
三 具体实现
在设置IK前,把人物的Collider/CharactorController配置好(在普通平地上能双脚着地)。
1.设置脚底射线
(1)打开人形骨骼,以Foot节点(一般在脚踝处)为参考点,计算胶囊体两端半球的中心位置。
(2)从上往脚底打一个胶囊体射线,探测脚将会接触的面,把需要进行IK的面的Layer设置为IKLayer。
射线示意图
(3)代码如下():
public bool UpdateFootTarget(Transform oriFootTrans, Transform footTargetTrans, out bool isOriFootOnStair)
{
//==========================胶囊体射线==========================
Vector3 oriFoot_point1 = oriFootTrans.position + oriFootTrans.up * 0.3f;//以Foot节点为参考往上0.3f
Vector3 oriFoot_point2 = oriFootTrans.position + oriFootTrans.up * 0.3f + oriFootTrans.forward*0.15f;
//画蓝色射线,模拟胶囊体射线范围
Debug.DrawLine(oriFoot_point1, oriFoot_point1 - oriFootTrans.up * 0.5f, Color.blue);
Debug.DrawLine(oriFoot_point2, oriFoot_point2 - oriFootTrans.up * 0.5f, Color.blue);
RaycastHit oriFoot_HitInfo;
bool hasHitPoint = Physics.CapsuleCast(oriFoot_point1, oriFoot_point2, 0.01f, -oriFootTrans.up, out oriFoot_HitInfo, 0.5f, LayerMask.GetMask("IKLayer"));
//...
}
2.设置IK参考点(footTarget)
(1)设置参考点的Rotation
①获取射线命中点的法线。
②根据法线设置Rotation:把Foot节点的UP方向转到与命中点的法线方向一致。
public bool UpdateFootTarget(Transform oriFootTrans, Transform footTargetTrans, out bool isOriFootOnStair)
{
//==========================设置参考点==========================
if (hasHitPoint) //如果有打中IKLayer
{
//rotation
Quaternion temQ = Quaternion.FromToRotation(oriFootTrans.up, oriFoot_HitInfo.normal);
footTargetTrans.rotation = temQ * oriFootTrans.rotation;
//...
}
else
{
isOriFootOnStair = false;
}
return hasHitPoint;
}
(2)设置参考点的Position
①根据击中点及其法线,生成面。
②通过Plane.ClosetPointOnPlane(Vector Point)求出平面上离Foot节点最近的点tempPointOnPlane,以此为参考设置footTarget的Position,其中footOffset将在面板中调节。
代码如下:
public bool UpdateFootTarget(Transform oriFootTrans, Transform footTargetTrans, out bool isOriFootOnStair)
{
//==========================设置参考点==========================
if (hasHitPoint) //如果有打中东西
{
//rotation
...
//position
var tempPlane = new Plane(oriFoot_HitInfo.normal, oriFoot_HitInfo.point); //用射线打中的点和该点的法线求脚所踩平面
var tempPointOnPlane = tempPlane.ClosestPointOnPlane(oriFootTrans.position);//求tempPlane面上离Foot节点最近的点
footTargetTrans.position = tempPointOnPlane + Vector3.up * footOffset;
Debug.DrawLine(oriFootTrans.position, tempPointOnPlane, Color.green);
Debug.DrawRay(oriFoot_HitInfo.point, oriFoot_HitInfo.normal, Color.red, 0.01f);
isOriFootOnStair = oriFoot_HitInfo.transform.CompareTag("Stair");
}
//...
}
③调节footOffset(偏移)
由于Foot节点在脚踝处,因此需要通过偏移来使脚底贴到地面,脚本中public float footOffset,在Inspctor窗口根据具体情况进行调节。
3.调节Collider/CharacterController
在上楼梯的时候CharacterController可能会把角色顶起来,造成腿伸直了也不能接触地面的情况,因此需要根据实际调节Collider/CharacterController来避免,本文直接粗暴地调节center的Y值。
调节完成后记录数据,在脚本中赋值,代码如下:
public void ChangeCenter()
{
if (characterController)
{
Vector3 tempPos = characterController.center;
if (hasFootOnStair) //如果有脚在楼梯上
{
//根据实际楼梯调节
characterController.center = new Vector3(tempPos.x, centerOnStair, tempPos.z);
}
else
{
characterController.center = new Vector3(tempPos.x, centerOnGround, tempPos.z);
}
}
}
4.通过动画曲线调节权重
如果权重一直为1,当射线一打到地面脚就会贴上去,当播放站立这类没有抬脚的动画时,没有什么大问题。
可是在跑步状态下也许会变成贴地滑行或者脚长时间被吸到地上,因此需要调节权重,牺牲一些准确度来使动画看起来尽可能的自然。
(1)添加动画状态机中 Float 类型变量:leftFootWeight和rightFootWeight,分别代表左右脚的权重。
(2)添加并调节动画曲线(参考图示操作)
①添加曲线:双击移动的动画状态>Inspector窗口>展开Curves>”+”。
②调节曲线(体力活):脚踩在地面时权重为1,其余参数根据不同动画自行调节,使动画自然。
③站立权重曲线全1即可。
(3)在脚本中,通过Animator.GetFloat(变量名)获取参数,根据变量设置权重,代码如下:
public float GetIKWeight(bool hasOriFootHitPoint, string oriFootWeight)
{
float tempWeight;
if (hasOriFootHitPoint)
{
tempWeight = animator.GetFloat(oriFootWeight);
}
else
{
tempWeight = 0;
}
return tempWeight;
}
5.设置IK
把调好的参考点Transform和获取的权重作为参数设置IK。
public void SetIK(AvatarIKGoal avatarIKGoal, Transform goalTargetTrans, float weight)
{
animator.SetIKPosition(avatarIKGoal, goalTargetTrans.position);
animator.SetIKPositionWeight(avatarIKGoal, weight);
animator.SetIKRotation(avatarIKGoal, goalTargetTrans.rotation);
animator.SetIKRotationWeight(avatarIKGoal, weight);
}
6.在OnAnimationIK()中执行以上函数,大概逻辑:
(1)更新脚对应的参考目标Transform。
(2)根据是否在楼梯(Stair)上获取权重。
(3)根据上边得出的Transfrom和权重设置IK。
(4)调节Collider/Character的中心高度。
代码如下(Transform变量都从外边拖入):
public class FootIK : MonoBehaviour
{
Animator animator;
CharacterController characterController;
public bool isIK;
[Header("IK Trans")]
public Transform leftFootTrans;
public Transform rightFootTrans;
[Header("Target Trans")]
public Transform leftFootTarget;
public Transform rightFootTarget;
[Header("hasHitPoint")]
public bool hasLeftFootHitPoint;
public bool hasRightFootHitPoint;
[Header("Is OnStair")]
public bool isLeftFootOnStair;
public bool isRightFootOnStair;
public bool hasFootOnStair;
[Header("Offset")]
public float footOffset = 0.12f; //Foot节点到地面偏移距离
public float centerOnStair = 1.1f; //Character中心高度偏移
public float centerOnGround = 1.01f;
[Header("Weight")]
public float leftFootWeight;
public float rightFootWeight;
void Start()
{
animator = GetComponent<Animator>();
characterController = GetComponent<CharacterController>();
}
private void OnAnimatorIK(int layerIndex)
{
if (!isIK && animator)
{
return;
}
//==========================UpdateFootTarget==========================
//LeftFoot
hasLeftFootHitPoint = UpdateFootTarget(leftFootTrans, leftFootTarget, out isLeftFootOnStair);
//RightFoot
hasRightFootHitPoint = UpdateFootTarget(rightFootTrans, rightFootTarget, out isRightFootOnStair);
//==========================SetIK==========================
hasFootOnStair = isLeftFootOnStair || isRightFootOnStair;
//Left
leftFootWeight = GetIKWeight(hasLeftFootHitPoint, &#34;leftFootWeight&#34;);//获取权重
SetIK(AvatarIKGoal.LeftFoot, leftFootTarget, leftFootWeight);
//Right
rightFootWeight = GetIKWeight(hasRightFootHitPoint, &#34;rightFootWeight&#34;);
SetIK(AvatarIKGoal.RightFoot, rightFootTarget, rightFootWeight);
//================改变characterController的Center,防止浮空==================
ChangeCenter();
}
//...
}
结语:本文通过Unity的IK功能,使人物在站立和跑动的时候脚能够贴合地面,并利用动画曲线进行优化,使移动的时候在贴合地面的同时看起来更加自然。
在游戏中有各种可以使用IK来提升游戏表现的地方(例如攀爬之类),当需求实现的难度比较高时,可以使用插件来解决,以后有机会介绍一下插件的用法。
链接:https://pan.baidu.com/s/1OpaPxZjva-XvInQ7WclLug
提取码:1zcw
————分割线————
咱们的游戏开发交流群也欢迎强势插入:869551769
有考虑参与线下游戏开发学习的,欢~~~~~~迎访问:http://www.levelpp.com/ |
本帖子中包含更多资源
您需要 登录 才可以下载或查看,没有账号?立即注册
×
|