An Introduction to Game Engine and OO Design Patterns
进程(Process)是执行中的程序,是操作系统管理、分配资源(CPU、内存、IO等)的单位。简单的,进程运行在由操作系统提供的虚拟计算机上,进程之间是隔离的,进程间只能通过管道、共享文件、网络相互通讯。具体见操作系统教程
线程(Thread)是进程中并发执行的函数(任务),有自己的栈和上下文环境,是操作系统能够进行资源调度的最小单位。一个进程至少有一个线程(main),应用线程可直接由操作系统线程库管理,也可由编程语言提供的库调度,线程之间共享进程的所拥有的资源。多线程可跟好的利用多CPU资源,但协同机制大大提升了编程难度
协程(Coroutine)是一个有应用程序自己调度的并发函数,有自己的栈和上下文环境,采用 非抢占式调度。协程的优势是避免了频繁线程调度的开销,单线程也能产生并发效果,由于在一个线程中能仅能有一个协程执行,减少的资源冲突。它功能虽然很像线程,但是必须由编译或用户编程代码切换线程。维基百科的解释是:协同程序是一种计算机程序组件,它通过允许暂停和恢复执行,将子程序泛化以实现无优先级的多任务处理。协同程序非常适合实现类似的程序组件,如协作任务、异常、事件循环、迭代器、无限列表和管道。
go-routine 或 fiber 是协程,它们有一个单线程或多线程的调度器调度,当协程被挂起后自动执行其他协程。
C# 使用生成器(Generator)模式协同机制,一个函数使用 yield 关键字产生一个对象的序列,通过 IEnumerator 接口对象遍历这个序列。以下是 Unity 中的一个脚本,它管理了 Generator 函数的两个实例,并调度它们运行。代码如下:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class CoroutineDemo : MonoBehaviour {
private IEnumerator<int> gen1,gen2;
// Use this for initialization
void Start () {
gen1 = Generator (0);
gen2 = Generator (100);
print ("exec start work or not ?");
}
// Update is called once per frame
void Update () {
if (gen1.MoveNext ())
print ("gen1: " + gen1.Current);
if (gen2.MoveNext ())
print ("gen2: " + gen2.Current);
}
IEnumerator<int> Generator(int start) {
print ("start work! " + start);
yield return start;
for (int i = 1; i <= 10; i++) {
print ("next work! " + start + i);
yield return start + i;
}
}
}
请运行以上代码。
语法
创建协程
exec start work or not ?
调度、执行协程
表面上,两个 Generator 函数是并发的,但执行顺序是代码控制的(多线程调度器执行顺序不一定有序)。
现在阅读 MonoBehaviour 的 StartCoroutine 方法就很清晰了。
Unity 引擎预定义一些协程返回值,我们可以从 Execution Order of Event Functions 中找到它们:
在游戏循环 Physics 阶段的最后处理
在游戏循环 GameLogic 阶段的开始依次处理:
在游戏循环 End of frame 阶段
当然你可以自己定义协程执行条件,仅需要继承 CustomYieldInstruction
官方文档 Coroutines 可以管理动作过程,如 Fading。
一个疑问用 update 不是挺方便?
事实是 Coroutines 有生命周期的(从创建到函数执行结束),而且是每个协程都是独立的函数实例。如果游戏对象的基本动作都用协程管理?。。。
不错!Dotweem 这个产品就是这样思考的!
协程、方法扩展、Lambda 表达式 等编程技巧,让 Unity 编程产生了梦幻般的效果。