Kinematic Rigidbody2D角色穿墙问题:近距离向墙移动碰撞失效
问题分析与解决方案
核心问题
- Raycast检测方向滞后:原代码中Raycast使用的是上一次的
direction变量,而非用户当前输入的移动方向,导致紧贴墙体时,输入朝向墙体的方向后,Raycast未更新到新方向,无法检测到障碍物,进而错误允许移动。 - 移动逻辑绕过物理检测:直接通过
transform.position插值移动Kinematic刚体,未利用Rigidbody2D的物理系统,且协程移动过程中未做碰撞校验,一旦启动移动就会执行到底。 - 状态重置不完整:仅在检测到障碍物时修改
canMove状态,无碰撞场景下未主动重置canMove = true,可能导致状态异常。
修复方案
1. 实时用输入方向做Raycast检测
将Raycast的方向替换为当前用户输入的moveInput,确保每次检测都是针对即将移动的方向。
2. 使用Rigidbody2D.MovePosition处理移动
对于Kinematic刚体,官方推荐使用rb.MovePosition而非直接修改transform.position,这样能更好地与2D物理系统交互,同时在移动过程中可以加入碰撞检测。
3. 完善状态管理
- 每次检测后主动重置
canMove状态,避免状态残留。 - 在协程移动过程中加入二次碰撞检测,防止移动途中出现碰撞遗漏。
修改后的完整代码
private bool isMoving; public bool canMove; private Vector2 originPos, targetPos; public float timeToMove = 0.3f; public Transform rayOrigin; public float size; public Vector2 direction; private Rigidbody2D rb; // assign the actions asset to this field in the inspector: public InputActionAsset actions; // private field to store move action reference private InputAction moveAction; void Awake() { // find the "move" action, and keep the reference to it, for use in Update moveAction = actions.FindActionMap("gameplay").FindAction("move"); } void Start() { canMove = true; rb = GetComponent<Rigidbody2D>(); } void FixedUpdate() { P_Input(); } private void P_Input() { // Read the move input first Vector2 moveInput = moveAction.ReadValue<Vector2>(); moveInput = moveInput.normalized; // 确保输入方向是单位向量 // 用当前输入方向做Raycast检测 RaycastHit2D hit = Physics2D.Raycast(rayOrigin.position, moveInput, size, ~(1 << 8)); Debug.DrawRay(rayOrigin.position, moveInput * size, Color.green); // 重置canMove为默认允许,再根据碰撞修改 canMove = true; if (hit.collider != null && hit.collider.CompareTag("obstacle")) { // 仅当输入方向朝向障碍物时,禁止移动 if (Vector2.Dot(moveInput, hit.normal) <= -0.9f) // 方向几乎相反(朝向障碍物) { canMove = false; } } if (canMove && !isMoving && moveInput.magnitude > 0.1f) // 输入有效时才处理 { direction = moveInput; StartCoroutine(MovePlayer(moveInput)); } } private IEnumerator MovePlayer(Vector2 moveDirection) { isMoving = true; float elapseTime = 0; originPos = rb.position; targetPos = originPos + moveDirection; while (elapseTime < timeToMove) { // 移动过程中再次检测碰撞,防止中途出现障碍物 RaycastHit2D midHit = Physics2D.Raycast(rayOrigin.position, moveDirection, size, ~(1 << 8)); if (midHit.collider != null && midHit.collider.CompareTag("obstacle")) { canMove = false; break; } // 用Rigidbody2D.MovePosition移动Kinematic刚体 float t = elapseTime / timeToMove; Vector2 newPos = Vector2.Lerp(originPos, targetPos, t); rb.MovePosition(newPos); elapseTime += Time.deltaTime; yield return null; } // 如果移动被中断,回到起点;否则到达目标点 if (!canMove) { rb.MovePosition(originPos); } else { rb.MovePosition(targetPos); } isMoving = false; canMove = true; // 重置移动状态 } void OnEnable() { actions.FindActionMap("gameplay").Enable(); } void OnDisable() { actions.FindActionMap("gameplay").Disable(); }
关键修改说明
- Raycast方向同步:现在每次检测都使用当前的
moveInput,确保检测的是用户想要移动的方向,解决了滞后问题。 - 物理移动方式:改用
rb.MovePosition处理Kinematic刚体的移动,符合Unity物理规范,避免直接修改transform导致的物理交互失效。 - 移动中碰撞校验:在协程的移动循环中加入二次Raycast,即使移动中途出现障碍物也能及时中断。
- 状态逻辑优化:每次输入检测时先重置
canMove为true,再根据碰撞结果修改,避免状态残留;移动结束后主动重置状态。
内容的提问来源于stack exchange,提问作者TakumiTakumi
相关产品推荐
相关产品推荐

