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
相关产品推荐
相关产品推荐

