支持原文,原文请戳: Unity3D中脚本的执行顺序和编译顺序

在Unity中可以同时创建很多脚本,并且可以分别绑定到不同的游戏对象上,它们各自都在自己的生命周期中运行。与脚本有关的也就是编译和执行啦,本文就来研究一下Unity中脚本的编译和执行顺序的问题。

事件函数的执行顺序

先说一下执行顺序吧。 
官方给出的脚本中事件函数的执行顺序如下图: 

我们可以做一个小实验来测试一下: 
在Hierarchy视图中创建三个游戏对象,在Project视图中创建三条脚本,如下图所示,然后按照顺序将脚本绑定到对应的游戏对象上: 

三条脚本的代码完全一样,只是做了一点名称上的区分:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
using UnityEngine;
using System.Collections;
public class Scring0 : MonoBehaviour
{
    void Awake()
    {
        Debug.Log("Script0 ======= Awake");
    }
 
    bool isUpdate = false;
    void Update()
    {
        if(!isUpdate)
        {
            Debug.Log("Script0 ======= Update");
            isUpdate = true;
        }
    }
 
    bool isLateUpdate = false;
    void LateUpdate()
    {
        if(!isLateUpdate)
        {
            Debug.Log("Script0 ======= LateUpdate");
            isLateUpdate = true;
        }
    }
}

播放游戏,看看它们的执行顺序。如下图所示,Awake、Update、LateUpdate,无论运行游戏多少次,它们的执行顺序是完全一样的。 


接着我们再做一个测试,把Script0的Update方法注释掉!!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
using UnityEngine;
using System.Collections;
public class Script0 : MonoBehaviour
{
  
    void Awake ()
    {
        Debug.Log("Script0 ========= Awake");
    }
  
//  bool isUpdate = false;
//  void Update ()
//  {
//      if(!isUpdate)
//      {
//          Debug.Log("Script0 ========= Update");
//          isUpdate = true;
//      }
//  }
  
    bool isLateUpdate = false;
    void LateUpdate()
    {
        if(!isLateUpdate)
        {
            Debug.Log("Script0 ========= LateUpdate");
            isLateUpdate = true;
        }
    }
}

再次运行游戏,看看它的结果。脚本的执行顺序和以前完全一样,Script0即便删除掉了Update方法,但是它也不会直接执行LateUpdate方法,而是等待Script1和Script2中的Update方法都执行完毕以后,再去执行所有的LateUpdate方法。 


通过这两个例子,我们就可以很清楚地断定,Unity后台是如何执行脚本的了。每个脚本的Awake、Start、Update、LateUpdate、FixedUpdate等等,所有的方法在后台都会被汇总到一起:

1
2
3
4
5
6
7
后台的Awake()
{
    // 这里暂时按照上图中的脚本执行顺序,后面会谈到其实可以自定义该顺序的
    脚本2中的Awake();
    脚本1中的Awake();
    脚本0中的Awake();
}

后台的方法Awake、Update、LateUpdate等等,都是按照顺序,等所有游戏对象上脚本中的Awake执行完毕之后,再去执行Start、Update、LateUpdate等方法的。

1
2
3
4
5
6
7
后台的Update()
{
    // 这里暂时按照上图中的脚本执行顺序,后面会谈到其实可以自定义该顺序的
    脚本2中的Update();
    脚本1中的Update();
    脚本0中的Update();
}

脚本的执行顺序

然后我们来看看这样一种情况:在脚本0的Awake方法中创建一个立方体对象,然后在脚本2的Awake方法中去获取这个立方体对象。代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// Script0.cs
using UnityEngine;
using System.Collections;
public class Script0 : MonoBehaviour
{
    void Awake ()
    {
        GameObject.CreatePrimitive(PrimitiveType.Cube);
    }
}
 
// Script2.cs
using UnityEngine;
using System.Collections;
public class Script2 : MonoBehaviour
{
    void Awake ()
    {
        GameObject go = GameObject.Find("Cube");
        Debug.Log(go.name);
    }
}

如果脚本的执行顺序是先执行Script0,然后再执行Script2,那么Script2中的Awake就可以正确地获取到该立方体对象;可是如果脚本的执行顺序是先执行Script2,然后是Script0,那么Script2肯定会报空指针错误的。

那么实际项目中的脚本会非常多,它们的先后执行顺序我们谁也不知道(有人说是按照栈结构来执行的,即后绑定到游戏对象上的脚本先执行。这一点可以从上面的例子中得到,但官方并没有这么说,还得进一步深入研究)。但一般的,建议在Awake方法中创建游戏对象或Resources.Load(Prefab)对象,然后在Start方法中去获取游戏对象或者组件,因为事件函数的执行顺序是固定的,这样就可以确保万无一失了。 
另外,Unity也提供了一个方法来设置脚本的执行顺序,在Edit -> Project Settings -> Script Execution Order菜单项中,可以在Inspector面板中看到如下图所示: 

