Unity3D_02_基类MonoBehaviour/自带函数以及脚本执行的生命周期

导引:

其中Time,Input,Physics都是Unity中的全局变量。GameObject是游戏中的基本物件。GameObject是由Component组合而成的,GameObject本身必须有Transform的Component,这也加深了我们对GameObject的理解,即GameObject是游戏场景中真实存在,而且有位置的一个物件。

但是我们怎么操纵这个GameObject呢?这就需要引入脚本组件了,也就是所有脚本组件的基类MonoBehaviour。

MonoBehaviour的生命周期:

MonoBehaviour是Unity中所有脚本的基类,如果你使用JS的话,脚本会自动继承MonoBehaviour。如果使用C#的话,你需要显式继承MonoBehaviour。

在我们使用MonoBehaviour的时候,尤其需要注意的是它有哪些可重写函数,这些可重写函数会在游戏中发生某些事件的时候被调用。我们在Unity中最常用到的几个可重写函数是这几个:

Awake:当一个脚本实例被载入时Awake被调用。我们大多在这个类中完成成员变量的初始化 
Start:仅在Update函数第一次被调用前调用。因为它是在Awake之后被调用的,我们可以把一些需要依赖Awake的变量放在Start里面初始化。 同时我们还大多在这个类中执行StartCoroutine进行一些协程的触发。要注意在用C#写脚本时,必须使用StartCoroutine开始一个协程,但是如果使用的是JavaScript,则不需要这么做。 
Update:当MonoBehaviour启用时,其Update在每一帧被调用。 
FixedUpdate:当MonoBehaviour启用时,其 FixedUpdate 在每一固定帧被调用。 
OnEnable:当对象变为可用或激活状态时此函数被调用。 
OnDisable:当对象变为不可用或非激活状态时此函数被调用。 
OnDestroy:当MonoBehaviour将被销毁时,这个函数被调用。

其他情况:

编辑器(Editor) 
Reset:Reset函数被调用来初始化脚本属性当脚本第一次被附到对象上,并且在Reset命令被使用时也会调用。 
编者注:Reset是在用户点击Inspector面板上Reset按钮或者首次添加该组件时被调用。Reset最常用于在见识面板中给定一个默认值。 
第一次场景加载(First Scene Load) 
这些函数会在一个场景开始(场景中每个物体只调用一次)时被调用。

Awake:这个函数总是在任何Start()函数之前一个预设被实例化之后被调用,如果一个GameObject是非激活的(inactive),在启动期间Awake函数是不会被调用的直到它是活动的(active)。 
OnEnable:只有在对象是激活(active)状态下才会被调用,这个函数只有在object被启用(enable)后才会调用。这会发生在一个MonoBehaviour实例被创建,例如当一个关卡被加载或者一个带有脚本组件的GameObject被实例化。 
注意:当一个场景被添加到场景中,所有脚本上的Awake()和OnEable()函数将会被调用在Start()、Update()等它们中任何函数被调用之前。自然的,当一个物体在游戏过程中被实例化时这不能被强制执行。

第一帧更新之前(Before the first frame update)

Start:只要脚本实例被启用了Start()函数将会在Update()函数第一帧之前被调用。 
对于那些被添加到场景中的物体,所有脚本上的Start()函数将会在它们中任何的Update()函数之前被调用,自然的,当一个物体在游戏过程中被实例化时这不能被强制执行。

在帧之间(In between frames)

OnApplicationPause:这个函数将会被调用在暂停被检测有效的在正常的帧更新之间的一帧的结束时。在OnApplicationPause被调用后将会有额外的一帧用来允许游戏显示显示图像表示在暂停状态下。 
更新顺序(Update Order)

当你在跟踪游戏逻辑和状态,动画,相机位置等的时候,有几个不同的事件函数你可以使用。常见的模式是在Update()函数中执行大多数任务,但是也有其它的函数你可以使用。

FixedUpdate: FixedUpdate函数经常会比Update函数更频繁的被调用。它一帧会被调用多次,如果帧率低它可能不会在帧之间被调用,就算帧率是高的。所有的图形计算和更新在FixedUpdate之后会立即执行。当在FixedUpdate里执行移动计算,你并不需要Time.deltaTime乘以你的值,这是因为FixedUpdate是按真实时间,独立于帧率被调用的。

