> 技术文档 > Unity笔记(一)——生命周期函数、Inspector面板、MonoBehavior、GameObject_unity 生命周期

Unity笔记(一)——生命周期函数、Inspector面板、MonoBehavior、GameObject_unity 生命周期


写在前面

写本系列的目的(自用)是回顾已经学过的知识、记录新学习的知识或是记录心得理解,方便自己以后快速复习,减少遗忘。这里只有部分C#相关语法知识,内容是跟着唐老师学的。

一、生命周期函数

1、帧的概念

游戏本质与帧的关系:游戏的本质就是⼀个死循环,每次循环处理游戏逻辑就会更新⼀次画面、当切换画面的速度达到⼀定时(通常24/秒),⼈眼就会认为画面是流畅的。

常见帧率:

60帧:每帧时间=1000ms / 60 ≈ 16.66ms

30帧:每帧时间=1000ms / 30 ≈ 33.33ms

⼈眼舒适帧率:人眼在放松状态下可视帧数为每秒24

Unity已经内置了死循环机制,开发者不需要自己编写while循环,通过使用Unity提供的生命周期函数来处理游戏逻辑。

2、生命周期函数

(1)Awake函数

AwakeUnity中的生命周期函数,在对象创建时自动调用。类似构造函数的存在(但不能带参数),用于对象创建时的初始化操作。⼀个对象在其生命周期中只会调用⼀次(仅与脚本实例创建有关)。

不写访问修饰符时默认是私有函数。

(2)OnEnable生命周期函数

当脚本依附的GameObject对象每次激活时自动调用。​​​​​​​且相同来源的打印信息会被折叠显示,但不同激活方式(初始创建vs手动激活)会产生不同来源的打印

用处是,当需要对象被激活时执行特定逻辑处理的场景,可以编写OnEnable生命周期函数。

(3)Start生命周期函数

从对象被创建出来后,第⼀次帧更新之前调用。⼀个对象只会调用一次。

与Awake的区别是:

Awake:对象生成时立即调用,类似构造函数

Start:在Awake之后,第⼀次帧更新之前调用

例如,​在Update中动态创建的对象会立即执行Awake,Start会等到当前帧结束后,下⼀帧开始前才执行。

用处是用于对象初始化。

(4)FixedUpdate生命周期函数

主要用于进行物理相关的处理,如碰撞检测等物理计算。如果不做物理相关的游戏开发,基本上很少使用该函数。

Unity内部实现了⼀个死循环,以固定间隔时间(物理帧间隔)执行FixedUpdate

(5)Update生命周期函数

处理游戏核心逻辑更新,如位移计算等;每帧执行,帧率可通过项目设置控制;Unity通过反射在每次游戏循环时自动调用;是编写游戏逻辑的主要接口,会频繁使用。

例如,在Update中,可以通过每帧修改位置实现平滑移动效果。

(6)LateUpdate生命周期函数

处理摄像机位置更新等渲染相关逻辑,在Update之后执行,两者之间Unity会处理动画更新,这样可以避免在Update中更新摄像机导致的渲染错误。

(7)OnDisable生命周期函数

​​​​​​​当依附的GameObject对象每次失活时调⽤,与OnEnable形成对应关系。​​​​​

对象失活后,所有生命周期函数(特别是循环更新类函数)都会停止调用。

核心作用:​​​​​​​用于在对象失活时进行特定逻辑处理,适合存放状态保存、资源释放等代码

(8)OnDestroy生命周期函数

当依附的GameObject对象被销毁(从场景中移除)时调用。

销毁时会先调用OnDisable再调⽤OnDestroy,形成完整生命周期闭环。只会调用一次。

二、Inspector窗口的变量

1、让私有的和保护的也可以被显示和编辑

在脚本中创建的变量,如果不是public的,在Inspector面板上就不会被显示。

但也可以通过特性,让私有的和保护的也可以被显示和编辑。

可以在变量前添加[SerializeField]特性,这样私有的和保护的变量都可以在面板上显示和编辑。

public class inspec : MonoBehaviour{ [SerializeField] private int a = 10;}

面板:

2、让公共的不能被显示和编辑

在脚本中创建的变量,可以通过特性,让公共的也不可以被显示和编辑。

可以在变量前添加[HideInInspector]特性,这样公共的也不可以在面板上显示和编辑。