点击右下角的"+"将弹出下拉窗口,包括游戏中的所有脚本。脚本添加完毕后,可以用鼠标拖动脚本来为脚本排序,脚本名后面的数字也越小,脚本越靠上,也就越先执行。其中的Default Time表示没有设置脚本的执行顺序的那些脚本的执行顺序。 

按照上面这张图的设置,我们再来看一下控制台的输出结果,来确认一下我们的设置是否起作用(注意:把Script0脚本中的Update方法取消注释): 


脚本的编译顺序

关于脚本的编译顺序很是头疼,官方的说法有点模糊,请看官方的解释: 

由于脚本的编译顺序会涉及到特殊文件夹,比如上面提到的Plugins、Editor还有Standard Assets等标准的资源文件夹,所以脚本的放置位置就非常重要了。下面用一个例子来说明不同文件夹中的脚本的编译顺序:

实际上,如果你细心的话会发现,如果在你的项目中建立如上图所示的文件夹层次结构时,编译项目之后会在项目文件夹中生成一些文件名中包含Editor、firstpass这些字样的项目文件。比如按照上图的文件夹结构,我们打开项目文件夹来看一下产生的项目文件是什么样的? 

下面就来详细探讨一下这些个字样是什么意思?它们与脚本的编译顺序有着怎样的联系?


1、首先从脚本语言类型来看,Unity3d支持3种脚本语言,都会被编译成CLI的DLL

如果项目中包含有C#脚本,那么Unity3d会产生以Assembly-CSharp为前缀的工程,名字中包含”vs”的是产生给Vistual Studio使用的,不包含”vs”的是产生给MonoDevelop使用的。

项目中的脚本语言 工程前缀 工程后缀
C# Assembly-CSharp csproj
UnityScript Assembly-UnityScript unityproj
Boo Assembly-Boo booproj

如果项目中这三种脚本都存在,那么Unity将会生成3种前缀类型的工程。

2、对于每一种脚本语言,根据脚本放置的位置(其实也部分根据脚本的作用,比如编辑器扩展脚本,就必须放在Editor文件夹下),Unity会生成4中后缀的工程。其中的firstpass表示先编译,Editor表示放在Editor文件夹下的脚本。

在上面的示例中,我们得到了两套项目工程文件:分别被Virtual Studio和MonoDevelop使用(后缀包不包含vs),为简单起见,我们只分析vs项目。得到的文件列表如下: 
Assembly-CSharp-filepass-vs.csproj 
Assembly-CSharp-Editor-filepass-vs.csproj 
Assembly-CSharp-vs.csproj 
Assembly-CSharp-Editor-vs.csproj

根据官方的解释,它们的编译顺序如下: 
(1)所有在Standard Assets、Pro Standard Assets或者Plugins文件夹中的脚本会产生一个Assembly-CSharp-filepass-vs.csproj文件,并且先编译; 
(2)所有在Standard Assets/Editor、Pro Standard Assets/Editor或者Plugins/Editor文件夹中的脚本产生Assembly-CSharp-Editor-filepass-vs.csproj工程文件,接着编译; 
(3)所有在Assets/Editor外面的,并且不在(1),(2)中的脚本文件(一般这些脚本就是我们自己写的非编辑器扩展脚本)会产生Assembly-CSharp-vs.csproj工程文件,被编译; 
(4)所有在Assets/Editor中的脚本产生一个Assembly-CSharp-Editor-vs.csproj工程文件,被编译。

之所以按照这样建立工程并按此顺序编译,也是因为DLL间存在的依赖关系所决定的。

好了,到此为止,我们可以很容易地判断出上面举的实例中,脚本的编译顺序(实际上,我已经把顺序写在了脚本的文件名中了)

小练习

一个Unity3d的工程中,最多可以产生多少个工程文件呢?

4*3*2=24

写在最后的话

以上内容参考的文章有: 
1. Unity3D研究院之深入理解Unity脚本的执行顺序(六十二) 
2. 全面解析Unity3D自动生成的脚本工程文件

