Unity的使用(三):编译顺序,文件类型和生命周期

我们写好的脚本,是经过编译之后才被使用,而Unity游戏引擎对脚本的编译顺序也是顺序的,了解这个顺序会对程序编码大有裨益。同时,写在我们脚本中的函数也是按一个固定的执行顺序执行的,这个固定的执行顺序就是Unity的生命周期,这是Unity学习者必须掌握的。

一. Unity中脚本编译顺序

编译的原则是在第一个引用前编译它,因此,将脚本放在Assets不同文件夹下不同位置会有不同的编译顺序。官方给出的编译顺序如下:
官方脚本编译顺序
其中各个特殊文件夹是什么请参考本系列的第一篇文章。可以看到,编译一共分为四个阶段,两大块(标准资源配置块和自定义块),且每个块下的Editor文件夹中的资源编译靠后(可以理解Editor编写的为引擎工具,是对其所属块内资源的编辑使用,因此要靠后编译)。

二. Unity中的文件类型

在Unity中一般存在这么几种文件类型:资源文件(Imported Assset),代码文件,序列化文件(Native Asset),文本文档,非序列化文件,meta文件。

1. 编译产生的文件

每个编译阶段都会产生对应的项目文件。以C#为例,当项目中包含有C#脚本时,Unity引擎会产出以Assembly-CSharp为前缀,csproj为后缀的文件,名称中包含“vs”表示是给Visual Studio用的,不含则是给Mono Develop用的。
四个编译阶段产生的对应项目文件
可能部分初学者没看过Assembly-CSharp-firstpass.csproj文件,那是因为你的项目里没有Standard Assets, Pro Standard Assets和Plugins三个文件夹中的任何一个,自然也不会编译产生这个项目文件。

在产生这些项目文件的编译过程中,同时也会在工程根目录/Library/ScriptAssemblies下生成dll,分别为:
Assembly-CSharp-Editor.dll:包含所有Editor下的脚本;
Assembly-CSharp-firstpass.dll:包含Standard Assets, Pro Standard Assets, Plugins文件夹下的脚本(不含Editor文件夹)
Assembly-CSharp.dll:包含除了以上两种所有位于Assets目录中的脚本;

不同语言的脚本,编译出的工程文件的前后缀也不同,也会生成不同的dll文件,下面为不同语言对应的前后缀:
不同语言对应的前后缀

2. Assets中常见文件类型

资源文件(Imported Asset):指一些创建好,并且不再修改的文件。比如美术给的FBX文件,贴图,音频,视频等等。这类文件在导入时都会对应一个Asset Importer,生成对应的.meta文件和存储在Library目录下Unity可识别的内部格式。每次修改原始文件,Unity都会重新导入一次,当删除Library或里面某个文件,会让Unity重新导入相应的资源,不会对工程造成影响;

代码文件:包括所有的代码文件,代码库文件,shader文件等,在导入时Unity会进行一次编译。

序列化文件(Nativa Asset):序列化文件是指Unity能够序列化的文件,一般为Unity自身的类型,如prefab,场景文件,.mat文件(材质球),asset(ScriptableObject)文件。这些文件能够在运行时直接反序列化为对应类的一个实例。

文本文档:不是序列化文件,但是Unity可以识别为TextAsset,如txt, xml, json等;

非序列化文件:Unity无法识别的文件。

3. meta文件

Assets文件夹中所有文件,文件夹,经过Unity导入过程后,都会在其同目录下生成一个.meta文件,该文件Unity内部用来管理文件的,记录着重要内容。meta文件本质上是一个使用YAML格式编写的文本文档。1

FBX文件的meta文件

GUID

guid,global unique ID,是meta中最重要的数据。它是该项目中全局唯一的,代表了这个文件。只要通过这个GUID就可以找到工程中的这个文件。对于Unity的序列文件来说,引用的对象就是这个GUID,故一旦meta中GUID变更了,会造成一场引用丢失的灾难。

ImportSetting数据

当该文件为从外部导入的资源文件,那么会有一个对应的ImportSetting。不同文件类型对应着不同的ImportSetting数据,如:NativeFormatImporter, ModelImporter, AudioImporter等等。ImportSetting中每一行都对应着Inspector面板中的条目。因此,当我们将一个文件和该文件的meta文件从一个Unity工程复制到另一个工程中,它的配置是不会变的。

FileID

当一个文件下有多个其它文件,如一个图集下面有若干图片,那么一个GUID怎么对应其下的文件呢?这个时候就需要FileID,通过GUID找到任何一个文件,再通过FileID找到其中某个子文件。

对于非序列化的资源文件,由于不会更改源文件,所以FileID存储在meta文件中,像FBX文件,图集就是这种类型;对于序列化文件,其自身数据中存储自身的FileID,也会记录所有子文件的FileID,因此mete文件中只有自身的FileID,如AnimatorController, prefab等。

4. 其它特殊文件

当文件名为"CVS"或者该文件拓展名为".tmp"时,该文件会被隐藏(在Project视图中不可见)。

