Unity玩家碰撞墙体后无限反弹问题求助及代码排查
Unity玩家碰撞墙体后反弹飞离问题解决
问题描述
Player与Wall碰撞时会被弹开,甚至在墙体间来回反弹最终飞到虚空。需求是:玩家按下按键后,若下一步会碰撞Wall,则该方向移动无效;若下一步无碰撞,则正常移动。
相关配置
- 标签设置:Player和Walls分别设置标签
Player、Wall - Collider配置:
None physical Material Is Trigger - unchecked Radius - 0.5 - Rigidbody配置:
Mass - 1 Drag - 0 AngularDrag = 0.05 Use Gravity = unchecked Is Kinematic = unchecked
当前PlayerController代码
using System.Collections; using System.Collections.Generic; using UnityEngine; public class PlayerController : MonoBehaviour { public bool smoothTransition = false; public float transitionSpeed = 10f; public float transitionRotationSpeed = 500f; Vector3 targetGridPos; Vector3 prevTargetGridPos; Vector3 targetRotation; private void Start() { targetGridPos = Vector3Int.RoundToInt(transform.position); Collider collider = gameObject.AddComponent<BoxCollider>(); collider.isTrigger = false; } private void FixedUpdate() { MovePlayer(); } void MovePlayer() { if (true) { prevTargetGridPos = targetGridPos; Vector3 targetPosition = targetGridPos; if (targetRotation.y > 270f && targetRotation.y < 361f) targetRotation.y = 0f; if (targetRotation.y < 0f) targetRotation.y = 270f; if (!smoothTransition) { transform.position = targetPosition; transform.rotation = Quaternion.Euler(targetRotation); } else { transform.position = Vector3.MoveTowards(transform.position, targetPosition, Time.deltaTime * transitionSpeed); transform.rotation = Quaternion.RotateTowards(transform.rotation, Quaternion.Euler(targetRotation), Time.deltaTime * transitionRotationSpeed); } // Check for collisions with walls Collider[] hitColliders = Physics.OverlapSphere(transform.position, 0.4f); foreach (Collider hitCollider in hitColliders) { Debug.Log("Collided with " + hitCollider.gameObject.name); if (hitCollider.gameObject.CompareTag("Wall")) { targetGridPos = prevTargetGridPos; // move the player back to their previous position break; } } Vector3 nextStep = targetGridPos + 2 * transform.forward; Collider[] nextStepHitColliders = Physics.OverlapSphere(nextStep, 0.4f); foreach (Collider nextStepHitCollider in nextStepHitColliders) { if (nextStepHitCollider.gameObject.CompareTag("Wall")) { return; // don't update targetGridPos if the next step will collide with a wall } } targetGridPos = nextStep; } else { targetGridPos = prevTargetGridPos; } } void OnCollisionEnter(Collision collision) { // Check if the collision is with a wall if (collision.gameObject.tag == "Wall") { // Move the player back to their previous position targetGridPos = prevTargetGridPos; } } public void RotateLeft() { if (AtRest) targetRotation -= Vector3.up * 90f; } public void RotateRight() { if (AtRest) targetRotation += Vector3.up * 90f; } public void MoveForward() { if (AtRest) targetGridPos += 2 * transform.forward; } public void MoveBackward() { if (AtRest) targetGridPos -= 2 * transform.forward; } public void MoveLeft() { if (AtRest) targetGridPos -= transform.right; } public void MoveRight() { if (AtRest) targetGridPos += transform.right; } bool AtRest { get { if ((Vector3.Distance(transform.position, targetGridPos) < 0.05f) && (Vector3.Distance(transform.eulerAngles, targetRotation) < 0.05f)) return true; else return false; } } }
解决方案
问题核心是同时使用物理引擎驱动的Rigidbody和手动设置transform位置,两者冲突导致物理引擎产生异常推力,引发反弹飞离。结合需求(预判碰撞,禁止无效移动),按以下步骤修复:
1. 调整Rigidbody设置
将Rigidbody的Is Kinematic设为checked,让物理引擎不再自动处理玩家移动,完全由代码控制,避免物理冲突。
2. 重构碰撞预判逻辑
把碰撞检查移到修改targetGridPos之前,在触发移动操作时就预判下一步是否会撞墙,只有无碰撞时才允许移动。同时移除碰撞后的补救代码,因为我们要提前阻止碰撞发生。
3. 修改后的关键代码
替换原有移动方法
public void MoveForward() { if (AtRest) { Vector3 nextPos = targetGridPos + 2 * transform.forward; if (!IsPositionCollidingWithWall(nextPos)) { targetGridPos = nextPos; } } } public void MoveBackward() { if (AtRest) { Vector3 nextPos = targetGridPos - 2 * transform.forward; if (!IsPositionCollidingWithWall(nextPos)) { targetGridPos = nextPos; } } } public void MoveLeft() { if (AtRest) { Vector3 nextPos = targetGridPos - transform.right; if (!IsPositionCollidingWithWall(nextPos)) { targetGridPos = nextPos; } } } public void MoveRight() { if (AtRest) { Vector3 nextPos = targetGridPos + transform.right; if (!IsPositionCollidingWithWall(nextPos)) { targetGridPos = nextPos; } } }
新增碰撞预判辅助方法
private bool IsPositionCollidingWithWall(Vector3 position) { Collider[] hitColliders = Physics.OverlapSphere(position, 0.4f); foreach (Collider hit in hitColliders) { if (hit.CompareTag("Wall")) { return true; } } return false; }
简化MovePlayer方法
删除冗余的碰撞修正代码,只保留平滑移动逻辑:
void MovePlayer() { prevTargetGridPos = targetGridPos; Vector3 targetPosition = targetGridPos; // 处理旋转循环 if (targetRotation.y > 270f && targetRotation.y < 361f) targetRotation.y = 0f; if (targetRotation.y < 0f) targetRotation.y = 270f; if (!smoothTransition) { transform.position = targetPosition; transform.rotation = Quaternion.Euler(targetRotation); } else { transform.position = Vector3.MoveTowards(transform.position, targetPosition, Time.deltaTime * transitionSpeed); transform.rotation = Quaternion.RotateTowards(transform.rotation, Quaternion.Euler(targetRotation), Time.deltaTime * transitionRotationSpeed); } }
删除冗余代码
移除OnCollisionEnter方法,以及MovePlayer中原有的碰撞检查代码块。
修改后,玩家只会在确认下一步无碰撞时才移动,彻底避免碰撞反弹问题,完全符合需求。
内容的提问来源于stack exchange,提问作者hamad
相关产品推荐
相关产品推荐

