目录

Unity中人形角色的IKI

站立、奔跑IK

1. 接触面法线

2. 调整质心位置

3. 保持原本朝向

攀爬IK

1. 四肢贴合

2. 保持身体与攀爬面的距离

3. 适应外拐角

瞄准IK

1. 头部朝向

2. 手臂朝向

尾声


本文将尝试仅使用Untiy内置的Animator来解决常见的几种运动所需的IK。也会给出核心功能的代码实现。

Unity中人形角色的IKI

逆运动学(Inverse Kinematics,简称IK)在工业机器人领域是指通过末端执行器的位姿来求解相应关节变量的过程。游戏开发中也存在类似需求,即根据末端肢体的位姿来调整身体其他部位的位置。值得庆幸的是,Unity引擎已经帮我们处理了这个复杂的计算过程。

对于使用Avatar的人形角色动画,Unity内置的Animator系统支持调整5个关键部位的IK:头部、左手、右手、左脚和右脚。开发者只需设置好这些部位的IK位置和旋转参数,Unity就会自动计算并调整角色的骨骼运动。

PS:左脚、右脚、左手、右手的IK设置可以通过Animator.SetIKPosition等系列函数,通过AvatarIKGoal的枚举来选择部位;而头部则通过Animator.SetIKPosition等系列函数来控制。

这些函数要在OnAnimatorIK生命周期函数中调用才奏效

站立、奔跑IK

这应该是人形角色最常规的IK了,通常的站立、奔跑、行走等动画都默认是在水平地面上的。但实际游戏地形会复杂很多,我们就需要调节足部的IK来贴合不同的地面。

1. 接触面法线

首先要做的就是通过物理检测找到「落脚点」,简单的射线检测就可以做到,射线检测返回的RaycastHit参数会告诉我们接触点和接触点的法线,以此就可以来调整脚的位置与姿态。

  1. 通过animator.GetBoneTransform得到脚部骨骼的Transform,进而得到脚部骨骼position。从该位置上方一段距离开始,向下检测接触面。人形角色通常是胶囊体,所以迈步时,脚很有可能就超出了胶囊体范围,而脚本身又没有碰撞体,就容易进入碰撞体内部,这时如果只是从脚本身开始检测就会检测失败,所以从上方开始检测。

csharp

/// <summary>
/// 实现类似 pointA.axis = pointB.axis + offset 指定轴向的变化
/// </summary>
private void FoottCheck(HumanBodyBones footBone, int iKGoal_Int, Vector3 upAxis)
{var footPos = animator.GetBoneTransform(footBone).position;//足部上移一段距离后的位置作为射线起点var originPos = footPos + upAxis * upOffset;//检测时指定地面层级遮罩,一般可以忽视触发器if(Physics.Raycast(originPos, -upAxis, out hitInfo, checkRayLength, checkMask, QueryTriggerInteraction.Ignore)){//不直接将足部位置设置为检测到的hit.point//而是将hit.point在upAxis上的分量赋值给足部//相当于把足部沿upAxis方向移到hit.point等高度CalculateAxisValue(ref footPos, hitInfo.point, upAxis);//记录下调整后的足部位置iKGoalPositions[iKGoal_Int] = footPos;//记录下从upAxis到接触面法线所需的旋转iKGoalRotations[iKGoal_Int] = Quaternion.FromToRotation(upAxis, hitInfo.normal);}
}/// <summary>
/// 辅助函数,功能: pointA.axis = pointB.axis + offset
/// </summary>
private void CalculateAxisValue(ref Vector3 pointA, Vector3 pointB, Vector3 axis, float offset = 0)
{pointA += axis * (Vector3.Dot(pointB - pointA, axis) + offset);
}

2. 调整质心位置

光调整脚的位置是不够的,因为这样容易出现一只脚够得着平面,但另一只则 「虚空接触」 的情况(左侧就是没调整的,右侧就是调整后的):

