You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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);
}

解决方案

问题根源分析

  1. Update与FixedUpdate时序不同步:物理操作(如刚体移动)应放在FixedUpdate中执行,Update的帧频率不固定,会导致输入驱动的移动和物理碰撞检测错位,碰撞后Update的移动代码仍可能推动物体。
  2. 碰撞状态维护不全:OnCollisionEnter仅触发一次,若玩家持续碰撞墙体,未在OnCollisionStay中持续阻止移动,且未在OnCollisionExit中重置状态。
  3. 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.28 13:44:55