1. 从物理与动画谈游戏引擎的“真实感杠杆”
游戏引擎架构的拆解系列写到了第三篇,前两篇我们聊了资源管理和渲染管线的底层逻辑,今天把物理系统和动画系统放在一起讲。为什么要把这两个系统放一块儿?因为在实际项目里,它们从来都不是独立的——角色踩着楼梯往上走时膝盖的角度由动画系统决定,但脚掌是否踩实台阶却要问物理系统;布娃娃倒地时的躯干扭曲是物理运算结果,可它刚起跳时那一下爆发力反而源于动画关键帧。两个系统之间的协同与冲突,几乎决定了玩家对“手感”和“真实感”的感知上限。
这篇文章我会从架构设计的角度,把物理系统里的碰撞检测、刚体约束、物理材质,以及动画系统里的状态机、混合树、IK反向动力学逐层拆开,最后再聊聊两者如何协作,以及一套很实用的调试方法论。适合的对象是有一定引擎使用经验、想深入理解底层运作的引擎开发者和游戏程序员。我知道纯理论很难啃,所以会穿插一些实操中的案例和踩坑记录,尽量把每个抽象概念落到具体场景里。
提示:我在这篇里提到的引擎管线思路,大多以主流商业引擎和开源引擎的通用架构为参照,不一定对应某款具体引擎的现成实现。不同引擎的做法会有差异,但底层思路基本相同。
2. 物理系统架构拆解:三个层级的协同运转
2.1 从物理引擎选型看架构差异
物理系统最底层的东西,是物理引擎本身。商业引擎常见的选择有PhysX和Havok,开源圈子则Bullet和Jolt用得较多。选哪家不止是“谁更准”的问题,它直接决定了上层还能不能做定制。
PhysX的优势在于生态成熟,GPU加速的Rigid Body Pipeline在大量物体同屏时表现尤其好,而且Unreal和Unity都深度集成了它。Havok的稳定性口碑一直不错,但授权费用高,中小团队很难承受。Bullet胜在开源自由,跨平台性好,很多自研引擎和科研项目用它打底;Jolt是后起之秀,多线程性能设计得很干净,用在一些独立游戏里效果出奇地好。
我做过一个自研引擎项目,底层选的是Bullet。为什么不用PhysX?因为我们的目标平台包括Linux物理机和ARM嵌入式设备,PhysX在非Windows平台上的支持虽然存在,但有些边缘API的行为差异较大,调试成本太高。而Bullet的代码完全可控,碰到精度问题能直接打开源码查约束求解器的实现细节。选型这件事,本质上是在“功能完整度”和“可控性”之间做权衡,没有绝对最好,只看你愿不愿意为某些隐性成本买单。
选了引擎不代表万事大吉,物理系统在引擎架构中一般是三层结构:
- 底层是物理引擎核心,负责宽相位碰撞检测、窄相位碰撞检测、约束求解。
- 中间层是引擎封装层,把物理引擎的实体、形状、材质映射成游戏引擎里的组件。
- 上层是游戏逻辑层,处理射线检测、物理查询、触发器回调等业务相关的东西。
很多人以为物理系统就是底层引擎那一大堆算法,其实对上层开发者的日常而言,中间层的封装设计反而更关键。封装得不好,底层引擎再强悍也无处使力。
2.2 碰撞检测的“由宽到窄”决策流程
碰撞检测是物理系统里最吃性能的部分,它通常分成两个阶段:宽相位(Broad Phase)和窄相位(Narrow Phase)。宽相位的目标是快速筛掉不可能碰撞的物体对,窄相位再对真正可能存在交集的物体对做精确的几何求交。
宽相位的常用算法有Sweep and Prune(SAP)和动态包围盒树(Dynamic BVH)。SAP的思路是把所有物体的AABB按三个坐标轴排序,然后只检测在任一轴上重叠的物体对。理解起来很简单:想象一排人在一条线上排队,只有位置相邻的人可能挤到彼此,距离远的根本不用看。BVH则是把场景里的物体按空间递归划分成树状结构,查询时从上往下遍历,跳过完全不相交的子树。两种方案各有优劣,SAP在物体分布均匀的场景里表现稳定,BVH则对动态插入和删除更友好。
窄相位就具体多了,常见做法包括GJK算法(Gilbert-Johnson-Keerthi)搭配EPA(Expanding Polytope Algorithm)计算穿透深度,以及SAT分离轴定理。GJK的思路很优雅:如果两个凸体之间不存在能把它们分开的平面,那么它们的明可夫斯基差必然包含原点。实际实现时GJK通过迭代寻找支撑点来逼近这个差集,速度很快。但GJK只告诉你“是否相交”,要拿到修正穿透所需的法线和深度,还得靠EPA继续膨胀多面体。这块代码一旦写错,物体就会以肉眼可见的“抖动”卡进墙壁。
注意:多数商业引擎已经把这些算法封装得很完善,但如果你在做自研引擎,窄相位的凸体检测建议直接参考成熟开源实现。自己从零写GJK不是不行,而是调试周期远比想象中长。
2.3 约束系统:为什么布娃娃总是“抽风”
碰撞解决之后,物理系统需要处理约束。约束分很多种,常见的有固定关节、铰链关节、球形关节和滑动关节。它们的本质都是在两个刚体之间施加某种位置或旋转限制,并让约束求解器通过迭代的方式算出满足限制的冲量。
约束求解器最常用的算法是顺序冲量法(Sequential Impulse)。它的核心逻辑是:每个物理帧对约束反复迭代多次,每次迭代计算当前违反约束的误差,并施加一个冲量去修正。迭代次数越高,修正越精确,但性能开销也越大。这就是为什么物理引擎里有个“求解迭代次数”参数,默认值通常在4到10之间。调大这个值可以减轻物体“发软”的感觉,但帧时间也会跟着涨。
布娃娃系统就是典型的约束密集场景——一根脊柱连接胸腔和骨盆,左右肩关节、肘关节、膝关节各有约束,加起来几十个约束同时求解。如果迭代次数不够,或者约束参数配置不当,布娃娃就会出现经典的“抽风”现象:四肢疯狂抖动,角色在地板上弹跳不止。这背后的原因通常是约束求解器在有限迭代次数内无法收敛,误差不断累积放大。
解决“抽风”有套路。第一步,把约束的“允许偏差”(ERP)和“阻尼”(CFM)两个参数调到一个合理区间。ERP控制位置误差的修正比例,CFM则模拟柔度,让约束在误差较大时产生一定“退让”。第二步,适当增加求解迭代次数。第三步,对不稳定的约束单独开启“加速收敛”模式。实际的调参过程有点像在调整一台精密仪表,不能只看单个参数,要先判断“抖动周期”和“约束类型”再下手。
2.4 物理材质和碰撞过滤矩阵
物理材质决定了物体表面的摩擦和弹性,看起来是两个数值,但它们的算法实现大有学问。摩擦分为静摩擦系数和动摩擦系数,引擎通常使用库仑摩擦模型,用一个圆锥体来近似摩擦锥,再用迭代求解器把切向冲量限制在摩擦锥内。弹性的实现则和碰撞冲量直接相关,恢复系数越高,反弹越明显。
碰撞过滤矩阵是物理系统中另一个容易忽视的模块,但它在“性能优化”上的作用非常显著。引擎通过一个32位或64位的碰撞层掩码来决定“哪些层之间会发生碰撞”,这在底层其实就是二进制与位运算。配置得当可以避免毫无意义的窄相位计算,比如玩家子弹不需要和“玩家的脚底触发器”做碰撞检测。设计碰撞层时我建议按“角色、环境、交互物、触发器、镜头”这个粒度来分,视觉上够用,计算上也不浪费。
3. 动画系统架构拆解:从骨骼到表现
3.1 骨骼动画的数据流
动画系统的核心数据结构是骨骼层次。所谓骨骼,本质上是一个树状变换层级,每个骨骼节点保存相对于父骨骼的局部变换矩阵。网格顶点通过蒙皮权重(Skinning Weights)绑定到一个或多个骨骼节点上,动画播放时通过骨骼变换驱动顶点位置更新。
蒙皮数据链路大致是这样:动画资源采样 → 计算每个骨骼的局部姿态(Local Pose)→ 从根骨骼开始做正向层级变换,得到全局姿态(Global Pose)→ 将全局姿态乘以每个骨骼的逆绑定矩阵(Inverse Bind Matrix),得到蒙皮矩阵(Skinning Matrix)→ 顶点着色器里用蒙皮矩阵变换顶点位置和法线。
理解这条数据链路的关键点在于:逆绑定矩阵是模型绑定时的“基准姿态”,它不会因为动画播放而改变。所以引擎在初始化网格时就会把这个矩阵预处理出来,动画运行时只需要做“全局姿态 × 逆绑定矩阵”的矩阵乘法。很多新手问为什么动画更新要放在物理更新后面,答案就在这条链路的依赖顺序里:物理系统驱动物体位置 → 物体驱动根骨骼位置 → 骨骼再驱动蒙皮顶点位置。
3.2 动画状态机与过渡的架构实现
动画状态机是游戏角色动画的神经中枢。它的职责不只是“切换到某个动画”,而是要管理不同动作之间的平滑过渡。试想一个角色从“跑动”突然切到“起跳”,如果不做过渡处理,你就会看到角色在原地“滑动”了一下,非常违和。这就是状态机里过渡(Transition)存在的意义。
过渡的核心参数有三个:过渡时间、过渡起点、过渡终点。引擎实现过渡的方式是在两个动画Clip之间做混合,按照时间进度计算一个混合权重,对骨骼的局部姿态做线性插值。听起来很简单,但实际实现里有个大坑:两个动画Clip的骨骼层次如果顺序不一致,插值就会出现“骨骼错位”。所以项目规范里通常要求所有动画角色用同一套骨名和逻辑层级,否则美术导出的动画文件一进引擎就出问题。
动画状态机的另一种组织方式是Blend Space(混合空间)。它把二维平面上的横纵坐标映射成动画权重组合,最典型的应用就是“速度和转向”控制中的“八方向移动”。参数从输入系统进来,引擎在混合空间里找到附近三个采样点,按距离做三角加权混合。这种方案的好处是动作过渡是连续函数生成的,不再依赖“离散状态切换”,手感会平顺很多。
3.3 动画层的概念与Mask Blend
大型游戏里角色动画往往是分层管理的。下层负责躯干和腿部的“基础移动”,上层只负责手臂的“射击瞄准”或“持枪”,这就是动画层(Animation Layer)加层遮罩(Mask)的用法。
实现的关键在于层与层之间的混合结果如何合并。引擎一般会为每个层预先算好骨骼的“有效权重”。下层骨骼的权重较高,上层只影响“手臂”相关的骨骼。最终姿态的计算公式是逐骨骼求混合:最终Pose = 下层Pose × (1 - 层权重×骨骼权重) + 上层Pose × (层权重×骨骼权重)。这里的骨骼权重来自Mask贴图或Mask资产,美术可以精确控制每一根骨骼的参与程度。
实操中层混合最容易出错的是“脚部滑步”。因为下层跑步动画的腿部骨骼被上层遮罩“排除”了,理论上没问题,但一旦上层的动画Clip里包含了腿部关键帧数据(哪怕权重为0),某些引擎的优化器会在融合时把“零权重骨骼”也加入结果里。排查这类问题,我一般先在动画预览器里将层遮罩可视化,再关闭所有层只剩基础层,一步步还原,基本十分钟内能定位到是哪一层的Clip污染了腿部数据。
3.4 动画压缩与性能预算
动画资源占据的内存开销同样不可小觑。一个包含100根骨骼的角色,每帧生成约一百个矩阵,每个矩阵4×4总共64个浮点数,也就是256字节,一秒钟60帧就是15360字节。看起来不大,但一个角色动辄几十上百个动画Clip,总时长如果达到几分钟,内存就很可观了。所以动画压缩显得更为关键。
主流的动画压缩方案分两类:关键帧降采样和浮点量化。关键帧降采样的思路是在误差允许范围内去掉冗余关键帧,用插值替代。浮点量化则把每帧所需的旋转值压缩成16位或8位定点数。现代引擎普遍采用“基于误差的自动降采样”,它会在曲线上反复试删关键帧,只要重新插值后的误差不超过阈值就保留删除结果。压缩率通常在50%到80%之间,视觉效果上几乎察觉不到。
动画系统的性能预算也不能忽视。每帧要更新蒙皮矩阵、计算状态机、执行混合与IK。为了节省CPU,引擎会做两件事:一是动画更新与物理更新分离线程,动画数据用“只读式”批量更新;二是采用LOD系统,对距离较远的角色降低骨骼精采样率、甚至直接切换为顶点动画。前者在架构上的体现是Job System,后者是项目层的策略取舍。
4. 物理与动画的协同:谁驱动谁
4.1 动画驱动物理的典型模式
最常见的协同方式是“动画驱动物理”。角色跑步时,手臂摆动是动画系统算好的,物理系统只需要对角色施加整体的移动和旋转。这种模式效率最高,因为物理系统不需要处理几十个关节的约束,也最容易保证动画表现可控。
但这种模式也有明显缺陷:角色和环境的交互缺乏“物理反馈”。角色撞到墙壁时,动画还在按原计划播“跑步”,脚部穿模看着就别扭。所以引擎通常会在动画驱动物理的基础上加一层“运动修正”——检测到碰撞时降低移动速度或触发受击动画,用“假物理”的方式弥补表现上的缺失。
4.2 物理驱动动画的典型模式
“物理驱动动画”最常见的应用是布娃娃系统。角色死亡后,动画系统不再输出姿态数据,而是把骨骼数据交给物理引擎,让每个关节变成一个约束,然后由重力、碰撞和约束求解器共同决定角色倒地翻滚的样子。这种模式不用提前策划布娃娃倒地时的每一个关键帧,表现完全由物理仿真实时生成,因此每次死亡倒地的姿态都不一样。
布娃娃系统的核心在于“初始姿态”的处理。角色从动画状态切到布娃娃状态时,如果动画当前帧的骨骼姿态与布娃娃刚体的初始姿态不一致,就会出现瞬间“爆裂”般的跳变。经验做法是:切换前采集当前动画骨骼变换,把每个刚体的初始位置、旋转设置为匹配动画姿态,再做解算。用人话说,就是“先对齐再松手”。
布娃娃还能和“运动匹配”结合使用:角色身体被NPC推动时,布娃娃物理使躯干偏移,偏移量经过计算后反向修正动画姿态中的某些关节角度。这一套流程常用来实现“受击反应”和“攀爬悬挂”,表现力会提升一个档次。
4.3 程序化动画:物理与动画结合的未来方向
程序化动画的典型手段是IK(反向动力学)。它根据末端执行器(比如手掌)的目标位置,反向推算出各关节的旋转角度。最常见的有二骨IK(Two-Bone IK)和FABRIK。二骨IK适合手臂、腿部这类链式骨骼,通过几何法解算肘关节或膝关节的弯曲方向与角度。FABRIK更适合多段骨骼链,它通过从末端往根端反复迭代,逐步逼近目标位置。
IK在实际项目里最常见的用途是“脚部着地约束”。角色在凹凸不平的地面上行走时,脚踝要贴合地面坡度,脚掌要踩实地面。实现方法是:从角色髋部向下打一条射线,拿到地面高度和法线,把这条信息传给IK解算器,解算器再调整腿部各关节的旋转。这里有个细节:脚部要根据“地面法线”做“脚踝朝向校正”,不然角色走在斜坡上时腿会扭曲成奇怪的角度。
程序化动画还有一个重要分支是“物理预测”。角色跳跃落地前,引擎可以提前预测落点,并根据落点高度自动生成一段“屈膝缓冲”动画。这个做法在《荒野大镖客2》和《塞尔达传说:旷野之息》里都运用过,体现出很强的水平。原理并不复杂:给动画系统传入“预测坡度、落下速度、着地角度”几个参数,再触发对应“落地缓冲状态机”。但对动画师而言,这类状态机比普通状态机复杂得多,因为输入参数是连续值而不是离散状态。
5. 调试方法论:在Linux物理机上搭虚拟机跑物理与动画系统
5.1 为什么建议把开发环境搭在虚拟机里
物理引擎和动画系统是高度依赖数学库和线性代数的代码,编译和调试异常频繁。直接在主机的物理开发机上跑不是不行,但频繁切换编译配置、测试不同物理引擎版本、对比动画混合效果时,很容易把开发系统搞乱。我的习惯是在物理机上装虚拟机,隔离一个干净的开发环境。
很多做引擎开发的人会问:“虚拟机跑3D引擎是不是很卡?”这其实取决于选择。物理引擎和动画系统的调试并不需要完整的高帧率渲染,重点是拿到数值输出、调试断言、查看骨骼姿态数据。相比之下,虚拟引擎渲染压力通常小很多。我自己的经验是,Unreal或Unity项目在虚拟机里跑渲染会有明显性能损失,但纯引擎层(物理+动画)的单元测试和调试,完全可以在虚拟机里流畅进行。
5.2 Ubuntu物理机挂载虚拟机的一线操作流
以我目前主力用的Ubuntu 22.04物理机为例,最顺手的方案是KVM配合Virtual Machine Manager。为什么不选VirtualBox?KVM是内核级虚拟化方案,直接在硬件虚拟化层跑,CPU与内存开销比VirtualBox低得多。这个差别在编译引擎代码时尤其明显——KVM下编译时间基本接近真机速度,VirtualBox则慢出天际。
挂载步骤很简单,我以QEMU/KVM为例:
- 在Ubuntu物理机终端里先确认CPU支持虚拟化,执行
egrep -c '(vmx|svm)' /proc/cpuinfo,结果大于0说明支持。 - 安装KVM组件,不同发行版镜像源名字略有差异,Ubuntu上用
sudo apt install qemu-kvm libvirt-daemon-system virt-manager。 - 把当前用户加到libvirt用户组,
sudo usermod -aG libvirt $(whoami),重新登录后生效。 - 打开Virtual Machine Manager(virt-manager),新建虚拟机。关键一步是选择“导入现有磁盘镜像”或“安装操作系统”,取决于你有没有现成镜像。
- 分配CPU和内存时不要贪多。我一般给虚拟机分配物理机一半的CPU核心和三分之一的内存,剩余资源留给物理机自己的编译任务。
踩过最大的坑是“嵌套虚拟化”的问题。有时候在虚拟机里跑引擎测试时,引擎会再启动一个子虚拟机或沙箱,此时需要打开“CPU → 选项 → 复制主机CPU配置”并勾选“启用嵌套虚拟化”。这个选项藏得比较深,找不到的时候我用virsh edit直接改虚拟机XML配置。
5.3 物理与动画调试的“可视化三板斧”
在虚拟机里调试物理和动画系统,可视化是最高效的手段。物理引擎通常自带调试渲染功能,它能画出碰撞体形状、约束连线、接触点法线和约束求解过程中的误差大小。动画系统则可以通过引擎的动画预览面板逐帧查看骨骼层次和蒙皮权重。
第一板斧:绘制碰撞形状。物理引擎调试渲染里,碰撞形状通常以线框方式绘制,颜色区分不同碰撞层和类型。调试时常看的是“碰撞形状是否匹配美术模型”。模型碰撞体如果比例不对,就会出现“隔空被击中”和“脚陷地面”的诡异问题。
第二板斧:绘制接触点与法线。角色站在地面上时,接触点应该均匀分布在脚底。如果接触点偏移到脚尖或后跟,说明角色重心设置有问题。接触法线如果倾斜,说明地面碰撞体或角色的胶囊体方向没对齐。
第三板斧:骨骼姿态叠加。动画调试里打开骨骼可视化,把当前帧的骨骼姿态绘制与引擎的蒙皮网格叠加在一起,检查是否有骨骼穿透“皮肤”。如果穿透,先看蒙皮权重,再看骨骼长度是否匹配模型。
5.4 从“能用”到“好用”:虚拟机调试的三个实用技巧
在虚拟环境里调试物理与动画系统,三个技巧能显著提升效率。
技巧一:利用虚拟机的快照功能做A/B对比。物理引擎调参数时,我经常在改参数前打一个快照,改完如果效果不对就秒回滚,不用重新编译。动画状态的混合参数也一样,频繁回滚能快速收敛到最优组合。
技巧二:把编译产物和3D资源放到宿主机共享目录。虚拟机里的引擎每次跑测试都读取共享目录里的数据,避免频繁复制大文件。共享目录用NFS或virtiofs都行,后者的吞吐量更高,拷贝大资源时优势明显。
技巧三:把自动化测试作为验收关。我会在虚拟机里跑一整套物理引擎回归测试脚本,覆盖刚体堆叠、绳索约束、关节链等场景。每次代码改动后跑一遍,能挡住大部分隐藏回归。
6. 实测踩坑记录:物理引擎与动画系统协作的常见问题
6.1 物理和动画的“帧不同步”问题
物理系统和动画系统的更新频率如果没有严格对齐,就会出现一个非常明显的问题:动画播放已经到“脚落地”的帧了,物理系统却还在算角色“悬空”的状态。结果就是角色走路时脚在地面滑行,或者起跳时动画先做了离地动作但物理还没给向上的速度。
排查方式并不复杂:在引擎的调试面板里同时显示物理帧计数和动画帧计数,如果两个计数出现偏差,基本就是“时序”问题。解决方案是对动画系统做“插值对齐”——动画系统不再每帧都采样当前Clip,而是根据物理帧的时间戳往前插值若干毫秒,从而让动画的关键动作与物理仿真同步。
6.2 刚体“抖动”与“参数敏感”问题排查
物理刚体对参数极为敏感,稍微调错一个阻尼系数或碰撞余量,物体就会像果冻一样抖动。抖动分两种:一种是“高频抖动”,表现为物体挨在一起时不停地微跳,通常由碰撞检测和约束求解的迭代不足导致;另一种是“低频漂移”,表现为物体慢慢陷入地板或相互挤压穿透,通常由穿透深度恢复力度不足导致。
遇到这类问题,我的排查顺序是先看求解迭代次数,再看ERP/CFM参数,再看碰撞余量,最后检查是否误开了连续碰撞检测。连续碰撞检测会消耗大量性能,很多抖动其实不来自物理引擎本身,而来自“物体初始速度设置过大”。如果一个物体初始速度每帧冲进墙里太深,迭代根本拉不回来,看起来就是“穿模加抖动”。
6.3 动画混合的“双倍骨骼变换”警告
在一些引擎里,如果动画系统的骨骼蒙皮流程与物理引擎的约束更新流程互相调用,会出现“某根骨骼被两套系统同时更新”的警告。这个问题的本质是“技术栈打架”:动画系统的骨骼更新是每帧从头到尾执行的,物理系统如果要修正骨骼姿态,则是在某个“延迟回调”里偷偷改了骨骼矩阵。两股数据流在时间轴上交错,自然产生冲突。
解决办法是设计一个统一的“骨骼改动接口”。动画系统计算结果写入一个中性数组,物理系统也通过同一接口修改数组,最终由骨骼蒙皮模块统一读取。这个设计说起来容易,做起来需要把两套系统的“数据所有权”理顺。具体落地时建议在接口层面做断言,检查是否有两个模块同时写同一骨骼索引。
6.4 性能瓶颈实测:谓词查询“劝退”了多少子弹
最后分享一个性能优化的实录。有一次物理场景里同时存在上千颗子弹动态体,每颗子弹每帧要做一次射线检测,物理系统直接卡成幻灯片。一看Profiler,90%的时间耗在Bullet的窄相位检测上——每颗子弹都和周围十几个刚体做精确求交,这是完全没有必要的。
优化思路有两步。第一步,给子弹设置“查询谓词”,让射线检测只可能命中某一碰撞层,绕开与普通刚体的求交。第二步,降低子弹物理模拟频率,子弹在空中飞行阶段不做刚体模拟,只用射线检测位置,直到快接近目标时才“激活”完整物理。优化后,物理帧时间从14毫秒降到了2毫秒以下,效果立竿见影。这种优化在游戏开发里的意义,比盲目追求高精度仿真大得多——玩家的眼睛不会盯着每一颗子弹的旋转细节,但一定会注意到物理系统卡顿掉帧。
7. 写在最后:物理与动画的调参是一门手艺
物理系统和动画系统的架构设计,说到底是“在可控性与表现力之间找平衡”。物理引擎追求逼真,但真让它完全接管角色的每一帧姿态,表现力反而不如精心手调的动画关键帧。动画系统追求可控,但没有物理反馈支撑,穿模和僵硬感又很出戏。真正成熟的引擎会在两者之间放一层“软接口”,让程序化动画、布娃娃、IK和物理修正共享同一套骨骼数据流。
我在实际项目里的体会是:不要一开始就追求“全物理驱动”或“全动画驱动”的极端路线。先在动画状态机里保证基础表现,再把物理系统以“修正者”身份接入,按需启用布娃娃、IK脚部约束和受击反应。这样做的好处是风险可控,每一层新增功能都可以独立回滚,也不至于把项目拖进“物理仿真不可复现”的深坑。
另外,不管你在Windows物理机还是Ubuntu物理机里搞虚拟机,总之要确保调试环境可快速重建。物理引擎参数、动画混合权重、骨骼Mask这些配置散落在各个资产里,没有一套可复现的调试环境,验证一次改动往往要花掉半天时间。宁可前期花两天把开发虚拟机、自动化回归和可视化调试链路搭扎实,后边每个工作日都能省出一两个小时来。
最后再分享一个小技巧:调布娃娃参数时,不用每次都摆出完整的骷髅模型。先用胶囊体代替四肢,把物理模拟跑到稳定后再贴皮肤网格。胶囊体调试速度快——没有蒙皮权重干扰,约束抖动一眼就能看清来源。等所有关节参数收敛了,再套上真实网格看表现。这个办法帮我解决了不少“参数看起来对但效果一塌糊涂”的疑难杂症,你下次卡在布娃娃调参时不妨试试。