public class inspec : MonoBehaviour{ [HideInInspector] public string str = \"HELLO\";}

3、小部分类型有显示限制

我们所熟知的各种类:数组、列表、枚举、整型、浮点型、字符串等,只要是公共的或是加了可编辑特性的,都可以在Inspector面板上显示和编辑。

但字典、自定义结构体、自定义类哪怕是公共的也不支持显示。​​​​​​​Unity默认不支持复杂数据结构的序列化显示。

但如果在自定义结构体或是自定义类前,加上特性[System.Serializable],那么自定义的结构体或是自定义类也能够显示并编辑了

[System.Serializable]public class Myclass{ public string name; public int age;}public class inspec : MonoBehaviour{ public Myclass myclass;}

但是字典仍然不可以。

三、MonoBehavior

1、重要成员

(1)获取依附的GameObject

通过MonoBehavior中的成员,可以获取当前脚本依附的对象的各种信息。名字、位置、将当前依附的脚本进行激活、失活。

以下是获取信息的函数。

void Start(){ //获取名字 print(this.gameObject.name); //获取位置信息 //获取位置 print(this.transform.position); //获取角度 print(this.transform.eulerAngles); //获取缩放大小 print(this.transform.lossyScale);}

将当前脚本激活/失活可以用this.enabled = true/false:

void Start(){ //激活 this.enabled = true; //失活 this.enabled = false;}

2、重要方法

(1)获取挂载的脚本

可以利用函数GetComponent的泛型方法来获取当前对象挂载的脚本。

void Start(){ //根据泛型获取 mono t = this.GetComponent(); print(t);}

(2)获取挂载的多个脚本

假如对象上挂载了多个相同的脚本,可以用this.GetComponents来得到挂载的所有脚本。

void Start(){ mono[] array = this.GetComponents(); print(array.Length);}

(3)获取子对象挂载的脚本 

可以使用this.GetComponentInChildren()获取挂载到子对象上的脚本(它默认也会找自己身上是否挂载该脚本)

括号内可以传参数,默认不传是false。假如穿了true,那么哪怕是失活的脚本也会去找,false默认失活的不找

同样的,也可以获取子对象挂载的多个脚本,添个s即可:this.GetComponentsInChildren()

void Start(){ mono2 t2 = this.GetComponentInChildren(true); print(t2); mono2[] array2 = this.GetComponentsInChildren(true); print(array.Length);}

(4)获取父对象挂载的脚本

可以使用this.GetComponentInParent()获取挂载到父对象上的脚本(它默认也会找自己身上是否挂载该脚本)

需要注意的是,找父对象的脚本时一般不会传入是否找失活对象。因为父对象失活时,子对象也会失活,没有意义。

同样的,也可以获取父对象挂载的多个脚本,添s即可:this.GetComponentsInParent()

void Start(){ mono_f t3 = this.GetComponentInParent(); print(t3); mono2[] array3 = this.GetComponentsInParent(); print(array3.Length);}

四、最小单位GameObject

GameObject是Unity场景中的最小单位,所有脚本都依附在GameObject上运行。

1、GameObject成员变量

(1)获取名字

可以通过this.gameObject.name获取当前对象名称。也可以直接对该值赋值事实修改场景中的对象名称。

 void Start() { print(this.gameObject.name); this.gameObject.name = \"MyCube\"; print(this.gameObject.name); }

执行结果:

(2)是否激活、是否是静态

判断对象是否激活可以用.activeSelf,判断是否是静态可以用.isStatic

 void Start() { print(this.gameObject.activeSelf); print(this.gameObject.isStatic); }

(3)获取层级和标签

获取层级:gameObject.layer,层级默认为0;获取标签:gameObject.tag,未设置标签时返回\"Untagged\"

 void Start() { print(this.gameObject.layer); print(this.gameObject.tag); }

(4)Transform

可以通过this.gameObject.transform.position获取详细的三维坐标,也可以直接通过this.transform.position获取。推荐后者:

print(this.transform.position);

结果Unity会自行四舍五入。 

2、GameObject静态方法

(1)创建自带几何体

可以通过GameObject.CreatePrimitive(PrimitiveType)静态方法创建Unity内置几何体。如下例,创建一个自带的Cube立方体并为其改名。

void Start(){ GameObject obj = GameObject.CreatePrimitive(PrimitiveType.Cube); obj.name = \"创建的立方体\";}

运行结果: 

(2)查找对象 

①可以通过按名查找和按tag查找。

按名查找:使用GameObject.Find(\"对象名\")静态方法。缺点是效率较低,因为会遍历场景中所有对象,假如没找到,返回null

按tag查找: 有两种方法,效果一样,GameObject.FindWithTag(\"标签名\")、GameObject.FindGameObjectWithTag(\"标签名\")。

需要注意的是,二者都无法查找到失活的对象。 假如场景中有多个同名或是同tag对象时,查找结果是随机的,无法指定找到哪个对象。

void Start(){ GameObject obj2 = GameObject.Find(\"创建的立方体\"); if(obj2 != null) { print(obj2.name); } GameObject obj3 = GameObject.FindWithTag(\"Player\"); if(obj3 != null) { print(obj3.name); } GameObject obj4 = GameObject.FindGameObjectWithTag(\"Player\");}

②查找多个对象 

 查找多个对象只能用GameObject.FindGameObjectWithTag(\"tag名\"),且同样只能找到激活对象

void Start(){ GameObject[] objs = GameObject.FindGameObjectsWithTag(\"Player\"); print(\"查找到的数量\" + objs.Length);}

 ③通过泛型查找对象

可以通过FindObjectOfType():查找挂载特定脚本的对象,返回的是脚本组件实例。

void Start(){ GB_1 o = GameObject.FindObjectOfType(); print(o.gameObject.name);}

(3)实例化(克隆)对象 

实例化(克隆)对象是根据⼀个GameObject对象创建和它完全相同的对象。克隆的对象包含原对象的所有脚本信息和组件。

使用GameObject.Instantiate()方法实现克隆功能。克隆的对象可以选择场景上的对象,也可以是预设体对象。

public GameObject obj;void Start(){ GameObject.Instantiate(obj);}

关联预设体:

运行结果:

(4)删除对象

GameObject.Destroy()方法可以删除游戏对象或脚本组件。可以传1~2个参数,第一个参数是需要删除的对象,第二个参数是延迟多少秒删除。

public GameObject obj;public GameObject obj2;// Start is called before the first frame updatevoid Start(){ GameObject.Instantiate(obj); GameObject.Destroy(obj2); //延迟5s删除 GameObject.Destroy(obj, 5); //删除当前脚本组件,Inspector窗口中该脚本组件会消失 GameObject.Destroy(this);}

GameObject.Destroy()方法实际上不会立即移除对象,只是添加了移除标识。通常会在下一帧时真正移除对象。如果需要立即删除,需要使用:DestroyImmediate()

(5)过场景不移除

默认情况下,在切换场景时,所有场景对象会被自动删除。如果不希望某个对象被删除,就可以使用:

GameObject.DontDestroyOnLoad(this.gameObject);

这样,该对象过场景就不会被移除。

3、GameObject成员方法

(1)创建空物体

创建空物体直接使用new即可,就可以在场景里创建一个空物体。可以传入参数,为空物体命名、挂载脚本。

添加脚本使用泛型方法 obj.AddComponent即可,添加的脚本有返回值,返回值是添加的脚本,可以用变量接收。

 void Start() { GameObject obj = new GameObject(); GameObject obj2 = new GameObject(\"空物体\"); GameObject obj3 = new GameObject(\"加脚本的空物体\",typeof(temp)); temp tp = obj2.AddComponent();}

查看加脚本的Inspector信息:

 (2)标签比较

标签比较有两种方法,第一种是使用gameObject.CompareTag(),会比较当前对象的tag名和括号中传入的tag名

第二种是直接判断gameObject.tag == “tag名”,两种方法完全一样。

void Start(){ if(this.gameObject.CompareTag(\"Player\")) { print(\"对象的标签是 Player\"); } if(this.gameObject.tag == \"Player\") { print(\"对象的标签是 Player\"); }}

(3)设置对象激活失活

设置对象激活失活可以使用obj.SetActive(),括号里填false时,对象就会失活。

void Start(){ GameObject obj = new GameObject(); GameObject obj2 = new GameObject(\"空物体\"); GameObject obj3 = new GameObject(\"加脚本的空物体\",typeof(temp)); obj.SetActive(false); obj2.SetActive(false); obj3.SetActive(false);}

(4)广播或发送消息

了解即可,不建议使用。

SendMessage()主要是通知自己要执行什么行为。例如下例,通知自己执行函数TestFun。于是会在自己身上挂载的所有脚本中去找这个名字的函数,效率很低。

 void Start() { this.gameObject.SendMessage(\"TestFun\"); } void TestFun() { print(\"执行TestFun\"); }

gameObject. BroadcastMessage(\"函数名\")是广播行为,会通知自己和自己的子对象去执行这个函数。

this.gameObject.BroadcastMessage(\"TestFun\");

gameObject.SendMessageUpwards(\"函数名\")也是广播行为,会通知自己和自己的父对象去执行这个函数。

this.gameObject.SendMessageUpwards(\"TestFun\");