角色卡在墙角

2026-09-11 · unity

这篇记一个从项目一开始就存在、反复出现、最后靠”计时器”解决的问题:角色按住方向键跳向平台边缘时,会被卡在墙角——上不去、下不来,保持跳跃姿势,直到松开方向键才落下。

现象

按住方向键朝平台边缘跳过去,角色撞上去之后就”挂”在那里:

一开始以为是动画问题,后来发现是物理真的被卡住了。

原因(两层)

第一层:状态退出条件太”脆”

跳跃状态退出靠这一句:

private void UpdateJump()
{
    rb.velocity = new Vector2(horizontalMoveLastFrame * moveSpeed, rb.velocity.y);
    if (rb.velocity.y <= 0) ChangeState(PlayerState.Fall);   // ← 只认 <= 0
}

碰撞发生时,物理引擎会把角色的竖直速度顶在”接近 0 的微小正值”上反复抖动——它永远到不了 <= 0,于是永远退不出 Jump 状态,角色就一直挂在跳跃姿势里。

第二层:矩形碰撞体的直角”咬”在一起

角色是矩形 BoxCollider2D,平台也是。角色撞到平台边缘时,两个直角互相咬住,物理在接触点产生一个”托住角色”的法向力;而代码每帧又把水平速度强制成”朝墙压”的值,于是角色被稳稳夹在那里——水平被墙挡、竖直被角托住,vy 停在 0。

松手后水平推力消失,角色脱离接触,重力才把它带走。这解释了”为什么松方向就掉下去”。

修复

① 放宽状态退出阈值(治”状态退不出”)

// 旧:if (rb.velocity.y <= 0) ChangeState(PlayerState.Fall);
if (rb.velocity.y <= 0.1f) ChangeState(PlayerState.Fall);   // 被顶到微小正值也算"到顶了"

0.1f 只是”快到顶点”的一点容忍,正常跳跃体感几乎无差,但避开了”被物理顶在微正”的死区。

② 卡墙计时器(治”物理卡死”,核心)

光靠阈值不够——vy 真的稳定在 0 时,还得有人把角色从角里推出来。用”持续时间”来区分”正常顶点”和”被卡住”:

// 字段
private float stuckTimer;

// FixedUpdate 里(算完 isGrounded、状态分发之后)
if (!isGrounded && currentState != PlayerState.WallSlide && Mathf.Abs(rb.velocity.y) < 0.2f)
    stuckTimer += Time.fixedDeltaTime;
else
    stuckTimer = 0f;

if (stuckTimer > 0.15f)          // 持续被卡住超过 0.15 秒
{
    stuckTimer = 0f;
    rb.velocity = new Vector2(rb.velocity.x, -2f);   // 轻推向下,挣脱那个角
}

为什么这样不会误伤正常跳跃:普通跳跃到达顶点时,vy ≈ 0 只会持续 1~2 帧(约 0.03 秒),计时器根本到不了 0.15 就被清零;而”被卡住”是 vy 持续压在 0 附近,时间一到就判定为卡死。

这也是这次最有价值的思路:当物体被物理”顶着”的时候,基于瞬时速度的判断会失真,需要引入”持续时间”这个维度,才能把”一瞬经过的状态”和”被困住的状态”分开。

备用方案

如果还想从几何上根治:BoxCollider2D 换 CapsuleCollider2D——圆角边会让”直角互咬”变成”沿弧面滑开”,再配合侧面检测。代价是碰撞体尺寸/偏移要重调,落地手感和过窄缝都要重测。当前用计时器已经稳定,所以留作备选。

小结