先说结论:UE4里做Foot IK,最难的不是把脚“压”到地面上,而是让脚掌像真人一样顺着斜坡转过去。很多项目做到一半发现,角色站在30度的坡上,脚是够着地了,但整只脚还是水平悬空,一眼假。这篇就把我在项目里踩完坑的一套做法放出来:用动画蓝图的Two Bone IK做位置,用地面Hit Normal做旋转,双管齐下解决斜坡、台阶、碎石路的脚部贴合问题。适合正在做第三人称动作、移动端射击,或者任何需要角色脚部自适应地形的朋友参考。
1. 先搞清楚:Foot IK到底在解决什么问题
1.1 平地动画放到斜坡上的两个漏洞
动画师在混合空间里K的走路、跑步、待机动画,默认场景都是平地。角色站在斜坡上时,会出现两个非常明显的问题:
第一个是高度差。左脚在坡底,右脚在坡顶,可平地动画要求两脚在同一水平高度,于是低处那只脚悬空,高处那只脚陷进地面。第二个是角度差。即使你用IK把悬空那只脚拉到了坡面,脚的朝向仍然是动画原本的朝向。脚掌没有顺着坡面旋转,鞋底还是平行于世界地平面的。这个现象在斜坡上尤其扎眼,就像一个人穿着硬底皮鞋站在倾斜的台阶上,整个鞋底只有边缘一点碰到地面,完全不符合物理直觉。
高度差好解决,Two Bone IK、FABRIK、甚至简单的骨骼位移都能把脚“送”到地面。真正麻烦的是角度差。因为Foot IK的常规做法只处理了Effector Location,也就是脚踝或脚的目标位置,根本没有处理脚骨的旋转。要让脚掌和地面贴合,必须额外把地面法线转换成脚骨的旋转,再叠加到动画上。
1.2 先分清“查询”和“物理模拟”,别把路带偏
很多刚开始做Foot IK的人,会在网上搜到两种完全不同的方向:一种是动画蓝图里的Line Trace加Two Bone IK,另一种是给脚部骨骼开Simulate Physics让它自己掉下去碰地面。这两条路的哲学完全不同。
Line Trace属于碰撞查询(Query)。它只回答一个问题:从A点向B点发出的射线有没有碰到东西?碰到的点在哪?碰到的面法线是什么?这个过程不做物理计算,不改变角色速度,不产生冲量。Foot IK用查询就足够了,我要的只是“脚底下面有多高、坡面朝哪个方向”。
而物理模拟(Simulation)是另一套体系。开启Simulate Physics后,骨骼受重力、惯性、碰撞、约束影响,脚会真的“掉”到地面上,看起来好像更自然,但它会和动画系统抢控制权,脚会抖动、会穿模、会和动画手势互相拧巴。简单说:查询是采样世界,模拟是反馈世界。Foot IK应该用查询,不是模拟。后面我会再展开讲这个分界线。
1.3 为什么选Two Bone IK而不是FABRIK或CCD
UE4自带的IK节点里,能用于腿部的主要是Two Bone IK、FABRIK、CCD。我的选择是Two Bone IK,因为它和腿部结构完全吻合:大腿、小腿、脚,正好是三段式骨骼链。Two Bone IK会根据Effector位置反推大腿和小腿的旋转,并且带有Twist控制,不容易出现膝盖乱转的“面条腿”。
FABRIK适合多关节链,比如脊椎、手臂,迭代解算会平滑收敛,但用在腿上略显大材小用。CCD适合过约束的链,用在两条腿后会不稳定。另外,UE4的老项目里,Two Bone IK节点稳定、参数直观、网上资料多,出了问题也容易查。所以除非你的腿骨结构特殊,否则首选就是它。
2. 动手前的准备:碰撞查询、骨骼与参数
2.1 射线往哪打,怎么打,决定一半的稳定性
我这里说的实现方式非常常见:左脚和右脚各发一条射线,从脚踝或脚骨Socket位置向下打。射线起点用脚部Socket,终点是起点往下减去一个固定距离,这个距离叫Reach Distance。
具体在蓝图里这样接:
Event Blueprint Update Animation -> TryGetPawnOwner -> IsValid
拿到Pawn后,用GetSocketLocation读取脚部Socket的世界坐标,然后发Line Trace。Trace Channel可以选Visibility,也可以选WorldStatic,看项目碰撞预设。关键是Actors to Ignore里一定要把自己加进去,否则射线可能先打中角色的Capsule顶到自身上,导致IK目标悬浮在半空。
射线命中后,要同时拿两个值:Hit Location和Hit Normal。Hit Location用来算脚的目标位置,Hit Normal用来算脚的目标旋转。很多人只拿位置,不拿法线,这就是为什么脚能贴地却不旋转。
2.2 核心参数表,建议直接抄走
参数这个东西,网上教程给的数值往往来自他们的项目,直接照搬不一定合适。我给一组我常用的初始值,你用这套值起步,再按角色体型调。
| 参数 | 建议初始值 | 作用 |
|---|---|---|
| ReachDistance | 80~120 | 射线向下探测长度。太短斜坡低处打不到,太长高处会吸地 |
| FootHeight | 5~10 | 脚骨骼离鞋底的高度。为0时脚骨会陷地,需要按模型微调 |
| InterpSpeed | 10~20 | 位置和旋转插值速度。越大越灵敏,越小越平滑 |
| MinNormalZ | 0.3 | 法线Z值低于这个阈值就忽略,防止射线打到台阶立面或墙面 |
这里特别说一下FootHeight。射线打到的命中点是坡面,而坡面是鞋底接触的地方,不是脚骨骼所在位置。如果直接把Hit Location当Effector,脚骨会插进地面一小截。正确做法是让目标点沿法线方向抬升一个FootHeight,让鞋底自然踩在坡面上。这个值一般是脚踝骨骼到脚底的距离,我习惯先设8厘米,再进游戏微调。
2.3 为什么要用IK Alpha曲线,而不是全程开启
如果两条射线每帧都在算,且IK权重恒为1,那么角色走路时,脚在摆荡相也会被强行拽向地面,看起来就像脚底装了磁铁,每个脚步都拖着地面滑过去。这个问题不是IK逻辑错了,是权重没控制好。
我习惯在混合空间里加一条EnableFootIK动画曲线。这个曲线的值在支撑相为1,在脚离开地面的摆荡相为0。动画蓝图里用GetCurveValue读出来,作为IK的目标Alpha,再经过FInterpTo做平滑过渡。这样脚在离地时保持原始动画,脚踩到地面时才开始发力,过渡自然,不会突然瞬间切换。
3. 动画蓝图核心链路:Two Bone IK + 地面法线旋转
3.1 Event Graph:每帧算左脚和右脚的目标
我这里以第三人称角色为例,角色有左脚、右脚两个Socket,在Event Graph里分别处理,然后存成变量给动画图用。
伪代码逻辑是这样的:
if (LineTrace_Left.Hit): L_FootLocation = Hit.Location + Hit.Normal * FootHeight L_FootAlphaTarget = 1.0 else: L_FootAlphaTarget = 0.0 L_FootAlpha = FInterpTo(L_FootAlpha, L_FootAlphaTarget, DeltaSeconds, InterpSpeed)右侧重复一遍。需要注意的是,L_FootAlphaTarget是每帧直接置0或置1的,但L_FootAlpha要经过插值,不能直接使用目标值。否则脚会在落地瞬间“啪”一下贴上去,离地瞬间又“啪”一下弹开,非常生硬。
位置算出来后,先存成L_FootLocation变量。同时把Hit Normal也存下来,下一步算旋转要用。
3.2 AnimGraph里Two Bone IK怎么接
动画图里节点的串联顺序我固定是这样:
Final Animation Pose -> Two Bone IK (Left) -> Two Bone IK (Right) -> Modify Bone (Left) -> Modify Bone (Right) -> Output Pose
每个Two Bone IK重点设置这几个属性:
IK Bone填foot_l或foot_r,这是要解算的末端骨骼。Effector Location Space选World Space或Component Space,我习惯用World Space,直接填射线算出来的世界坐标。Effector Location连到事件图存下来的L_FootLocation变量。Joint Target Location也很重要,它告诉IK膝盖应该朝哪个方向弯。如果不填,Two Bone IK有可能解出内八字、膝盖外翻等各种奇怪姿势。我把膝盖Socket的位置加上角色前方向量乘以固定距离,作为Joint Target。这个值不需要每帧更新,角色初始化时算一次就行。
Alpha连L_FootAlpha。这一步解决的是高度差,让脚能准确踩到坡面。
3.3 地面法线转成脚骨旋转,这是核心中的核心
Two Bone IK管位置,不管脚底朝向。所以要在它后面加一个Modify Bone节点,把脚骨旋转按地面法线叠加上去。
法线是世界坐标里的方向,我需要先转换到Mesh组件空间。因为Modify Bone节点里的Component Space是相对于Mesh组件的。不转换的话,如果角色旋转了90度,左右脚的法线旋转结果会对不上。
转换方式是用Mesh组件的InverseTransformDirection节点处理Hit Normal。得到组件空间法线后,再用下面两个角度算出旋转:
Pitch = atan2(Normal.X, Normal.Z) Roll = -atan2(Normal.Y, Normal.Z) SlopeRotator = MakeRotator(Pitch, 0, Roll)Pitch管前后坡,Roll管左右坡。如果项目里角色前方向不是X轴,或者左右手坐标系导致结果反了,直接调正负号就行,不用死记。我这里解释一下为什么要用反正切:当平面法线和Z轴重合时,角度是0;当法线倾斜,X或Y分量变大,反正切能准确还原出坡面相对垂直方向的角度。直接用MakeRotFromZ(Normal)也不是不行,但最终旋转会包含角色自身朝向,容易和动画产生“拧麻花”效果。
最后在Modify Bone里这样设置:
BoneToModify填foot_l,Rotation Space选Component Space,Rotation Mode选Additive,Rotation连L_FootSlopeRot,Alpha连L_FootAlpha。Translation和Scale都设成Ignore。这里用Additive加法模式,而不是Replace替换模式,是为了保留动画原有的脚踝弯折,只在这个基础上补一个坡面角度。如果直接用Replace,站在斜坡上脚确实贴地了,但待机动画里脚踝的微调也会被抹掉,角色会显得僵硬。
3.4 骨盆高度要不要一起调
如果你的地形只是普通缓坡,只调脚就够了。但如果角色要上下楼梯、踩大块石头,只有脚贴地会导致膝盖过度拉伸,或者角色好像“劈叉”。这时候可以考虑给骨盆也做一点修正。
最简单的做法是对比左右脚的IK偏移差值,得到一个骨盆的垂直和旋转修正量,再修改骨盆骨骼的位置或旋转。不过骨盆修正很容易做过头,让角色看起来像在坐电梯。我的经验是,先把脚部IK调稳定,再按需加骨盆修正,而且骨盆的InterpSpeed要比脚部更慢,宁可迟钝,不能抖动。标题里提到的问题,大部分情况下不需要骨盆也解决得好。
4. 常见问题与排查记录
4.1 脚在斜坡上还是“平的”
这个是最常见的问题。先检查你是不是只把位置加上了法线偏移,整个链路里却没有修改脚骨的旋转。如果位置IK和旋转IK都做了,再检查Modify Bone的Rotation Mode。注意,不能把旋转加到脚踝甚至小腿上,那样脚掌不会转。BoneToModify一定要是脚骨。
还有一个容易漏掉的地方:Modify Bone节点的Alpha也要接IK权重。如果只给Two Bone IK接了Alpha,Modify Bone那边权重为1,结果就是位置平滑过渡了,旋转却还是瞬间跳变,看起来像脚崴了一下。
4.2 脚在台阶边缘疯狂抖动
射线打到台阶立面时,Hit Normal接近水平,算出来的Pitch或Roll会特别夸张,脚就像踩在刀刃上一样来回摆。解决办法是判断Hit Normal.Z,低于阈值就直接忽略这一针的命中。我一般设0.3,也就是坡面超过70度左右就不处理。普通台阶和斜坡的命中点,法线Z都远大于0.3,不会影响效果。
如果抖动发生在平地上,检查L_FootAlpha的插值速度。插值速度太快,IK位置每一帧都在追赶地面,会有些微抖动;太慢又显得脚跟不上地形。我一般从10开始,一边跑斜坡一边调,直到肉眼看不到明显颤动。
4.3 走路时脚像“吸”在地上
这是典型的IK权重全程为1的问题。看角色走路时,摆荡的那只脚如果不离开地面,那就是Alpha曲线没起作用。去混合空间里确认EnableFootIK曲线是否在正确的动画片段的正确时间帧赋值。也可以先不管曲线,直接用动画片段的播放进度做近似权重,但精确控制还是曲线最可靠。
另外,射线长度太长也会加剧这个问题。角色从高处跳下来,离地30厘米,射线还能打到下面50厘米的地面,IK就会把脚提前拽下去。ReachDistance给100合适,不要给几千几万。
4.4 膝盖反拐、姿势拧巴
膝盖乱转,先查Joint Target。Joint Target不是随便设一个点就完事,它要尽量放在膝盖前方,也就是角色前方向量与膝盖位置交叉的位置。如果Athlete模型是X轴朝前,就用膝盖Socket位置 + ActorForwardVector * 30。如果是右脚,可以把前方向量稍微偏外一点,但先不要偏,大多数角色默认就够。
4.5 查询和物理模拟的坑,我再强调一次
有次调试,脚面在碰撞体上不停弹跳,检查半天发现是某个人为了“脚能踩着箱子”给脚骨开了Simulate Physics。Foot IK查询已经把脚放到箱子上,Simulate Physics又在同一帧里让脚骨受重力往下掉,两个系统互相打架,结果就是高频抖动。记住分界线:查询负责采样世界,模拟负责表现物理反馈。要做脚踩石头联动石头的效果,单独加物理约束和冲量传递就行,不要和Foot IK混在一起。
5. 还想再抠细节:输入映射、物理模拟与调试材质
5.1 外接设备映射先于动画,别让输入绑架IK
有些项目会接方向盘、摇杆、跑步机甚至体感设备。我的建议是,无论什么外接设备,映射到角色状态时都只做一层转换:设备输入 → 目标速度/转向 → 角色移动组件 → 混合空间动画 → Foot IK。不要让设备的原始轴向直接驱动脚骨旋转。Foot IK只关心地面贴合,不应该关心输入来源。
如果硬要把外接设备的位移映射到角色上,正确的做法是在Pawn层面把设备数据转换成加速和转向指令,让CharacterMovement自己处理,Foot IK照常在动画层工作。这样以后换设备、换输入协议,都不用重写IK逻辑。
5.2 查询和物理模拟器的边界,画画线就清楚
做一个简单的测试就能理解:在斜坡上开DrawDebugLine,显示Foot IK的射线。查询模式下,射线是一条从脚向下的虚线,命中后出现一个Debug点,角色位置不会被改变。模拟模式下,射线还没出来,脚已经开始往下掉,角色重心也会受到影响。调Foot IK时,我希望射线稳定、目标明确,不希望角色重心被脚一下一下地顶起来。所以我把Foot IK严格限定在查询侧,物理模拟留给跳跃、被撞飞、尸体倒地这些场景。
5.3 用调试节点和材质观察地面信息
调试时我必开DrawDebugLine和DrawDebugSphere,用彩色线条把射线、命中点、目标点都画出来。命中点位置正常,说明位置IK没问题;再开DrawDebugCoordinateSystem在命中点显示法线坐标轴,法线垂直坡面,说明旋转计算的数据源没问题。
有些项目还想做脚印、泥地痕迹。这个时候可以用SpawnDecalAtLocation生成贴花,材质节点里配合世界坐标、法线方向、透明度节点做脚印渐隐。但要注意,贴花和材质是表现层,不应该反过来影响Foot IK的计算结果。调试时先把表现层关掉,只看射线和目标点,逻辑稳定后再开贴花效果,能省很多排查时间。
最后再分享一个习惯:做Foot IK不要迷恋参数,要做减法。先把位置IK调稳定,再加旋转IK,最后才考虑骨盆和贴花。每次只引入一个变量,出问题就知道是哪一层。我在实际项目里踩过几次坑之后,现在只要发现脚部异常,第一步永远是关掉表现层、打开DrawDebugLine,沿着斜坡跑两圈,基本就能定位是射线起点问题、法线转换问题,还是权重插值问题。