先来说一说反射。
元数据
有关程序及其类型的数据被称为元数据
,他们保存在程序的程序集
中。
反射
一个运行的程序查看本身的元数据
或其他程序的元数据的行为叫做反射
。
C#中和反射相关的一个类是System.Type
。程序中的每一个类型都会关联一个Type
对象。Type
对象包含了有关类型的很多信息:
Name
:类型的名字Namespace
,Assembly
GetFields()
,GetProperties()
,GetMethods()
获得Type
对象的方法:
- 通过
System.object.GetType()
- 使用
typeof
运算符
大家都很关心的一个问题,为什么反射很慢。当我们使用反射时(C#
),实际发生了什么1:
- 获取方法/字段/属性信息。当然,有
Info Cache
机制可以节省之后再调用反射的时间。
FieldInfo f = foo.getType().GetField("some_field");
- 验证参数以及错误处理:在invoke时验证类型是否匹配,验证参数的类型和数量。抛出异常。
- 安全检查:这个主要是.Net的
Code Access Security Permission
特性
特性是一种语言结构;通过特性,我们可以向程序的程序集
增加元数据
Update()是怎么工作的
Unity的Update()
等事件机制并不是通过反射实现的2。
Unity会通过runtime
来检视是否一个MonoBehaviour
具有Update()
之类的方法,并将这种信息缓存
起来。(可以通过Script Execution Order
选项来手动调节这些Updates
的执行顺序,或通过特性)
Unity的BehaviourManager
会统一调度Updates
等方法,这里面会涉及到Bahaviour
对象的遍历,检查调用的合法性,为调用做准备,检查GameObject
是否激活等工作,会造成一定的时间开销。使用Update()
是十分易于开发的,追求效率的话或许可以手动实现一个manager
。
(一种可行的方法是,让manager
继承自MonoBehaviour
,并提供注册函数和相应的Array
。其余的脚本继承自manager
,这样全局就只有一个Update()
了)
协程
C#语言原本就有yield和IEnumrator。惰性求值
。
IEnumerator ie;
public Start() {
ie = Loop();
}
void Loop() {
int i = 0;
while (true) {
yield return i++;
}
}
void Update() {
if (ie.MoveNext()) {
Debug.Log(ie.current);
}
}
Unity协程不是并行的。
Unity协程的magic效果在于:通过StartCoroutine()
将任务函数委托给协程执行器(猜想)执行,可以设定WaitForSeconds()
来决定执行间隔。yield
类型的协程调用时机是每一帧的Update()
之后
void Start() {
StartCoroutine(Loop());
}
void Loop() {
int i = 0;
while (true) {
yield return new WaitForSeconds(1.0f);
}
}
Unity协程的适用情形:
- 需要每帧执行的任务:使用协程可以让代码美观简洁(for循环简单明了)。
- 有一定的时间间隔,且时间间隔大于每帧时间的情况。
协程的另一个注意点就是通过方法名启动时使用了反射机制,消耗较大。
脚本生命周期
Update
:每帧调用一次,它是帧更新的主要功能。
FixedUpdate
:和帧率实际是有关的。使用独立的定时器。如果帧率较高,它就每一帧少调用几次,反之亦然。
LateUpdate
:也是每帧调用一次,在Update
之后。它的经典用法是摄像机跟随。
各个脚本的生命周期官网讲的很清楚了3
MonoBehaviour
有了以上的介绍,MoneBehaviour
的行为仅很好理解了。
Invoke
:Invoke
相关的方法都是反射相关的Coroutine
:协程