这也是上一步中,要用别扭的方法移动脚部的原因。这样我们就能算得哪只脚触碰接触面所需要移动的距离较大了,我们就将较大的这个偏移量同步应用到animator.bodyPositon就可以了!

csharp

/// <summary>
/// 根据足部在当前up轴的偏差来调整质心位置(身体升降)
/// 为了让奔跑连贯,奔跑时不建议开启,仅静止时开启
/// </summary>
/// <param name="isIdle">是否是闲置状态</param>
private void MoveCentroidPosition(bool isIdle)
{if (isIdle && iKGoalPositions[leftFoot_Idx] != Vector3.zero && iKGoalPositions[rightFoot_Idx] != Vector3.zero && lastCentriodPosInUpAxis != 0) //非闲置时、未获取正确信息时不做调整{var animTransform = animator.transform;//取离躯体最远的脚(更需要贴近地面的脚)与身体的差距作为偏移值var leftOffset = Vector3.Dot(animTransform.up, iKGoalPositions[0] - animTransform.position);var rightOffset = Vector3.Dot(animTransform.up, iKGoalPositions[1] - animTransform.position);finalCentroidOffset = leftOffset < rightOffset ? leftOffset : rightOffset;//在指定方向上线性逼近Vector3 newCentroidPos = animator.bodyPosition + animTransform.up * finalCentroidOffset;float newCentroidPosInUpAxis = Vector3.Dot(animTransform.up, newCentroidPos);//用插值的方式改变质心位置,更自然newCentroidPosInUpAxis = Mathf.Lerp(lastCentriodPosInUpAxis, newCentroidPosInUpAxis, centroidMoveSpeed);CalculateAxisValue(ref newCentroidPos, Vector3.zero, animTransform.up, newCentroidPosInUpAxis);//应用调整后的位置animator.bodyPosition = newCentroidPos; }//将当前质心位置记录为「上次质心在upAxis上的位置」,方便下一帧判断lastCentriodPosInUpAxis = Vector3.Dot(animTransform.up, animator.bodyPosition);
}

你可能注意到了,质心调整并不一定要时时开启,否则像快速上楼梯等斜面变化频繁的情况,可能会剧烈抖动

3. 保持原本朝向

