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

Unity中与新生成GameObject碰撞时如何避免物体位置偏移

问题根因

位置偏移完全是物理引擎碰撞解算导致的必然结果:

  • OnTriggerEnter触发的瞬间,原有移动物体的碰撞体受离散碰撞检测精度影响,已经部分进入可收集对象的触发范围,此时你在可收集对象的坐标位置生成新移动物体,两个带碰撞体、带刚体的物体会出现瞬间几何重叠
  • Unity物理引擎会在当前物理tick自动对重叠的碰撞体施加分离冲量,强行把两个物体推开,这就是你看到的微小位置偏移。你代码里提前禁用可收集对象碰撞体的逻辑完全不影响这个结果——碰撞是发生在新旧两个移动物体之间的,和已经被禁用的可收集对象碰撞体没关系。
解决方案(按实现成本从低到高排序)

方案1:临时忽略初始碰撞(最小改动,兼容所有需求)

这个方案不需要改项目设置,不需要改现有移动逻辑,只需要修改Collectable脚本即可,兼容后续移动物体之间需要碰撞交互的需求:
生成新物体后临时关闭新旧两个物体碰撞体的碰撞检测,等固定物理步(足够原物体向前移动离开重叠区域)再恢复碰撞即可。
修改后的代码:

using System.Collections;
using UnityEngine;

public class Collectable : MonoBehaviour
{
    [SerializeField] private GameObject movingPrefab;
    // 可根据自己的移动速度、碰撞体尺寸调整等待的物理步数量
    [SerializeField] private int waitPhysicsStepCount = 3;
    
    private void OnTriggerEnter(Collider other)
    {
        // 禁用自身触发器防止重复触发
        GetComponent<Collider>().enabled = false;
        // 生成新的移动物体
        GameObject newMovingInstance = Instantiate(movingPrefab, transform.position, transform.rotation);
        
        // 取新旧物体的碰撞体,临时忽略二者碰撞
        Collider oldCollider = other.GetComponent<Collider>();
        Collider newCollider = newMovingInstance.GetComponent<Collider>();
        Physics.IgnoreCollision(oldCollider, newCollider, true);
        
        // 延迟指定物理步后恢复碰撞
        StartCoroutine(RestoreCollision(oldCollider, newCollider));
        
        Destroy(gameObject);
    }

    private IEnumerator RestoreCollision(Collider colA, Collider colB)
    {
        for (int i = 0; i < waitPhysicsStepCount; i++)
        {
            yield return new WaitForFixedUpdate();
        }
        
        // 两个物体都没被销毁的话就恢复碰撞检测
        if (colA != null && colB != null)
        {
            Physics.IgnoreCollision(colA, colB, false);
        }
    }
}

方案2:层碰撞矩阵隔离(适合同类移动物体无需交互的场景)

如果你做的这类移动物体本来就不需要和同类产生任何碰撞交互,直接用Unity原生的层碰撞过滤从根源屏蔽碰撞,性能最好,代码改动量极小:

  • 打开Unity Layer设置,新建一个专属层,比如命名为MovingEntity
  • 把移动物体预制体的Layer设置为这个新建层
  • 打开Edit > Project Settings > Physics面板,找到Layer Collision Matrix,取消勾选MovingEntity层和自身的碰撞对应选项
    设置完成后所有同类移动物体之间完全不会触发碰撞检测,根本不会产生分离冲量,你原来的Collectable脚本不需要做任何修改就能实现平滑衔接。
    注意:如果后续需求里要求移动物体之间有碰撞、阻挡、击退之类的交互,不要用这个方案,选方案1。

不推荐的方案

不要靠调整生成位置偏移、延迟一帧生成、开关刚体isKinematic属性的方式解决问题,这些方案都强依赖你当前的移动速度、碰撞体尺寸参数,后续改任何数值都要重新调偏移量,很容易出穿模、卡顿的bug。

额外优化建议

你现在写的MoveForwardBehaviour每帧FixedUpdate都调用GetComponent<Rigidbody>(),属于没必要的性能浪费,建议在Awake阶段缓存刚体引用,优化后代码:

using UnityEngine;

public class MoveForwardBehaviour : MonoBehaviour
{
    private Rigidbody _rb;

    private void Awake()
    {
        _rb = GetComponent<Rigidbody>();
    }

    private void FixedUpdate()
    {
        _rb.velocity = Vector3.forward;
    }
}

内容的提问来源于stack exchange,提问作者Question3r

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.29 08:15:43