三. Unity的生命周期

对于游戏场景中所有继承了MonoBehaviour的组件(自定义脚本),MonoBehaviour提供了一系列函数供调用。当游戏运行时,进程会按照生命周期中函数的顺序自动调用,如:当执行完所有游戏物体的OnMouseXXX()后才会执行所有游戏物体的Update()。

这里推荐一篇关于Unity生命周期的文章。下面为Unity生命周期图:
起始部分
物理更新部分
逻辑更新
渲染更新
进程结束
开发中生命周期需要注意的地方:

  1. 生命周期中的函数2都与游戏物体是否激活有关,当游戏物体的状态为激死状态时,生命周期中的函数都无法被调用,但是已经正在执行的生命周期函数会继续执行直到完成外部依旧可以对游戏物体的状态参量(如transform, 脚本状态等)进行更改,依旧可以使用脚本中的属性,函数方法
  2. Awake() 在Monobehaviour创建后且游戏对象初始状态为激活,脚本甚至都可以是不激活状态的情况下,会被立刻调用且在整个生命周期仅执行一次(除非将该游戏物体销毁再重新创建或重新加载场景);如果创建的游戏物体对象初始状态为关闭,那么Awake()函数不会执行。
  3. OnEnable() 启用函数,当对应脚本被启用的时候执行(如果原脚本已经是启用状态则不会);如gameObject.SetActive(true), enabled = true。
  4. Start() 同样也只会执行一次, 且一定在OnEnable()之后执行,Start()只在Monobehaviour创建后Update()函数第一次执行前执行一次和Awake()不同的是,Start()是若创建游戏物体,游戏物体为激活状态(gameObject.activeSelf == true)但物体上绑定的脚本实例被禁用(GetComponent<“脚本名”>().enabled == false)的话,那么Start()不会执行;若是在运行中创建新的游戏物体,即便游戏物体和脚本实例都为激活状态,Start()也是在下一帧执行;而Awake()是只需要创建的游戏物体是激活状态就会执行,和脚本实例状态无关。3
    因为Awake()在Start()前面执行,因此可以在进入新场景后,在Awake()里提前关闭某些脚本,防止脚本中的Start()函数执行。
  5. FixedUpdate() 固定多少秒更新(默认为0.02s,可以在Edit -> Project Settings -> Time里设置Fixed Timestep),如果因为计算量庞大而卡顿的话,该函数可能会在同一帧里比Update()多执行几次以保证固定频率;当然也可能比Update()慢而少执行。因此该函数往往用来处理和物理组件相关的更新。4
  6. Update() 逻辑更新函数,每帧调用一次。
  7. 协程在其挂载的游戏物体被激死的情况下是不会再被调用的,即使激死之后重新激活游戏物体,激死前正在执行的协程也不会继续执行。因此为了防止协程中断,往往采用外挂协程(即让一个游戏物体专门进行协程的管理,该游戏物体不会被激死)
  8. LateUpdate() 稍后更新函数,处在逻辑更新循环的最后部分,往往用来处理摄像机镜头的移动。5
  9. OnGUI() 用来对Unity中GUI进行渲染的函数,调用次数是Update()的两倍;GUI是用来绘制UI的,直接也只能在OnGUI()函数里使用GUI类来绘制界面(有了UGUI已经很少有人用了)。
  10. OnDisable() 禁用函数,和OnEnable()刚好相反,在销毁游戏物体或禁用组件时会被调用。
  11. OnDestroy() 销毁函数,当脚本所处的游戏物体被销毁时调用(同时先调用OnDisable())。

  1. Unity内部的序列化文件都是用这个格式类写的,如prefab, 场景等; ↩︎

  2. Reset()函数是在编辑模式下使用的,严格来说不属于生命周期,Reset()函数在将该脚本附加到游戏物体上时或点击组件栏右上角菜单下的Reset选项时调用; ↩︎

  3. 这里还要强调一下生命周期中创建脚本实例的情况:在创建脚本实例过程中,若创建带有该脚本的游戏物体且该游戏物体为激活状态(创建后立刻被激死也不行),则该脚本的Awake()在创建过程中会立刻执行,而该脚本的Start()则根据执行添加该脚本时的生命周期不同而有所区别:若该脚本实例在Awake()中创建则在当前帧和其它要执行的Start()一起执行;若是在Start()或之后的生命周期中里被创建则会延后到下一帧执行。 ↩︎

  4. 改变Time.timeScale将会对FixedUpdate()函数的执行造成影响。因为FixedUpdate()是根据时间来执行的,而Time.timeScale会对游戏中的时间进行缩放(虽然不会影响真实过了的时间)。因此,随着Time.timeScale的增大,FixedUpdate()执行的频率将会越来越高;而当Time.timeScale为0时,FixedUpdate()将会停止执行。(其它的如WaitForSeconds()和时间相关的受同样的影响); ↩︎

  5. Time.timeScale不会影响Update()和LateUpdate()的执行速度; ↩︎

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值