Unity中OnCollisionEnter(other)回调的调用顺序由什么决定?
Unity 碰撞回调顺序问题解决方案
调用顺序的官方规则
Unity 未对两个碰撞物体的
OnCollisionEnter同帧执行顺序做任何稳定的公开约定,你测试中观测到的Mover先执行的表现仅为当前运行环境的临时结果,跨版本、跨平台都可能发生变化,绝对不能作为逻辑依赖。
你提到的「层级窗口排序决定执行顺序」的说法是错误的,只有Edit > Project Settings > Script Execution Order中设置的脚本优先级可以控制回调执行顺序,但该方案仅能临时解决表层问题,扩展性极差,不推荐使用。
问题核心
你当前代码的问题是将「状态修改」和「依赖对方状态的读取逻辑」都放在了同一个OnCollisionEnter回调阶段,两个回调执行顺序不确定,自然会产生竞态,导致结果不符合预期。
推荐解决方案
方案1:单脚本集中处理碰撞逻辑(最优)
把同一碰撞事件的所有相关逻辑全部放在其中一个脚本里执行,避免两边重复处理,从根源上消除顺序依赖。按照你的需求,只需要保留Mover的碰撞回调,删除Obstacle的碰撞回调即可:
// Obstacle 脚本简化后 public class Obstacle : MonoBehaviour { private void Start() { this.tag = "Unhitted"; } }
// Mover 脚本修改后 public class Mover : MonoBehaviour { private int hits = 0; private void OnCollisionEnter(Collision other) { Debug.Log("Mover has collision."); if (other.gameObject.TryGetComponent(out Obstacle obstacle) && obstacle.CompareTag("Unhitted")) { obstacle.tag = "Hitted"; Debug.Log($"The mover has had {++hits} times collision"); } } }
该方案逻辑清晰,执行结果100%确定,没有任何额外副作用。
方案2:拆分状态修改和读取阶段
如果确实有需要两边都处理碰撞的场景,可以把逻辑拆分到不同的Unity执行阶段:
- 所有
OnCollisionEnter中仅做自身状态修改,不要读取对方在本次碰撞阶段修改的状态 - 把依赖对方最新状态的逻辑,放到当前物理帧所有碰撞回调执行完毕后再执行,比如放在
LateUpdate或者下一次FixedUpdate中
Unity的物理执行顺序保证:同一物理帧产生的所有OnCollisionEnter回调,会在本次FixedUpdate结束后、Update开始前全部执行完毕,延迟到后续阶段读取状态就不会读到中间态。
内容的提问来源于stack exchange,提问作者nowisbest
相关产品推荐
相关产品推荐

