state的定义
在本文中,state指的是MonoBehaviour和ScriptableObject的状态,也就是成员变量(字段),而成员函数定义了对state进行操作的逻辑。
数据冲突的定义
Awake和Start都可以用于初始化状态,但是,我们无法预估它们的执行顺序。于是,对于一个特定的mono1组件,我们可能在Awake(或Start)中对某一状态进行了初始化。但是,却试图在另一个mono2的Awake(或Start)对其进行访问(通过mono对外暴露的方法或直接访问),这样就有可能会导致数据的冲突(访问没有初始化的字段)。之所以说有可能,是因为Awake之间的执行顺序是随机的。如果足够幸运,mono1的Awake执行在前,mono2的Awake执行在后,那么就不会导致数据冲突。但是,如果mono2的Awake执行在前,那么就会导致其试图访问一个没有初始化的状态,如果这个状态是引用类型的,那么就会出现NullReferenceError(空指针)的引用错误,会直接报错。如果这个状态是基本类型的,那么虽然游戏能继续运行,但是就会导致后序程序的执行逻辑不符合我们的预期。基于此,我们必须对Awake和Start的职能进行清晰的划分,遵循一定的原则。
在Awake和Start初始化状态的基本原则
- 当场景中的所有游戏对象都完全生成完毕后,才会调用Awake方法。这时,Awake只适用于初始化mono组件中对其它任意mono组件进行引用的状态(字段)。因为在这一刻,我们可以十分确定场景中的所有mono组件对象都已经完全的生成完毕。但是总体上,为了避免数据冲突的潜在可能,我们不能在Awake中试图访问任何mono组件的状态(直接对状态进行访问或间接的通过mono暴露的函数进行访问),因为这些mono组件也有Awake,可能这个Awake排在后面执行,所以我们试图访问的是未初始化的状态。例如Transform也是一个mono组件,我们不能试图在Awake里面访问transform.position,有可能position是在Awake里初始化的,但是我们不能确保Transform的Awake已经执行。
- 确保场景中所有组件的状态在Start里都能被安全的访问(直接或者间接的),即我们必须确保所有组件的状态都已经在Start之前被初始化了。这就意味着我们应当避免在Start里面对mono组件的状态进行任何初始化,因为正如Awake方法一样,所有组件的Start回调的执行顺序都是随机的,在Mono1组件的Start方法里对某一状态进行初始化,有可能还会在mono2组件的Start方法里对(mono1的)该状态进行访问,从而引入了潜在的数据冲突风险。需要注意的是,我们讨论的风险是潜在的风险(可能在未来发生),例如,在当前的开发进度中,我们在Mono1组件的Start方法里对某一状态进行了初始化,但是在其它mono组件的Start方法里并不存在对该状态的访问,虽然在当前时刻这是安全的,但是如果不遵循上述原则,那么在后续的开发流程中,难保不会疏忽性的在其它mono组件的Start方法里对该状态进行访问。
- 既然在Awake里只适用于