Unity中碰撞时Rigidbody无法稳定停止的解决方法
问题:RigidBody碰撞后无法稳定停止,速度影响停止效果
当RigidBody与碰撞对象发生碰撞时,事件会触发,但物体并不总能停止,这取决于它的速度,不符合需求。调试发现碰撞时_rb.velocity和_rb.angularVelocity已经为0,但问题依旧。怀疑和Update运行机制有关,但无法解决。
原代码
public class Mover : MonoBehaviour { private Rigidbody _rb; private bool _isColliding; [SerializeField] private float moveSpeed = 1f; // Start is called before the first frame update void Start() { _rb = GetComponent<Rigidbody>(); } // Update is called once per frame void Update() { MovePlayer(); } void MovePlayer() { float xValue = Input.GetAxis("Horizontal") * moveSpeed; float zValue = Input.GetAxis("Vertical") * moveSpeed; Vector3 movement = new Vector3(xValue, 0f, zValue); _rb.MovePosition(_rb.position + movement * Time.fixedDeltaTime); } private void OnCollisionEnter(Collision collision) { _isColliding = true; _rb.velocity = Vector3.zero; _rb.angularVelocity = Vector3.zero; } }
已尝试的方法
- 将墙体设置为静态
- 添加新的Physic Material(物理材质)
- 调整墙体碰撞盒大小,避免重叠
- 将Rigidbody的碰撞检测设置为Continous(连续检测)
- 在“墙体”侧捕获并处理碰撞事件:
public class WallHit : MonoBehaviour { private void OnCollisionEnter(Collision collision) { if (collision.gameObject.CompareTag("Player")) { Rigidbody rbdy = collision.gameObject.GetComponent<Rigidbody>(); rbdy.velocity = Vector3.zero; rbdy.angularVelocity = Vector3.zero; } } }
- 在
OnCollisionEnter中尝试设置_rb.isKinematic = true,但导致物体永久减速 - 尝试用射线检测处理:
void Update() { // Check for player input if (!IsColliding()) { MovePlayer(); } } bool IsColliding() { // Calculate movement vector based on player input float xValue = Input.GetAxis("Horizontal") * moveSpeed; float zValue = Input.GetAxis("Vertical") * moveSpeed; Vector3 movement = new Vector3(xValue, 0f, zValue); // Perform a collision check using a Raycast RaycastHit hit; if (Physics.Raycast(transform.position, movement, out hit, movement.magnitude * Time.deltaTime)) { // If a collision is detected, return true return true; } // If no collision is detected, return false return false; } void MovePlayer() { // Move the player float xValue = Input.GetAxis("Horizontal") * moveSpeed; float zValue = Input.GetAxis("Vertical") * moveSpeed; Vector3 movement = new Vector3(xValue, 0f, zValue); _rb.MovePosition(_rb.position + movement * Time.fixedDeltaTime); }
解决方案
问题根源分析
- Update与FixedUpdate时序不同步:物理操作(如刚体移动)应放在
FixedUpdate中执行,Update的帧频率不固定,会导致输入驱动的移动和物理碰撞检测错位,碰撞后Update的移动代码仍可能推动物体。 - 碰撞状态维护不全:
OnCollisionEnter仅触发一次,若玩家持续碰撞墙体,未在OnCollisionStay中持续阻止移动,且未在OnCollisionExit中重置状态。 MovePosition的强制位移特性:该方法直接设置刚体位置,会绕过部分物理约束,即使清零速度,后续的MovePosition仍会强行移动刚体。
方案1:修正物理更新时序,结合碰撞状态控制移动
将移动逻辑移至FixedUpdate,并在碰撞全程维护状态、清零速度:
public class Mover : MonoBehaviour { private Rigidbody _rb; private bool _isColliding; [SerializeField] private float moveSpeed = 1f; void Start() { _rb = GetComponent<Rigidbody>(); // 适配无重力场景,碰撞检测设为连续动态(针对运动刚体) _rb.useGravity = false; _rb.collisionDetectionMode = CollisionDetectionMode.ContinuousDynamic; } // 物理更新固定时间步长执行 void FixedUpdate() { if (!_isColliding) { MovePlayer(); } else { // 碰撞时持续清零速度,避免物理引擎微小推力 _rb.velocity = Vector3.zero; _rb.angularVelocity = Vector3.zero; } } void MovePlayer() { float xValue = Input.GetAxis("Horizontal") * moveSpeed; float zValue = Input.GetAxis("Vertical") * moveSpeed; // 使用velocity驱动移动,让物理引擎处理碰撞约束 _rb.velocity = new Vector3(xValue, 0f, zValue); } private void OnCollisionEnter(Collision collision) { _isColliding = true; _rb.velocity = Vector3.zero; _rb.angularVelocity = Vector3.zero; } private void OnCollisionExit(Collision collision) { _isColliding = false; } // 处理持续碰撞场景 private void OnCollisionStay(Collision collision) { _isColliding = true; _rb.velocity = Vector3.zero; _rb.angularVelocity = Vector3.zero; } }
方案2:改进射线检测(适配MovePosition场景)
若坚持使用MovePosition,用BoxCast替代Raycast,覆盖玩家碰撞盒的移动路径检测:
public class Mover : MonoBehaviour { private Rigidbody _rb; [SerializeField] private float moveSpeed = 1f; [SerializeField] private float skinWidth = 0.01f; // 避免碰撞盒与墙体重叠 void Start() { _rb = GetComponent<Rigidbody>(); _rb.useGravity = false; } void FixedUpdate() { Vector3 movement = GetMovementInput(); if (!WillCollide(movement)) { _rb.MovePosition(_rb.position + movement * Time.fixedDeltaTime); } else { _rb.velocity = Vector3.zero; _rb.angularVelocity = Vector3.zero; } } Vector3 GetMovementInput() { float x = Input.GetAxis("Horizontal") * moveSpeed; float z = Input.GetAxis("Vertical") * moveSpeed; return new Vector3(x, 0f, z); } bool WillCollide(Vector3 movement) { Collider col = GetComponent<Collider>(); Vector3 moveDir = movement.normalized; float moveDistance = movement.magnitude * Time.fixedDeltaTime; // 用BoxCast检测碰撞盒移动路径上的碰撞 if (Physics.BoxCast(col.bounds.center, col.bounds.extents - new Vector3(skinWidth, skinWidth, skinWidth), moveDir, out RaycastHit hit, transform.rotation, moveDistance)) { if (hit.collider.gameObject != gameObject) { return true; } } return false; } }
关键注意事项
- 所有物理相关操作必须放在
FixedUpdate中,确保与物理引擎更新同步。 - 优先用
velocity驱动移动,而非MovePosition,前者更贴合物理碰撞约束。 - 碰撞状态需在
OnCollisionStay中持续维护,覆盖玩家持续推墙的场景。
内容的提问来源于stack exchange,提问作者buco
相关产品推荐
相关产品推荐