我们希望足部在调整后仍能保持动画原本的偏航角,(也就是说该外八的还是外八,内八的还是内八;而如果像这么做的话,就会导致脚笔直朝向玩家前方:

csharp

iKGoalRot = iKGoalRotations[leftFoot_Int] * animator.transform.rotation;
animator.SetIKRotation(AvatarIKGoal.LeftFoot, iKGoalRot);

显然,问题就出在我们是基于animator.transform.rotation来调整的。所以我们应该在真正调整朝向前,先记录脚部IK原本的朝向,再在记录下的这个朝向上应用步骤1中得到的「贴合地面的旋转」。

csharp

private void MoveFeetToIKPos(AvatarIKGoal iKGoal, int iKGoal_Int)
{//真正调整前,先记录原本IK的位置和朝向var animTransform = animator.transform;var iKGoalPos = animator.GetIKPosition(iKGoal);var iKGoalRot = animator.GetIKRotation(iKGoal);//如果FixedUpdate中没有检测到信息就不更新IKif(iKGoalPositions[iKGoal_Int] != Vector3.zero) {//将当前IKGoal位置和目标IKGoal位置都转到当前坐标系下iKGoalPos = animTransform.InverseTransformPoint(iKGoalPos);iKGoalPositions[iKGoal_Int] = animTransform.InverseTransformPoint(iKGoalPositions[iKGoal_Int]);//从当前坐标的y方向线性逼近目标IKGoal,同样插值逼近显得自然var upVar = Mathf.Lerp(lastPosInUpAxis[iKGoal_Int], iKGoalPositions[iKGoal_Int].y, footIKMoveSpeed);iKGoalPos.y += upVar;lastPosInUpAxis[iKGoal_Int] = upVar;//将调整后的位置转回世界坐标空间(因为SetIKPosition是根据世界坐标的)iKGoalPos = animTransform.TransformPoint(iKGoalPos);//四元数旋转:原本足部旋转的基础上 + 地面贴合旋转iKGoalRot = iKGoalRotations[iKGoal_Int] * iKGoalRot;animator.SetIKRotation(iKGoal, iKGoalRot);}animator.SetIKPosition(iKGoal, iKGoalPos);//清空信息,以待下次FixedUpdate提供信息iKGoalPositions[iKGoal_Int] = Vector3.zero;
}

攀爬IK

通常人形动画的攀爬要调整的是四肢的位置,使其贴合墙面。

攀爬IK的设置方式其实和你所实现攀爬系统的逻辑密切相关,我就暂定现在我们已经实现好了一个攀爬系统,它能时时获取攀爬法线

1. 四肢贴合

最简单的环境,实现思路与足部贴合地面类似,获取四肢IKGaol的位置,然后沿角色后方远离一段距离作为射线检测的起点,往角色的前方进行检测。如下图所示(红色端为射线起点)

csharp

/// <summary>
/// 通过射线检测调整攀爬时四肢IK位置、旋转,并将结果存储在数组中
/// </summary>
private void LimbsClimb_Solver(int iKGoal_Int, LayerMask climbMask)
{var animTransform = animator.transform;//这里假设在攀爬系统的作用下,角色总能面朝攀爬面,故用forwardvar origin = limbsPositions[iKGoal_Int] - animTransform.forward * limbOffset;if(Physics.Raycast(origin, animTransform.forward, out hitInfo, climbRayLength, climbMask, QueryTriggerInteraction.Ignore)){iKGoalPositions[iKGoal_Int] = hitInfo.point;iKGoalRotations[iKGoal_Int] = Quaternion.FromToRotation(animTransform.forward, -hitInfo.normal);return;}
}

「远离一段距离」还有其它好处,比如贴合这种上沿或者内拐角

2. 保持身体与攀爬面的距离

让角色的身体与墙面保持一定距离,可以让动画看起来更顺眼。因为这位置只和墙面有关,所以调整起来也很简单(需要用到攀爬法线climbNormal):

csharp

/// <summary>
/// 调整身体离墙的距离
/// </summary>
private void AdjustBodyPos(Vector3 climbNormal, LayerMask climbMask)
{if(Physics.Raycast(animator.bodyPosition, -climbNormal, out hitInfo, climbCornerRayLength, climbMask, QueryTriggerInteraction.Ignore)){animator.bodyPosition = hitInfo.point + climbNormal * climbDisWithWall;}
}

3. 适应外拐角

有一种比较麻烦的地方是「外拐角」,步骤1中的前向射线检测会扑空。我们需要从两侧向中间检测

具体思路就是四肢向内侧方向进行检测。而且要多段检测,也就是将射线起点向前移动几次,能更好贴合V形角(就算没有刻意的V形墙面,当角色爬过外墙角时也会变成面向V形角的情况)

我们对步骤1中的函数进行补充:

csharp

/// <summary>
/// 通过射线检测调整攀爬时四肢IK位置、旋转,并将结果存储在数组中
/// </summary>
private void LimbsClimb_Solver(int iKGoal_Int, LayerMask climbMask)
{var animTransform = animator.transform;//这里假设在攀爬系统的作用下,角色总能面朝攀爬面,故用forwardvar origin = limbsPositions[iKGoal_Int] - animTransform.forward * limbOffset;if(Physics.Raycast(origin, animTransform.forward, out hitInfo, climbRayLength, climbMask, QueryTriggerInteraction.Ignore)){iKGoalPositions[iKGoal_Int] = hitInfo.point;iKGoalRotations[iKGoal_Int] = Quaternion.FromToRotation(animTransform.forward, -hitInfo.normal);return;}//——————————————————新增部分————————————————else //当前向射线检测不到时,大概率进入了外拐角{//射线起点回到原本位置origin += animTransform.forward * limbOffset;//根据肢体所属左右来设置检测方向var dir = (iKGoal_Int & 1) == 0 ? animTransform.right : -animTransform.right;//向中间进行多次射线检测for(int i = 0; i < cornerRayCount; ++i){if(Physics.Raycast(origin, dir, out hitInfo, climbCornerRayLength, climbMask, QueryTriggerInteraction.Ignore)){iKGoalPositions[iKGoal_Int] = hitInfo.point;iKGoalRotations[iKGoal_Int] = Quaternion.FromToRotation(animTransform.forward, -hitInfo.normal);return;}//如果这次没检测到,就将起点前移origin += cornerRayGap * animTransform.forward;}}
}

瞄准IK

第三人称射击游戏的瞄准,需要让玩家的头能朝向瞄准的地方,玩家拿枪的手也指向瞄准的地方。

1. 头部朝向

头部的处理,我倒是比较简单。因为我的角色会转身,所以头部只需要调整俯仰角就可以了。而头部朝向不一定要百分百朝着瞄准点,看着像个样子就差不多,所以我的选择是——看向手里武器

csharp

public void HeadLookAt(Vector3 weaponPos, float weight)
{animator.SetLookAtPosition(weaponPos);animator.SetLookAtWeight(weight);
}

2. 手臂朝向

调整手臂朝向的一大难点是保持手部姿势,直接设置朝向容易破坏持械姿势。

我的想法是:让双手IK的上下活动限制在一个球面上,这样一来,无论双臂朝向何方手臂伸展的距离都不会变化,这样就能保证动画的姿势维持。

至于这个球心位置,我是简单地选择角色胸骨骼位置,效果还行,动作变形程度不会很大(也可能是因为角色拿着手枪的原因)

csharp

public void BodyLookAt(Vector3 pos)
{//奔跑时胸骨骼会上下移动,瞄准方向会剧烈变化,选bodyPosition来算方向更稳定Vector3 handIKPos, dir = (pos - animator.bodyPosition).normalized;Vector3 chestPos = animator.GetBoneTransform(HumanBodyBones.Chest).position;//双手IK位置调整var handIKGoal = AvatarIKGoal.LeftHand;handIKPos = animator.GetIKPosition(handIKGoal);var originDis = (chestPos - handIKPos).magnitude; //保持半径距离,圆形摆动handIKPos = chestPos + dir * originDis;//奔跑时胸骨骼可能会小幅度上下移动,让手部IK位置也做同样移动animator.SetIKPosition(handIKGoal, handIKPos + animTransform.up * animator.deltaPosition.y);animator.SetIKPositionWeight(handIKGoal, 1);var handIKGoal = AvatarIKGoal.RightHand;handIKPos = animator.GetIKPosition(handIKGoal);var originDis = (chestPos - handIKPos).magnitude; handIKPos = chestPos + dir * originDis;animator.SetIKPosition(handIKGoal, handIKPos + animTransform.up * animator.deltaPosition.y);animator.SetIKPositionWeight(handIKGoal, 1);
}

尾声

还是再次声明一下,这些调整策略都是经验之谈,一定还有更好的调整方式。而且追求更高质量的IK或更多部位IK的调整,可以使用商店插件,或者Unity包里的Animator Rigging。本文就当抛砖引玉了捏!(´▽`)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/bicheng/93378.shtml
繁体地址,请注明出处:http://hk.pswp.cn/bicheng/93378.shtml
英文地址,请注明出处:http://en.pswp.cn/bicheng/93378.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

基于wireshark的USB 全速硬件抓包工具USB Sniffer Lite的使用

1、前言 随着MCU的发展和需求的增多&#xff0c;USB已成为主流MCU的标配外设&#xff0c;但很多还是全速或低速IP&#xff0c;因此往往用不上高速抓包设备。 2、安装wireshark和拷贝抓包插件 将抓包插件拷贝到wireshark的extcap目录里&#xff0c;可参考基于wireshark的USB …

easyexcel模板导出Map数据时空值列被下一行列非空数据覆盖

场景是&#xff1a;我用模板导出数据&#xff0c;sheet数据是一个List<String,Object>集合&#xff0c;然后发现第一行的第三列应该为空&#xff0c;但是不为空&#xff0c;填上了第二行的第三列数据&#xff1b;就像按列写数据&#xff0c;碰到空值&#xff0c;下一行数…

并行Builder-输出型流程编排的新思路

如果对于框架的介绍不感兴趣的可以直接跳到Getting Started快速开始 在设计一款数据加载编排框架时&#xff0c;除了任何框架都必须具备的可靠性与稳定性之外&#xff0c;对于本次编排框架的设计&#xff0c;我们把核心目标放在高性能与易用性上。这不仅要求框架能够快速、高效…

C#WPF实战出真汁03--登录界面设计

1、登录界面设计要点简洁直观的布局 登录界面应避免复杂元素&#xff0c;突出核心功能。通常包含用户名/邮箱输入框、密码输入框、登录按钮及可选功能&#xff08;如“记住我”“忘记密码”&#xff09;。保持表单字段不超过5个&#xff0c;减少用户认知负担。清晰的视觉层次 通…

前端css学习笔记6:盒子模型

本文为个人学习总结&#xff0c;如有谬误欢迎指正。前端知识众多&#xff0c;后续将继续记录其他知识点&#xff01; 目录 前言 一、组成 ​编辑content padding border margin margin塌陷 margin合并 使用场景 标题与段落间距 卡片列表布局 二、内容溢出—overflo…

以下是对智能电梯控制系统功能及系统云端平台设计要点的详细分析,结合用户提供的梯控系统网络架构设计和系统软硬件组成,分点论述并补充关键要点:

智能电梯控制系统功能及系统云端平台设计要点一、梯控系统网络架构设计服务本地化&#xff1a;电梯门禁服务器本地化部署&#xff1a;核心服务器部署在项目本地&#xff0c;确保数据安全、运维及时性&#xff0c;减少网络依赖。需支持本地独立运行&#xff0c;避免云端故障影响…

全球电商业财一体化:让出海品牌实现“看得见的增长“

内外贸并行的数字化挑战在数字经济浪潮下&#xff0c;中国品牌呈现"双轮驱动"增长态势&#xff1a;一边深耕天猫、京东、抖音等国内主流平台&#xff0c;一边通过Amazon、Tiktok、eBay、Temu等渠道拓展全球市场。然而&#xff0c;多平台、多币种、多地区的复杂运营环…

Nacos-5--Nacos2.x版本的通信原理

Nacos 2.x引入了gRPC作为其主要的通信协议&#xff0c;取代1.x版本中的HTTP长轮询和UDP通信方式&#xff0c;显著提升了性能、实时性和稳定性。gRPC是一个高性能、开源的远程过程调用&#xff08;RPC&#xff09;框架&#xff0c;它基于HTTP/2标准设计&#xff0c;并使用Protoc…

如何以开发者的身份开发出比python更好的应用软件?

作为一名拥有多年软件架构经验的开发者,我见证了Python从实验室脚本语言成长为数字时代基础设施的完整历程。2008年我参与欧洲核子研究中心的粒子数据分析系统时,Python还是辅助工具,而今天它已成为驱动LIGO引力波探测的核心引擎——这种跃迁绝非偶然。 一、Python的巅峰应…

zynq代办事项

测试verilog按键 1.0 按键->队列->串口 1.1 按键模块ming_key包括 按下,松开,单击,双击,长按,事件 1.2 队列模块ming_fifo存储按键发出的[事件和事件戳] 1.3 顶层模块TOP 轮询 ming_fifo,将读到的事件用串口封装成数据包发给串口助手 测试zynq的M_AXI_GP0 1.0 用axi_li…

【Redis】Redis典型应用——缓存

目录 一.什么是缓存 二.使用Redis作为缓存 2.1.关系型数据库的缺点 2.2.使用Redis作为MySQL的缓存 三. 缓存更新策略:识别热点数据 3.1.定期更新 3.2.实时生成 四.缓存的使用注意事项 4.1.缓存预热(Cache preheating) 4.2.关于缓存穿透 (Cache penetration) 4.3..关…

C#控制台项目,鼠标点击后线程会暂停

C#控制台应用程序&#xff0c;点击后就会暂停运行&#xff0c;但是我想让它运行不受鼠标点击的影响。 下面是程序演示&#xff1a;class Program{static void Main(string[] args){Console.WriteLine("Hello");int index 0;while(true){Console.WriteLine($"in…

云计算-实战 OpenStack 私有云运维:服务部署、安全加固、性能优化、从服务部署到性能调优(含数据库、内核、组件优化)全流程

简介 此次围绕OpenStack 私有云平台的运维与开发展开,涵盖了从核心服务安装到深度优化的全流程实战内容。文中详细介绍了 OpenStack 各关键组件(如 Keystone、Glance、Nova、Neutron、Cinder 等)的安装部署方法,包括使用脚本快速搭建服务、创建用户、上传镜像、配置网络等…

流水的 AI,铁打的腾讯

腾讯 昨天腾讯公布了 2025 年第二季度的业绩报告。 就还是那只鹅&#xff0c;就还是那个超预期。 总营收 1845 亿&#xff0c;同比增长 15%&#xff1b;净利润 556.3 亿&#xff0c;同比增长 17%&#xff1b;经营利润 692.5 亿&#xff0c;同比增长 18%。 这里面最炸裂的&#…

再回C的进制转换--负数

概念 负数在计算机中以补码的形式保存&#xff0c;以int类型的-15为例&#xff0c;求补码先对-15取绝对值&#xff0c;然后对其按位取反(得到反码)&#xff0c;然后加1&#xff0c;就可以得到其的补码。 二进制的补码 -15 (取绝对值)–> 15 --> (十六进制表示)0x000f (按…

项目绩效域-笔记

一、项目管理绩效域 1. 价值驱动的项目管理知识体系 1&#xff09;体系构成要素 核心转变&#xff1a;从预测型生命周期&#xff08;计划驱动&#xff09;转向价值驱动体系&#xff0c;融合预测型和敏捷方法组成要素&#xff1a; 12个项目管理原则&#xff08;基础&#xff09;…

怎么判断晶振的好坏,有什么简单的办法

今天来聊聊晶振的好坏判断方法&#xff0c;3个步骤轻松搞定。外观检查&#xff1a;先看脸&#xff0c;再看脚晶振体积虽小&#xff0c;但问题往往写在“脸上”。第一步&#xff0c;用肉眼观察&#xff1a;裂痕与破损&#xff1a;晶振表面如果有明显裂纹或缺口&#xff0c;大概率…

mac下载maven并配置,以及idea配置

文章目录下载配置settingsidea配置下载 https://maven.apache.org/download.cgi 我下的3.6.3 https://archive.apache.org/dist/maven/maven-3/3.6.3/binaries/ 配置 open ~/.zprofile添加&#xff0c;根据自己安装路径修改 export MAVEN_HOME/Users/xxx/tools/apache-mave…

基于机器视觉的车道线检测与跟踪关键技术研究

摘 要 随着自动驾驶技术的迅速发展&#xff0c;车道线检测与跟踪技术在提高道路安全性和驾驶自动化水平方面发挥着至关重要的作用。本文针对基于机器视觉的车道线检测与跟踪关键技术进行了深入研究&#xff0c;旨在提升车道线检测的准确性与系统的实时响应能力。通过采用先进的…

flutter 跨平台编码库 protobuf 工具使用

1 安装依赖 dependencies:protobuf: ^3.1.0 # 或最新版本flutter pub get安装成功之后 1 lib 下创建文件夹 testProto 2 创建文件Student.proto 文件Student.proto 文件内容 syntax "proto3"; package example2;//导入其它proto文件 import "testProto/user.…