在Unity中可以同时创建很多脚本,并且可以分别绑定到不同的游戏对象上,它们各自都在自己的生命周期中运行。与脚本有关的也就是编译和执行啦,本文就来研究一下Unity中脚本的编译和执行顺序的问题。
先说一下执行顺序吧。
官方给出的脚本执行顺序如下图:
我们可以做一个小实验来测试一下:
在Hierarchy视图中创建三个游戏对象,在Project视图中创建三条脚本,如下图所示,然后按照顺序将脚本绑定到对应的游戏对象上:
三条脚本的代码完全一样,只是做了一点名称上的区分:
1 using System.Collections; 2 3 public class Scring0 : MonoBehaviour 4 { 5 void Awake() 6 { 7 Debug.Log("Script0 ======= Awake"); 8 } 9 10 bool isUpdate = false; 11 void Update() 12 { 13 if(!isUpdate) 14 { 15 Debug.Log("Script0 ======= Update"); 16 isUpdate = true; 17 } 18 } 19 20 bool isLateUpdate = false; 21 void LateUpdate() 22 { 23 if(!isLateUpdate) 24 { 25 Debug.Log("Script0 ======= LateUpdate"); 26 isLateUpdate = true; 27 } 28 } 29 }
播放游戏,看看它们的执行顺序。如下图所示,Awake、Update、LateUpdate,无论运行游戏多少次,它们的执行顺序是完全一样的。
接着我们再做一个测试,把Script0的Update方法注释掉!!
1 using System.Collections; 2 3 public class Scring0 : MonoBehaviour 4 { 5 void Awake() 6 { 7 Debug.Log("Script0 ======= Awake"); 8 } 9 10 // bool isUpdate = false; 11 // void Update() 12 // { 13 // if(!isUpdate) 14 // { 15 // Debug.Log("Script0 ======= Update"); 16 // isUpdate = true; 17 // } 18 // } 19 20 bool isLateUpdate = false; 21 void LateUpdate() 22 { 23 if(!isLateUpdate) 24 { 25 Debug.Log("Script0 ======= LateUpdate"); 26 isLateUpdate = true; 27 } 28 } 29 }
再次运行游戏,看看它的结果。脚本的执行顺序和以前完全一样,Script0即便删除掉了Update方法,但是它也不会直接执行LateUpdate方法,而是等待Script1和Script2中的Update方法都执行完毕以后,再去执行所有的LateUpdate方法。
通过这两个例子,我们就可以很清楚地断定,Unity后台是如何执行脚本的了。每个脚本的Awake、Start、Update、LateUpdate、FixedUpdate等等,所有的方法在后台都会被汇总到一起:
1 后台的Awake() 2 { 3 脚本0中的Awake(); 4 脚本1中的Awake(); 5 脚本2中的Awake(); 6 }
后台的方法Awake、Update、LateUpdate等等,都是按照顺序,等所有游戏对象上脚本中的Awake执行完毕之后,再去执行Start、Update、LateUpdate等方法的。
1 后台的Update() 2 { 3 脚本0中的Update(); 4 脚本1中的Update(); 5 脚本2中的Update(); 6 }
然后我们来看看这样一种情况:在脚本0的Awake方法中创建一个立方体对象,然后在脚本2的Awake方法中去获取这个立方体对象。代码如下:
1 脚本0的代码: 2 using UnityEngine; 3 using System.Collections; 4 5 public class Script2 : MonoBehaviour 6 { 7 void Awake() 8 { 9 GameObject.CreatePrimitive(PrimitiveType.Cube); 10 } 11 } 12 13 脚本2的代码: 14 using UnityEngine; 15 using System.Collections; 16 17 public class Script0 : MonoBehaviour 18 { 19 void Awake() 20 { 21 GameObject go = GameObject.Find("Cube"); 22 Debug.Log(go.name); 23 } 24 }
如果脚本的执行顺序是先执行Script0,然后再执行Script2,那么Script2中的Awake就可以正确地获取到该立方体对象;可是如果脚本的执行顺序是先执行Script2,然后是Script0,那么Script2肯定会报空指针错误的。
那么实际项目中的脚本会非常多,它们的先后执行顺序我们谁也不知道(又说是按照栈结构来执行的,即后绑定到游戏对象上的脚本先执行。这一点值得研究)。但一般的,建议在Awake方法中创建游戏对象或Resources.Load(Prefab)对象,然后在Start方法中去获取游戏对象或者组件,这样就可以确保万无一失了。
另外,Unity也提供了一个方法来设置脚本的执行顺序,在Edit -> Project Settings -> Script Execution Order菜单项中,可以在Inspector面板中看到如下图所示:
点击右下角的"+"将弹出下拉窗口,包括游戏中的所有脚本。脚本添加完毕后,可以用鼠标拖动脚本来为脚本排序,脚本名后面的数字也越小,脚本越靠上,也就越先执行。其中的Default Time表示没有设置脚本的执行顺序的那些脚本的执行顺序。