在很多基于 Rigidbody 的角色项目里,大家遇到"卡墙"时第一反应是改摩擦系数、切换物理材质,甚至运行时动态改材质。
我们这次走了另一条路:不依赖动态物理材质,而是用"预测胶囊 + ComputePenetration 挤出向量"做防卡墙,最终稳定解决了问题。
这篇文章记录完整思路和落地方式。
1. 问题背景
项目中的角色移动使用 AddForce 驱动,场景有大量复杂几何(边角、凹凸 Mesh、环形结构)。
常见异常:
- 明明有移动输入,但角色贴墙后"推不动"。
- 某些边角处速度突然被吃掉。
- 偶发楔入(wedging),表现为抖动、短距离抽搐或原地卡住。
2. 为什么不再依赖"动态物理材质"方案
很多项目会尝试在代码里动态切换 friction/bounce 来缓解卡墙,但这种方式有几个硬伤:
-
治标不治本
卡墙核心是接触几何与解算方向问题,不是单纯摩擦问题。
-
全局副作用大
改材质会影响整套接触行为(滑移、落地、平台交互、推挤手感)。
-
调参成本高、稳定性差
一处场景调好了,另一处复杂结构可能又失效。
-
行为不一致
不同 FPS、不同接触体组合时,动态材质策略往往表现漂移。
我们最后选择:玩家和子物体都使用默认材质,不在运行时改材质参数 。
把防卡墙交给"几何预测 + 挤出向量"来处理,结果更稳。
3. 核心方案:Predictive Capsule + Penetration Push
最终方案分 5 步:
- 预测下一物理步角色胶囊位置(仅水平分量)。
- 在预测位姿做 OverlapCapsuleNonAlloc。
- 对每个重叠体调用 Physics.ComputePenetration。
- 聚合所有 separationDir * separationDistance 得到总挤出向量。
- 只处理水平向内分量:
- 去掉"继续往里顶"的加速度
- 可选抵消当前"向里"的水平速度(不加反弹)
重点:不改 Y 轴,不覆盖跳跃系统,不依赖动态材质。
4. 为什么它比单次 SweepTest 稳
单 SweepTest 适合"前方是否有障碍"的预警,但对 wedging 场景信息不够。
而 ComputePenetration 给的是"最小分离向量",可以直接回答:
- 现在应该往哪边解开重叠?
- 多个接触体同时挤压时,合力方向是什么?
这正是防卡墙真正需要的信息。
5. 精简代码思路
cs
// 预测水平位移
Vector3 predictedDelta = horizontalV * dt + accPlanar * (dt * dt);
predictedDelta += desiredDir.normalized * movementSweepPadding;
// 预测位姿胶囊重叠检测
int hitCount = Physics.OverlapCapsuleNonAlloc(
pointA, pointB, radius, overlapBuffer, movementSweepBlockMask, QueryTriggerInteraction.Ignore);
// 聚合挤出向量
Vector3 planarPush = Vector3.zero;
for (int i = 0; i < hitCount; i++)
{
Collider other = overlapBuffer[i];
if (other == null || other.isTrigger) continue;
if (Physics.ComputePenetration(
capsule, predictedPos, predictedRot,
other, other.transform.position, other.transform.rotation,
out Vector3 sepDir, out float sepDist))
{
Vector3 planarDir = Vector3.ProjectOnPlane(sepDir, Vector3.up);
if (planarDir.sqrMagnitude > 1e-6f && sepDist > 0f)
planarPush += planarDir.normalized * sepDist;
}
}
// 只移除向内推进,不改Y
if (planarPush.sqrMagnitude > 1e-6f)
{
Vector3 outward = planarPush.normalized;
Vector3 intoObstacle = -outward;
float intoAcc = Vector3.Dot(accelerationStep, intoObstacle);
if (intoAcc > 0f) accelerationStep -= intoObstacle * intoAcc;
}
6. 参数建议
- movementSweepPadding: 0.05 ~ 0.12
- movementSweepMaxCancelAcceleration: 25 ~ 60
- movementSweepBlockMask: 只包含会阻挡移动的层
7. 这套"防卡墙"方案的价值
- 不依赖动态物理材质,避免全局副作用。
- 对复杂场景更稳,特别是边角/环形/凹凸结构。
- 不破坏原有跳跃与竖直力模型。
- 参数可控、行为一致性更高。
结论
卡墙问题的核心是"接触解算方向"而不是"摩擦值本身"。
把策略从"动态改材质"切换到"预测胶囊 + 挤出向量",防卡墙会从"偶发有效"变成"稳定可控"。
如果你也在做 Unity 物理角色控制,这个方案非常值得直接落地。