版权声明:本文为博主原创文章,未经博主允许不得转载。欢迎指出文中错误之处!!!
一、MonoBehaviour 的生命周期
MonoBehaviour 是 Unity 中所有脚本的基类,如果你使用js的话,脚本会自动继承MonoBehaviour。如果使用C#的话,你需要显式继承MonoBehaviour。
在我们使用MonoBehaviour的时候,尤其需要注意的是它有哪些可重写函数,这些可重写函数会在游戏中发生某些事件的时候被调用。我们在Unity中最常用到的几个可重写函数是这几个:
-
Awake:当一个脚本被实例化时,Awake 被调用。我们大多在这个类中完成成员变量的初始化。
-
Start:仅在 Update 函数第一次被调用前调用。因为它是在 Awake 之后被调用的,我们可以把一些需要依赖 Awake 的变量放在Start里面初始化。 同时我们还大多在这个类中执行 StartCoroutine 进行一些协程的触发。要注意在用C#写脚本时,必须使用 StartCoroutine 开始一个协程,但是如果使用的是 JavaScript,则不需要这么做。
-
Update:当开始播放游戏帧时(此时,GameObject 已实例化完毕),其 Update 在 每一帧 被调用。
-
LateUpdate:LateUpdate 是在所有 Update 函数调用后被调用。
-
FixedUpdate:当 MonoBehaviour 启用时,其 FixedUpdate 在每一固定帧被调用。
-
OnEnable:当对象变为可用或激活状态时此函数被调用。
-
OnDisable:当对象变为不可用或非激活状态时此函数被调用。
-
OnDestroy:当 MonoBehaviour 将被销毁时,这个函数被调用。
下面用一张图来更形象地说明一下这几个类的在MonoBehaviour的生命周期中是如何被调用的:
二、MonoBehaviour 的那些坑
-
私有(private)和保护(protected)变量只能在专家模式中显示。属性不被序列化或显示在检视面板.
-
不要使用命名空间(namespace)
-
记得使用 缓存组件查找, 即在MonoBehaviour的长远方法中经常被访问的组件最好在把它当作一个私有成员变量存储起来。
-
在游戏里经常出现需要检测敌人和我方距离的问题,这时如果要寻找所有的敌人,显然要消耗的运算量太大了,所以最好的办法是将攻击范围使用Collider表示,然后将Collider的isTrigger设置为True。最后使用OnTriggerEnter来做攻击范围内的距离检测,这样会极大提升程序性能。
三、Monobehaviour 常用方法
不可重写函数:
-
Invoke
function Invoke (methodName : string, time : float) : void
在 time 秒之后,调用 methodName 方法;public class example : MonoBehaviour { public Rigidbody projectile; void LaunchProjectile() { Rigidbody instance = Instantiate(projectile); instance.velocity = Random.insideUnitSphere * 5; } public void Awake() { Invoke("LaunchProjectile", 2); } }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
-
InvokeRepeating
function InvokeRepeating (methodName : string, time : float, repeatRate : float) : void
从第一次调用开始,每隔repeatRate时间调用一次. -
CancelInvoke
function CancelInvoke () : void
取消这个MonoBehaviour上的所有调用Invoke。 -
IsInvoking
function IsInvoking (methodName : string) : bool
某指定函数是否在等候调用。 -
StartCoroutine
function StartCoroutine (routine : IEnumerator) : Coroutine
一个协同程序在执行过程中,可以在任意位置使用 yield 语句。yield 的返回值控制何时恢复协同程序向下执行。协同程序在对象自有帧执行过程中堪称优秀。协同程序在性能上没有更多的开销。StartCoroutine函数是立刻返回的,但是yield可以延迟结果。直到协同程序执行完毕。 -
StopCoroutine / StopAllCoroutines
可重写函数:
-
Update
当 MonoBehaviour 实例化完成之后,Update 在每一帧被调用。
-
LateUpdate
LateUpdate 是在所有 Update 函数调用后被调用。这可用于调整脚本执行顺序。例如:当物体在Update里移动时,跟随物体的相机可以在LateUpdate里实现。
-
FixedUpdate
处理 Rigidbody 时,需要用FixedUpdate代替Update。例如:给刚体加一个作用力时,你必须应用作用力在FixedUpdate里的固定帧,而不是Update中的帧。(两者帧长不同)
-
Awake
Awake 用于在游戏开始之前初始化变量或游戏状态。在脚本整个生命周期内它仅被调用一次。Awake 在所有对象被初始化之后调用,所以你可以安全的与其他对象对话或用诸如 GameObject.FindWithTag 这样的函数搜索它们。每个游戏物体上的Awke以随机的顺序被调用。因此,你应该用Awake来设置脚本间的引用,并用Start来传递信息Awake总是在Start之前被调用。它不能用来执行协同程序。
C#和Boo用户注意:Awake 不同于构造函数,物体被构造时并没有定义组件的序列化状态。Awake像构造函数一样只被调用一次。
-
Start
Start在behaviour的生命周期中只被调用一次。它和 Awake 的不同是,Start 只在脚本实例被启用时调用。你可以按需调整延迟初始化代码。Awake 总是在Start之前执行。
-
OnMouseEnter /OnMouseOver / OnMouseExit / OnMouseDown / OnMouseUp / OnMouseDrag
当鼠标进入 / 悬浮 / 移出 / 点击 / 释放 / 拖拽GUIElement(GUI元素)或Collider(碰撞体)中时调用OnMouseEnter。
-
OnTriggerEnter / OnTriggerExit / OnTriggerStay
当Collider(碰撞体)进入 / 退出 / 停留在 trigger(触发器)时调用OnTriggerEnter。OnTriggerStay 将会在每一帧被调用。
-
OnCollisionEnter / OnCollisionExit / OnCollisionStay
当此collider/rigidbody触发另一个rigidbody/collider时,被调用。OnCollisionStay 将会在每一帧被调用。
四、脚本与GameObject的关系
被显式添加到 Hierarchy 中的 GameObject 会被最先实例化,GameObject 被实例化的顺序是从下往上。GameObject 被实例化的同时,加载其组件 component 并实例化,如果挂载了脚本组件,则实例化脚本组件时,将调用脚本的 Awake 方法,组件的实例化顺序是也是从下往上。在所有显式的 GameObject 及其组件被实例化完成之前,游戏不会开始播放帧。
当 GameObject 实例化工作完成之后,将开始播放游戏帧。每个脚本的第一帧都是调用 Start 方法,其后每一帧调用 Update,而且每个脚本在每一帧中的调用顺序是从下往上。
总结:被挂载到 GameObject 下面的脚本会被实例化成 GameObject 的一个成员。
4.1 脚本变量的引用
在脚本中声明另一个脚本的变量。在 ClassA 中建立一个 public 的变量类型是 ClassB。
// class A
public class classA : MonoBehaviour {
public classB b;
// Use this for initialization
void Start () {
}
// Update is called once per frame
void Update () {
}
}
// class B
public class classB : MonoBehaviour {
public classA a;
// Use this for initialization
void Start () {
}
// Update is called once per frame
void Update () {
}
}
4.1.1 非同一个 GameObject 的脚本引用
情况如下:
此时,如果 classA 中的成员 B 想要引用由 GameObjectB new 出来的 classB 对象,只需要将 GameObjectB 拖拽到 GameObjectA 中 classA 脚本即可。
4.1.2 同一个 GameObject 中互相引用
情况如下:
此时,发现没法通过拖拽的方式建立 classA 和 classB 的引用。因为 Unity 编辑器里面的拖拽绑定方式是 GameObject 级别的。
那么此时如何解决互相引用的问题呢?此时,需要用到 gameObject 这个变量。
被挂载到 GameObject 中的脚本,被实例化时,其内部继承自 Monobehavior 的 gameObject 成员会绑定所挂载的 GameObject 对象。可以注意到,在本例中,classA 和 classB 都是同一个 GameObject 下的组件,所以通过 GetComponent 便可以获得另一个脚本变量的引用。
// class A
public class classA : MonoBehaviour {
public classB b;
// Use this for initialization
void Start () {
b = gameObject.GetComponent ("ClassB") as ClassB;
}
// Update is called once per frame
void Update () {
}
}
// class B
public class classB : MonoBehaviour {
public classA a;
// Use this for initialization
void Start () {
a = gameObject.GetComponent ("ClassA") as ClassA;
}
// Update is called once per frame
void 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
- 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
4.1.3 父子关系的 GameObject 中引用
把问题引申一步,还是那两个脚本ClassA,ClassB,不过这回不是绑在同一个GameObject上面,而是分辨绑定在两个GameObject:Parent(ClassA),Child(ClassB)
首先还是来尝试拖拽,虽然无法在Unity的编辑器中通过拖拽互相引用脚本(Componet),不过绑定 GameObject 是可以.所以只需要建立两个public的变量,然后类型都是 GameObject,在Unity里面互相拖拽引用,最后在 Start 函数时候通过已经绑定好的 gameObject 调用其 GetComponent 方法即可。
的确,这个方法是可行,不过有个更好的方法就是使用 Transform。Transform 是一个很特殊的Component,其内部保留着 GameObject 之间的显示树结构.所以就上面的例子来说,当要从 Child 访问到 Parent,只需要在 Child 对应的脚本里面写 transform.parent.gameObject.GetComponent() 即可
返过来就相对麻烦一点,因为无法保证一个parent只有一个child,所以无法简单的使用 transform.child.gameObject这样访问, 但是Unity给我们提供了一个很方便的函数,那就是Find。
需要注意的是Find只能查找其Child,举个复杂点的例子
Parent->ChildA->ChildB->ChildC
当在 Patent 中想要找到 ChildC中的一个Component时候,调用 transform.Find(“ChildA/ChildB/ChildC”).gameObject;