【Unity】【转载】Awake()和Start()的本质区别

本文详细解析了Unity引擎中MonoBehaviour的Awake()与Start()方法的区别,并通过实验对比了它们的调用时机及应用场景,对于Unity游戏开发人员来说具有较高的参考价值。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

版权声明:本文为燕良原创文章,转载请注明出处:https://neil3d.github.io

昨天被问到一个问题MonoBehaviour的Awake()和Start()的区别是啥?

当然Awake()会在Start()之前被调用,这个地球人都知道了。如果只是顺序问题,那当然就没太大必要搞两个函数了。仔细研究了一下API文档:

Awake():Awake is called when the script instance is being loaded.
Start():Start is called on the frame when a script is enabled just before any of the Update methods is called the first time.
OK,从文档中我们看到他俩的区别是: Awake()是在脚本对象实例化时被调用的,而Start()是在对象的第一帧时被调用的,而且是在Update()之前。

为了更明确这点,我们做一个小实验,写一个脚本,用来动态创建另外一个脚本对象:

using UnityEngine;  
using System.Collections;  
  
public class TryObject : MonoBehaviour  
{  
  
    // Use this for initialization  
    void Start()  
    {  
#if true  
        GameObject dynaGO = new GameObject("DynamicGO");  
        dynaGO.AddComponent<DynamicObject>();  
#else  
        Object prefab = Resources.Load("DynamicGO");  
        Object instance = GameObject.Instantiate(prefab);  
#endif  
  
    }  
  
}  

另外一个脚本就写几个空函数,用来打断点:

using UnityEngine;  
using System.Collections;  
  
public class DynamicObject : MonoBehaviour  
{  
    void Awake()  
    {   
    }  
  
    // Use this for initialization  
    void Start()  
    {  
  
    }  
  
    // Update is called once per frame  
    void Update()  
    {  
  
    }  
}  

以下是使用AddComponent()方法时,DynamicObject:Awake()的调用堆栈:
在这里插入图片描述

下面是使用加载prefab的方式时,DynamicObject:Awake()的调用堆栈:

在这里插入图片描述

以下是DynamicObject:Start()的调用堆栈:
在这里插入图片描述

这样的话,前面的结论就更明确了。在使用上,有几点值得注意:
脚本的一些成员,如果想在创建之后的代码中立即使用,则必须写在Awake()里面;
当关卡加载时,脚本的Awake的次序是不能控制的;至于在关卡加载时,对象实例化和Awake()的调用关系,得看源码才知道了。

<think>嗯,用户问的是Unity中的“Object reference not set to an instance of an object”错误的原因。首先,我需要回忆一下这个错误的常见情况。通常这在C#里是NullReferenceException,也就是尝试访问一个未初始化的对象的成员。但在Unity中,可能还有其他特定情况,比如场景中的对象未正确引用或者被销毁了。 应该分点解释可能的原因。比如,公共变量未在Inspector中赋值,或者通过GetComponent获取组件但组件不存在,还有可能在代码中访问已经被Destroy的对象。另外,Unity的生命周期问题,比如在Awake里访问其他对象的Start方法中的初始化变量,可能导致时机不对。 还要考虑是否正确处理了Unity的对象销毁,比如使用Destroy后是否置为null,或者是否在协程中异步操作导致引用失效。另外,预制体实例化后是否正确获取了引用,或者是否在编辑器中未保存的更改导致引用丢失。 需要确保用户理解这些情况,并给出检查建议,比如使用Debug.Log输出检查对象是否为null,检查Inspector中的赋值,确认组件是否存在,以及生命周期顺序是否正确。可能还需要提醒使用TryGetComponent来避免异常,或者在访问前进行null检查。 要注意用中文,结构清晰,分点说明原因解决方法,避免使用Markdown,使用口语化表达,但保持专业性。同时,确保符合系统级指令中的格式要求,比如数学公式的正确使用,但在这个问题中可能用不上数学公式,但如果有涉及计算的地方需要留意。</think>在Unity中,$\text{Object reference not set to an instance of an object}$ 是典型的空引用异常($\text{NullReferenceException}$),以下是逐步分析原因解决思路: --- ### 一、核心原因 本质是**代码试图访问一个未被实例化的对象**。在Unity中具体表现为: 1. 访问未初始化的类实例 2. 引用已被销毁的$\text{GameObject/Component}$ 3. Unity序列化引用丢失 --- ### 二、常见场景解决方法 #### 场景1:Inspector未赋值 ```csharp public Rigidbody rb; // 在Inspector未拖拽赋值 void Start() { rb.AddForce(Vector3.up); // 触发错误 } ``` ✅ **解决方案**: - 检查Inspector面板中对应脚本的公共字段是否已拖拽赋值 - 添加null检查:$\text{if (rb != null) \{ ... \}}$ --- #### 场景2:错误获取组件 ```csharp void Start() { var renderer = GetComponent<Renderer>(); renderer.material.color = Color.red; // 若物体没有Renderer组件则报错 } ``` ✅ **解决方案**: - 使用$\text{TryGetComponent}$替代: ```csharp if (TryGetComponent<Renderer>(out var renderer)) { renderer.material.color = Color.red; } ``` --- #### 场景3:访问已销毁对象 ```csharp void Update() { Destroy(gameObject); Debug.Log(gameObject.name); // 下一帧访问已销毁对象报错 } ``` ✅ **解决方案**: - 销毁后立即停止相关操作 - 添加销毁状态检查:$\text{if (gameObject != null) \{ ... \}}$ --- #### 场景4:生命周期时序错误 ```csharp // ScriptA void Awake() { ScriptB.instance.Initialize(); // ScriptB可能还未初始化 } // ScriptB public static ScriptB instance; void Start() { // 应改用Awake初始化 instance = this; } ``` ✅ **解决方案**: - 统一使用$\text{Awake()}$进行初始化 - 使用$\text{[SerializeField]}$配合Inspector赋值代替静态引用 --- ### 三、调试技巧 1. **断点调试**:在报错行设置断点,检查变量是否为$\text{null}$ 2. **日志输出**:添加$\text{Debug.Log($"对象状态: {obj == null}")}$ 3. **层级检查**:确认场景中物体激活状态脚本执行顺序 --- ### 四、特殊注意事项 - **预制体引用**:预制体修改后可能导致场景实例引用丢失 - **异步操作**:协程中需确保操作完成前对象未被销毁 - **Editor未保存**:测试前保存场景防止临时引用丢失 通过以上步骤系统排查,通常可以准确定位问题根源。记得在关键操作前始终添加null检查以提高代码健壮性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值