Unity脚本执行顺序疑问:OnEnable为何先于Awake触发?
嘿,我完全懂你的困惑——这个问题是Unity脚本执行顺序里一个非常容易踩的坑,我刚接触Unity的时候也被它坑过好几次!
你的认知其实接近正确,但忽略了一个关键细节:Unity并不是先执行所有脚本的Awake,再执行所有脚本的OnEnable。实际的执行逻辑是按每个GameObject的激活状态,结合脚本在「脚本执行顺序」列表中的优先级来处理的。
问题根源拆解
当场景加载时:
- Unity会先处理所有激活状态的GameObject上的脚本:先执行该对象的
Awake,紧接着就执行它的OnEnable(如果对象是激活的)。 - 如果
GameManager所在的GameObject的脚本执行顺序优先级比MainMenuController低,或者GameManager的对象在场景加载时是禁用状态,那么它的Awake(也就是给instance赋值的逻辑)就会晚于MainMenuController的OnEnable执行,这时候你访问GameManager.Instance自然就会拿到null,触发NullReferenceException。
看你的代码,MainMenuController在OnEnable里直接调用Init(),而Init()第一行就访问GameManager.Instance,如果GameManager的Awake还没跑,这个引用肯定是空的。
解决方法(按推荐程度排序)
调整脚本执行顺序:
打开Unity编辑器的Edit > Project Settings > Script Execution Order,把GameManager脚本拖到MainMenuController的上方。这样Unity会保证GameManager的Awake先于MainMenuController的任何生命周期方法执行。把Init逻辑移到Start方法:
Start方法的执行时机是所有脚本的Awake都执行完毕之后,在第一帧Update之前调用。把Init()从OnEnable移到Start,就能确保GameManager.Instance已经被初始化了:private void Start() { Init(); }(注意:如果你的
MainMenuController所在对象会被反复禁用/启用,OnEnable还是会触发,但Start只会执行一次,这时候要结合你的需求调整。)优化GameManager的单例实现:
给单例加一个兜底判断,确保即使Awake没执行,也能自动创建或找到实例:public static GameManager Instance { get { if (instance == null) { // 先找场景里已有的GameManager instance = FindObjectOfType<GameManager>(); // 如果没有,就自动创建一个 if (instance == null) { GameObject gmObject = new GameObject("GameManager"); instance = gmObject.AddComponent<GameManager>(); DontDestroyOnLoad(gmObject); // 跨场景保持单例 } } return instance; } }确保GameManager对象默认激活:
检查场景里的GameManagerGameObject,确保它的激活勾选是打开的。如果对象是禁用状态,它的Awake要等到对象被激活时才会执行,这时候就会晚于MainMenuController的OnEnable。
总结
Unity的生命周期方法执行顺序不是全局统一的「所有Awake→所有OnEnable」,而是按对象激活状态+脚本优先级逐个处理对象的。只要注意单例的初始化时机,或者调整执行顺序,就能解决这个空引用问题啦!
内容的提问来源于stack exchange,提问作者Siddharth