【转】Unity3D中脚本的执行顺序和编译顺序的更多相关文章

  1. Unity3D中脚本的执行顺序和编译顺序

    http://www.cnblogs.com/champ/p/execorder.html 在Unity中可以同时创建很多脚本,并且可以分别绑定到不同的游戏对象上,它们各自都在自己的生命周期中运行.与 ...

  2. 【转】Unity3D中脚本的执行顺序和编译顺序(vs工程引用关系)

    http://www.cnblogs.com/champ/p/execorder.html 在Unity中可以同时创建很多脚本,并且可以分别绑定到不同的游戏对象上,它们各自都在自己的生命周期中运行.与 ...

  3. (转)Unity3D中脚本的执行顺序和编译顺序(vs工程引用关系)

    自:http://www.cnblogs.com/champ/p/execorder.html 在Unity中可以同时创建很多脚本,并且可以分别绑定到不同的游戏对象上,它们各自都在自己的生命周期中运行 ...

  4. php脚本的执行过程(编译与执行相分离)

    php脚本的执行过程(编译与执行相分离) 深入理解PHP代码的执行的过程 PHP程序的执行流程 Apache + PHP 的并发访问

  5. Unity基础-脚本的加载与编译顺序

    脚本的加载与编译顺序 C#是以Assembly(汇编集)为一个基本单元组织代码的,dll就是一个assembly,dll之间有加载以来顺序 Assets/*.dll Stamdard Assets/* ...

  6. Unity中脚本的执行顺序总结(@WhiteTaken)

    (Editor)以上是Unity官方文档中的截图,脚本在被挂载到物体上,会启用Editor的方法Reset. (Initialization)当执行脚本开始,初始化的过程中,依次执行的是Awake-& ...

  7. Postman集合/文件夹/请求中脚本的执行顺序

    Postman的Collection(集合)/Folder(集合的子文件夹)/Request(请求)中都有Pre-request Script(请求前脚本)和Tests(请求后脚本) 这个功能类似于不 ...

  8. Unity3D中关于场景销毁时事件调用顺序的一点记录

    先说一下我遇到的问题,我弄了一个对象池管理多个对象,对象池绑定在一个GameObject上,每个对象在OnBecameInvisible时会进行回收(即移出屏幕就回收),但是当场景切换或停止运行程序时 ...

  9. (转)unity3d中脚本生命周期(MonoBehaviour lifecycle)

    自:http://blog.csdn.net/qitian67/article/details/18516503 最近在做一个小示例,发现类继承于MonoBehaviour的类,有很多个方法,于是乎必 ...

随机推荐

  1. 解决linux的centos版本修改时间重启后无效的问题

    安装完centos后,发现时间与本地时间不匹配,在网上找了好多的办法,但是一直没有奏效,重启之后,又恢复为原来的时间.很是纳闷.最后抱着试一试的心态加上了这句指令: ln -sf /usr/share ...

  2. iOS 10 的一个重要更新-新的通知推送 API

    iOS 10 最重要的变化可能就是通知 API 的重构了.本文用一个简单闹钟的例子介绍了 User Notification 的 API 变化和新功能. 简介 很久以前,开发者就可以在 iOS 里预约 ...

  3. Windows Sockets错误标识及对应解释

    WSAETIMEDOUT 对应 linux 的 ETIMEDOUT WSAETIMEDOUT 对应 linux 的 ETIMEDOUT IdWinsock2.pas 文件中也有说明. Windows ...

  4. HDU 4302 Holedox Eating (STL + 模拟)

    Holedox Eating Time Limit: 4000/2000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)To ...

  5. 【JQuery】jquery对象和javascript对象即DOM对象相互转换

    jQuery 对象是通过 jQuery 包装DOM 对象后产生的对象.jQuery 对象是 jQuery 独有的,其可以使用 jQuery 里的方法,但是不能使用 DOM 的方法:例如: $(&quo ...

  6. hadoop相关内容

    数据库导出到hadoop http://www.ibm.com/developerworks/cn/data/library/techarticle/dm-1212liuqy/ http://www. ...

  7. 【转】Currying 的局限性

    Currying 的局限性 很多基于 lambda calculus 的程序语言,比如 ML 和 Haskell,都习惯用一种叫做 currying 的手法来表示函数.比如,如果你在 Haskell ...

  8. 跟我学SharePoint2013视频培训课程——设置列表名称、描述、导航等基本信息(12)

    课程简介 第12天,怎样在SharePoint 2013设置列表名称.描述.导航等基本信息. 视频 SharePoint 2013 交流群 41032413

  9. SharePoint 2013 启用 以其他用户身份登陆(Sign in as different user)

    习惯于SharePoint 2010的用户会发现,SharePoint 2013默认把  以其他用户身份登陆(Sign in as different user)的选项去掉了,这对于开发人员来说很麻烦 ...

  10. OLTP和OLAP有何区别?

    OLTP即联机事务处理,就是我们经常说的关系数据库,意即记录即时的增.删.改.查,就是我们经常应用的东西,这是数据库的基础:OLAP即联机分析处理,是数据仓库的核心部心,所谓数据仓库是对于大量已经由O ...