Update: Update每一帧都会被调用,对于帧更新它是主要的负荷函数。 
LateUpdate:LateUpdate会在Update结束之后每一帧被调用,任何计算在Update里执行结束当LateUpdate开始时。LateUpdate常用为第三人称视角相机跟随。 
渲染(Rendering)

OnPreCull: 在相机剔除场景前被调用。剔除是取决于哪些物体对于摄像机是可见的,OnPreCull仅在剔除起作用之前被调用。

OnBecameVisible/OnBecameInvisible:当一个物体对任意摄像机变得可见/不可见时被调用。

OnPreRender:在摄像机开始渲染场景之前调用。

OnRenderObject:在指定场景渲染完成之后调用,你可以使用GL类或者Graphics.DrawMeshNow 来绘制自定义几何体在这里。

OnPostRender:在摄像机完成场景渲染之后调用。

OnRenderImage(Pro Only):在场景徐然完成之后允许屏幕图像后期处理调用。

OnGUI:为了响应GUI事件,每帧会被调用多次(一般最低两次)。布局Layout和Repaint事件会首先处理,接下来处理的是是通过 
Layout和键盘/鼠标事件对应的每个输入事件。

OnDrawGizmos:用于可视化的绘制一些小玩意在场景视图中。 
协同程序(Coroutines)

正常的协同程序更新是在Update函数返回之后运行。一个协同程序是可以暂停执行(yield)直到给出的依从指令(YieldInstruction )完成,写成的不同运用:

yield:在所有的Update函数都已经被调用的下一帧该协程将持续执行。 
yield WaitForSeconds:一段指定的时间延迟之后继续执行,在所有的Update函数完成调用的那一帧之后。

yield WaitForFixedUpdate:所有脚本上的FixedUpdate函数已经执行调用之后持续。

yield WWW:在WWW下载完成之后持续。

yield StartCoroutine:协同程序链,将会等到MuFunc函数协程执行完成首先。 
销毁(When the Object is Destroyed)

OnDestory:这个函数在会在一个对象销毁前一帧调用,会在所有帧更新一个对象存在的最后一帧之后执行,对象也许会响应Object.Destroy 或一个场景关闭时被销毁。 
退出游戏(When Quitting) 
这些函数会在你场景中所有的激活的物体上调用:

OnApplicationQuit:这个函数在应用退出之前的所有游戏物体上调用,在编辑器(Editor)模式中会在用户停止PlayMode时调用,在网页播放器(web player)中会在网页视图关闭时调用。 
OnDisable:当行为变为非启用(disable)或非激活(inactive)时调用。

下面用一张图来更形象地说明一下这几个类的在MonoBehaviour的生命周期中是如何被调用的:

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

Unity系统自带函数:

 1 using UnityEngine;
 2 using System.Collections;
 3
 4 public class test : MonoBehaviour
 5 {
 6
 7     void Awake()
 8     {
 9         print("Awake");
10     }
11
12     void OnEnable()
13     {
14         print("OnEnable");
15     }
16
17     void Start()
18     {
19         print("Start");
20     }
21
22     void Update()
23     {
24         print("Update");
25     }
26
27     void LateUpdate()
28     {
29         print("LateUpdate");
30     }
31
32     void OnGUI()
33     {
34         print("OnGUI");
35     }
36
37     void OnDestroy()
38     {
39         print("OnDestroy");
40     }
41
42     void OnDisable()
43     {
44         print("OnDisable");
45     }
46 }  

自带函数执行顺序如下:

运行时:

结束时: 

会发现结束的时候比运行时多两个方法,OnDisable和OnDestroy,以上就是函数执行的顺序!

脚本的编译顺序

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

由于脚本的编译顺序会涉及到特殊文件夹,比如上面提到的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间存在的依赖关系所决定的。

原文地址:https://www.cnblogs.com/NBOWeb/p/8967661.html

时间: 2024-11-06 05:46:28

Unity3D_02_基类MonoBehaviour/自带函数以及脚本执行的生命周期的相关文章

【Unity3D基础教程】给初学者看的Unity教程(二):所有脚本组件的基类 -- MonoBehaviour的前世今生

作者:王选易,出处:http://www.cnblogs.com/neverdie/ 欢迎转载,也请保留这段声明.如果你喜欢这篇文章,请点[推荐].谢谢! 引子 上一次我们讲了GameObject,Compoent,Time,Input,Physics,其中Time,Input,Physics都是Unity中的全局变量.GameObject是游戏中的基本物件.GameObject是由Component组合而成的,GameObject本身必须有Transform的Component,这也加深了我们

