1. 什么是协同程序
在主程序运行时同时开启另一段逻辑处理,来协同当前程序的执行。换句话说,开启协同程序就是开启一个线程。
2. 协同程序的开启与终止
在Unity3D中,使用MonoBehaviour.StartCoroutine方法即可开启一个协同程序,也就是说该方法必须在 MonoBehaviour 或继承于MonoBehaviour的类中调用。
在Unity3D中,使用StartCoroutine(string methodName)和StartCoroutine(IEnumerator routine)都可以开启一个线程。区别在于使用字符串作为参数可以开启线程并在线程结束前终止线程,相反使用IEnumerator 作为参数只能等待线程的结束而不能随时终止(除非使用StopAllCoroutines()方法);另外使用字符串作为参数时,开启线程时最多只能传递 一个参数,并且性能消耗会更大一点,而使用IEnumerator 作为参数则没有这个限制。
在Unity3D中,使用StopCoroutine(string methodName)来终止一个协同程序,使用StopAllCoroutines()来终止所有可以终止的协同程序,但这两个方法都只能终止该 MonoBehaviour中的协同程序。
还有一种方法可以终止协同程序,即将协同程序所在gameobject的active属性设置为false,当再次设置active为ture时,协同程序并不会再开启;如是将协同程序所在脚本的enabled设置为false则不会生效。这是因为协同程序被开启后作为一个线程在运行,而 MonoBehaviour也是一个线程,他们成为互不干扰的模块,除非代码中有调用,他们共同作用于同一个对象,只有当对象不可见才能同时终止这两个线 程。然而,为了管理我们额外开启的线程,Unity3D将协同程序的调用放在了MonoBehaviour中,这样我们在编程时就可以方便的调用指定脚本 中的协同程序,而不是无法去管理,特别是对于只根据方法名来判断线程的方式在多人开发中很容易出错,这样的设计保证了对象、脚本的条理化管理,并防止了重 名。
3. 协同程序的输入、输出类型
协同程序的返回类型为Coroutine类型。在Unity3D中,Coroutine类继承于YieldInstruction,所以,协同程序的返回类型只能为null、等待的帧数(frame)以及等待的时间。
协同程序的参数不能指定ref、out参数。但是,我们在使用WWW类时会经常使用到协同程序,由于在协同程序中不能传递参数地址(引用),也不能输出对 象,这使得每下载一个WWW对象都得重写一个协同程序,解决这个问题的方法是建立一个基于WWW的类,并实现一个下载方法。如下:
usingUnityEngine;
usingSystem.Collections;
public class WWWObject:MonoBehaviour
{
public WWW www;
public WWWObject(string url)
{
if(GameVar.wwwCache)
www = WWW.LoadFromCacheOrDownload(url,GameVar.version);
else
www =new WWW(url);
}
public IEnumerator Load()
{
Debug.Log("Start loading : "+www.url);
while(!www.isDone)
{
if(GameVar.gameState ==GameState.Jumping||GameVar.gameState ==GameState.JumpingAsync)
LoadScene.progress =www.progress;
yield return 1;
}
if(www.error!=null)
Debug.LogError("Loading error : "+www.url+"\n"+www.error);
else
Debug.Log("End loading : "+www.url);
}
public IEnumerator LoadWithTip(string resourcesName)
{
Debug.Log("Start loading : "+www.url);
LoadScene.tipStr = "Downloading resources <"+ resourcesName +"> . . .";
while(!www.isDone)
{
if(GameVar.gameState ==GameState.Jumping||GameVar.gameState ==GameState.JumpingAsync)
LoadScene.progress =www.progress;
yield return 1;
}
if(www.error!=null)
Debug.LogError("Loading error : "+www.url+"\n"+www.error);
else
Debug.Log("End loading : "+www.url);
}
}
调用:
usingUnityEngine;
usingSystem.Collections;
usingSystem.Collections.Generic;
public class LoadResources:MonoBehaviour
{
static string url ="http://61.149.211.88/Package/test.unity3d";
public static WWW www =null;
IEnumerator Start()
{
if(!GameVar.resourcesLoaded)
{
GameVar.gameState =GameState.Jumping;
WWWObject obj =newWWWObject(url);
www = obj.www;
yield return StartCoroutine(obj.LoadWithTip("Textures"));
GameVar.resourcesLoaded =true;
GameVar.gameState =GameState.Run;
}
}
}
关于StartCoroutine的简单线程使用
StartCoroutine在unity3d的帮助中叫做协程,意思就是启动一个辅助的线程。
在C#中直接有Thread这个线程,但是在unity中有些元素是不能操作的。这个时候可以使用协程来完成。
使用线程的好处就是不会出现界面卡死的情况,如果有一次非常大量的运算,没用线程就会出现假死的情况。
下面通过一个简单的例子来说明使用协程的好处:
voidOnGUI()
{
GUI.Label(newRect(0, 0, 200, 50),"测试1:"+ result);
if(GUI.Button(newRect(0, 100, 100, 50),"开启协程"))
{
StartCoroutine(GetResult());
}
GUI.Label(newRect(200, 0, 200, 50),"测试2:"+ result1);
if(GUI.Button(newRect(200, 100, 100, 50),"无协程测试"))
{
GetResult1();
}
}
上面的代码表示在GUI中定义2个label和按钮,一个按钮启动协程计算,另一个直接计算结果。由于2个方法都是计算同样的结果,计算量比较大,所以直接计算出现了暂时的卡死情况。
floatresult;
IEnumerator GetResult()
{
for(inti = 0; i < 1000; i++)
{
for(intj = 0; j < 100000; j++)
{
result += (i + j);
}
if(i%100==0)
yieldreturn1;
}
}
这个方法是协程的写法,在C#中协程要定义为IEnumerator 这个类型,javascript中不需要。
yield return 1;这句话表示返回1帧的结果。在i为100的整数时,就返回一次结果,这样可以避免大量的计算卡死。
floatresult1;
voidGetResult1(){
for(inti = 0; i < 1000; i++){
for(intj = 0; j < 100000; j++){
result1 += (i + j);
}
}
}