C++的虚基类知识点

当在多条继承路径上有一个公共的基类,在这些路径的某几条汇合处,这个公共的基类就会产生多个实例(或多个副本),若只想保存这个基类的一个实例,可以将这个公共基类说明为虚基类. class x1:virtual public x{//... ...};class x2:virtual public x{//... ...};虚基类的初始化 虚基类(虚拟继承)的初始化与一般多继承的初始化在语法上是一样的,但构造函数的调用次序不同. 派生类的构造函数的调用次序有三个原则:(1)虚基类的构造函数在非虚基类之

C++虚基类详解

1.虚基类的作用从上面的介绍可知:如果一个派生类有多个直接基类,而这些直接基类又有一个共同的基类,则在最终的派生类中会保留该间接共同基类数据成员的多份同名成员.在引用这些同名的成员时,必须在派生类对象名后增加直接基类名,以避免产生二义性,使其惟一地标识一个成员,如    c1.A::display( ).在一个类中保留间接共同基类的多份同名成员,这种现象是人们不希望出现的.C++提供虚基类(virtual base class )的方法,使得在继承间接共同基类时只保留一份成员.现在,将类A声明为

为什么基类的析构函数是虚函数?

1.第一段代码 #include<iostream> using namespace std; class ClxBase{ public: ClxBase() {}; ~ClxBase() { cout << "Output from the destructor of class ClxBase!" << endl; }; void DoSomething() { cout << "Do something in class

转 理解虚基类、虚函数与纯虚函数的概念

原文地址:理解虚基类.虚函数与纯虚函数的概念 引言 一直以来都没有写过一篇关于概念性的文章,因为我觉得这些概念性的东西书本上都有并且说的也很详细写来也无用,今天突发奇想想写 一写,下面就和大家讨论一下虚基类.虚函数与纯虚函数,一看名字就让人很容易觉得混乱.不过不要紧待看完本文后你就会理解了. 正文 虚基类        在说明其作用前先看一段代码 class A { public:     int iValue; }; class B:public A { public:     void bP

C++ 虚基类 派生与继承

在学习设计模式时我就有一个疑问,关联和继承除了用法上的区别,好像在内存上并没有什么区别,继承也是父类作为了子类的元素(内存上),关联也是这样.而且关联好像更占内存一些.这就是设计模式里问题了“依赖倒转原则”. 继承分为public继承,protect继承,private继承 public:父类中的public,protected成员到了派生类中属性不变. protected:父类中的public,protected成员到了派生类中,都变为protected成员. private:父类中的publ

理解虚基类、虚函数与纯虚函数的概念

总结 虚基类     1, 一个类可以在一个类族中既被用作虚基类,也被用作非虚基类.     2, 在派生类的对象中,同名的虚基类只产生一个虚基类子对象,而某个非虚基类产生各自的子对象.     3, 虚基类子对象是由最派生类的构造函数通过调用虚基类的构造函数进行初始化的.     4, 最派生类是指在继承结构中建立对象时所指定的类.     5, 派生类的构造函数的成员初始化列表中必须列出对虚基类构造函数的调用:如果未列出,则表示使用该虚基类的缺省构造函数.     6, 从虚基类直接或间接派

构造函数为什么不能为虚函数 &amp; 基类的析构函数为什么要为虚函数

一.构造函数为什么不能为虚函数 1. 从存储空间角度,虚函数对应一个指向vtable虚函数表的指针,这大家都知道,可是这个指向vtable的指针其实是存储在对象的内存空间的.问题出来了,如果构造函数是虚的,就需要通过 vtable来调用,可是对象还没有实例化,也就是内存空间还没有,怎么找vtable呢?所以构造函数不能是虚函数. 2. 从使用角度,虚函数主要用于在信息不全的情况下,能使重载的函数得到对应的调用.构造函数本身就是要初始化实例,那使用虚函数也没有实际意义呀.所以构造函数没有必要是虚函

为什么基类的析构函数要写成虚函数?

为什么基类的析构函数要写成虚函数? 答:在实现多态时,当用基类操作派生类,在析构时防止只析构基类而不析构派生类的状况发生. 代码说明如下 第一段代码: 1 #include<iostream> 2 using namespace std;   3    4 class ClxBase   5 {public:   6 ClxBase() {}   7 ~ClxBase() {cout << "Output from the destructor of class ClxB