您的位置:首页 > 移动开发 > Unity3D

Unity 协程的使用简介

2017-08-01 16:17 381 查看
猴子原创,欢迎转载。转载请注明: 转载自Cocos2D开发网–Cocos2Dev.com,谢谢!
原文地址: http://www.cocos2dev.com/?p=496

Coroutine在Unity3D中叫做协程或协同程序,和多线程类似,也就是说开启协同程序就是开启一个线程。但是在任意指定时刻只有一个协程执行,其他协程挂起。

Coroutine的相关函数:
StartCoroutine:启动一个协程。
StopCoroutine:终止一个协程。
StopAllCoroutine:终止所有协程。
WaitForSeconds:等待几秒。
WaitForFixedUpdate:等到下一次FixedUpdate调用时执行。

使用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中,这样我们在编程时就可以方便的调用指定脚本 中的协同程序,而不是无法去管理,特别是对于只根据方法名来判断线程的方式在多人开发中很容易出错,这样的设计保证了对象、脚本的条理化管理,并防止了重 名。
注意:在GameObject.active = false 时他会销毁你的任何一个协同程序过程,并且不会等待过程结束后销毁,GameObject.enable时他并不可以恢复。
协同程序的返回类型为Coroutine类型。在Unity3D中,Coroutine类继承于YieldInstruction,所以,协同程序的返回类型只能为null、等待的帧数(frame)以及等待的时间。

准备写个例子,但是MOMO的这个已经非常详细了,而且我的是基础版,所以这里就直接把MOMO这篇放过来了。(点击查看原文地址

异步任务相信大家应该不会陌生,那么本章内容MOMO将带领大家学习Unity中的一些异步任务。在同步加载游戏场景的时候通常会使用方法 Application.LoadLevel(“yourScene”); 这句代码执行完毕后程序会干什么呢??如下图所示,这是我随便找了一个游戏场景, 在Hierarchy视图中我们可以看到该场景中“天生”的所有游戏对象。天生的意思就是运行程序前该场景中就已经存在的所有游戏对象。然后这些对象就会在执行完Application.LoadLevel(“yourScene”);方法后加载至内存当中。如果该场景中的游戏对象过多那么瞬间将会出现卡一下的情况,因为LoadLevel()方法是同步进行的。MOMO把这种加载起个名字叫A形式加载。



下面我说说“后天“加载的游戏对象。意思是这些游戏对象是通过脚本动态的创建出来的。比如常用方法 :

[csharp] view plain copy print?GameObject Obj = (GameObject)Instantiate(prefab);
GameObject Obj = (GameObject)Instantiate(prefab);

这句代码执行完毕后同样会在Hierarchy视图中添加对应的游戏对象。MOMO把这种加载起个名字叫B形式加载。

下面我们学习异步加载游戏场景,异步异步顾名思义就是不影响当前游戏场景的前提下加载新场景。通常异步加载的方式分为两种:第一种是异步加载新游戏场景,当新场景加载完成后进入新场景并且销毁之前的场景。第二种:同样异步加载新场景,新场景加载完毕后,保留旧场景的游戏对象并且进入新场景。 这里加载的内容就是上面提到的A形式加载。然后B形式加载不会记入这里的加载。
第一种异步加载游戏场景对应的方法是:

[csharp] view plain copy print?Application.LoadLevelAsync(“yourScene”);//only pro
Application.LoadLevelAsync("yourScene");//only pro

第二种异步家在游戏场景对应的方法是:

[csharp] view plain copy print?Application.LoadLevelAdditiveAsync (“yourScene”);
Application.LoadLevelAdditiveAsync ("yourScene");

这两种方法加载的方式完全一样。异步加载其实重要还是应用于游戏LOADING界面,毕竟LOADING如果采用同步的机制会影响用户体验,说到这里MOMO告诉大家如何在Unity中制作游戏进度条。我们应当在Unity中创建一个专门用于读取进度的场景,假设A场景到C场景,我们应当让A场景先到读取进度的场景B场景,当异步任务完成后在进入C场景。 A – 》B -》 C ,在B场景中绘制游戏进度条或读取动画。因为B场景仅仅是个显示LOADING动画的场景,所以读取该场景是瞬间就完成的。
程序在切换场景时应当有一个全全局的静态变量来记录简要读取的场景名称。这里简单的写一下。

[csharp] view plain copy print?using UnityEngine; using System.Collections; public class Globe { //在这里记录当前切换场景的名称 public static string loadName; }
using UnityEngine;
using System.Collections;

public class Globe
{
//在这里记录当前切换场景的名称
public static string loadName;
}


在A场景中通过某些触发条件 调用LoadLevel进入B场景。

[csharp] view plain copy print?//记录LOADING场景中需要读取的C场景名称
Globe.loadName = ”C”;
//先进入B场景
Application.LoadLevel (”B”);
//记录LOADING场景中需要读取的C场景名称
Globe.loadName = "C";
//先进入B场景
Application.LoadLevel ("B");


OK我们在B场景中异步读取C场景与 播放读取动画,Loading.cs 绑定在B场景的摄像机对象身上。当C场景异步读取完毕后即可直接进入C场景。

[csharp] view plain copy print?using UnityEngine;
using System.Collections;

public class Loading : MonoBehaviour {

private float fps = 10.0f;
private float time;
//一组动画的贴图,在编辑器中赋值。
public Texture2D[] animations;
private int nowFram;
//异步对象
AsyncOperation async;

//读取场景的进度,它的取值范围在0 - 1 之间。
int progress = 0;

void Start()
{
//在这里开启一个异步任务,
//进入loadScene方法。
StartCoroutine(loadScene());
}

//注意这里返回值一定是 IEnumerator
IEnumerator loadScene()
{
//异步读取场景。
//Globe.loadName 就是A场景中需要读取的C场景名称。
async = Application.LoadLevelAsync(Globe.loadName);

//读取完毕后返回, 系统会自动进入C场景
yield return async;

}

void OnGUI()
{
//因为在异步读取场景,
//所以这里我们可以刷新UI
DrawAnimation(animations);

}

void Update()
{

//在这里计算读取的进度,
//progress 的取值范围在0.1 - 1之间, 但是它不会等于1
//也就是说progress可能是0.9的时候就直接进入新场景了
//所以在写进度条的时候需要注意一下。
//为了计算百分比 所以直接乘以100即可
progress = (int)(async.progress *100);

//有了读取进度的数值,大家可以自行制作进度条啦。
Debug.Log(”xuanyusong” +progress);

}
//这是一个简单绘制2D动画的方法,没什么好说的。
void DrawAnimation(Texture2D[] tex)
{

time += Time.deltaTime;

if(time >= 1.0 / fps){

nowFram++;

time = 0;

if(nowFram >= tex.Length)
{
nowFram = 0;
}
}
GUI.DrawTexture(new Rect( 100,100,40,60) ,tex[nowFram] );

//在这里显示读取的进度。
GUI.Label(new Rect( 100,180,300,60), “lOADING!!!!!” + progress);

}

}
using UnityEngine;
using System.Collections;

public class Loading : MonoBehaviour {

private float fps = 10.0f;
private float time;
//一组动画的贴图,在编辑器中赋值。
public Texture2D[] animations;
private int nowFram;
//异步对象
AsyncOperation async;

//读取场景的进度,它的取值范围在0 - 1 之间。
int progress = 0;

void Start()
{
//在这里开启一个异步任务,
//进入loadScene方法。
StartCoroutine(loadScene());
}

//注意这里返回值一定是 IEnumerator
IEnumerator loadScene()
{
//异步读取场景。
//Globe.loadName 就是A场景中需要读取的C场景名称。
async = Application.LoadLevelAsync(Globe.loadName);

//读取完毕后返回, 系统会自动进入C场景
yield return async;

}

void OnGUI()
{
//因为在异步读取场景,
//所以这里我们可以刷新UI
DrawAnimation(animations);

}

void Update()
{

//在这里计算读取的进度,
//progress 的取值范围在0.1 - 1之间, 但是它不会等于1
//也就是说progress可能是0.9的时候就直接进入新场景了
//所以在写进度条的时候需要注意一下。
//为了计算百分比 所以直接乘以100即可
progress =  (int)(async.progress *100);

//有了读取进度的数值,大家可以自行制作进度条啦。
Debug.Log("xuanyusong" +progress);

}
//这是一个简单绘制2D动画的方法,没什么好说的。
void   DrawAnimation(Texture2D[] tex)
{

time += Time.deltaTime;

if(time >= 1.0 / fps){

nowFram++;

time = 0;

if(nowFram >= tex.Length)
{
nowFram = 0;
}
}
GUI.DrawTexture(new Rect( 100,100,40,60) ,tex[nowFram] );

//在这里显示读取的进度。
GUI.Label(new Rect( 100,180,300,60), "lOADING!!!!!" + progress);

}

}


OK 下面我们继续学习在游戏场景中加载对象,文章的开始MOMO已经告诉大家,游戏场景中Hierarchy视图中的所有的对象在切换场景的时候都会加载。其实有一种方法可以让某些游戏对象不会被加载,如下图所示,首先在Hierarchy视图中选择一个游戏对象,在右侧监测面板视图中我们可以看到一个 “小对勾”默认情况下是勾选状态,说明该游戏对象处于激活状态,如果点掉的话该对象将被隐藏。这个小功能在开发中其实用处非常大,请大家务必记住哈。



此时此刻大家相像一个游戏场景,默认进入的时候是没有任何游戏对象的,然后运行游戏时开启一个异步任务将它们一个一个的加载显示出来,这种方式适合异步的加载一个比较大的游戏场景。
Test.cs 把它挂在摄像机对象中。

[csharp] view plain copy print?using UnityEngine;
using System.Collections;

public class Test : MonoBehaviour {

//这里是需要加载激活的游戏对象
public GameObject [] Objects;

//当前加载的进度
int load_index =0;
void Start ()
{
//开启一个异步任务,加载模型。
StartCoroutine(loadObject());
}

IEnumerator loadObject()
{
//便利所有游戏对象
foreach(GameObject obj in Objects)
{
//激活游戏对象
obj.active = true;
//记录当前加载的对象
load_index ++;

//这里可以理解为通知主线程刷新UI
yield return 0;
}
//全部便利完毕返回
yield return 0;
}

void OnGUI ()
{
//显示加载的进度
GUILayout.Box(”当前加载的对象ID是: ” + load_index);
}
}
using UnityEngine;
using System.Collections;

public class Test : MonoBehaviour {

//这里是需要加载激活的游戏对象
public GameObject  [] Objects;

//当前加载的进度
int load_index =0;
void Start ()
{
//开启一个异步任务,加载模型。
StartCoroutine(loadObject());
}

IEnumerator loadObject()
{
//便利所有游戏对象
foreach(GameObject obj in Objects)
{
//激活游戏对象
obj.active = true;
//记录当前加载的对象
load_index ++;

//这里可以理解为通知主线程刷新UI
yield return 0;
}
//全部便利完毕返回
yield return 0;
}

void OnGUI ()
{
//显示加载的进度
GUILayout.Box("当前加载的对象ID是: " + load_index);
}
}

如下图所示,我们把需要加载的游戏对象以数组的形式放在Objects数组中,因为这些对象属于未激活状态,所以不能通过Find 等方法在脚步那种中找到他们。讲到这里我们在说说 编辑器赋值与代码中赋值的区别,编辑器中赋值所消耗的时间都会记在loadlevel ()读取场景中。而代码中使用Resource.load()这类方法所消耗的时间会记在脚本中。开发中还得自行的把握一下把loading加在那里。



当然我们还可以使用Instantiate(prefab);方法来动态的创建游戏对象。
Main.cs 把它挂在摄像机中。

[csharp] view plain copy print?using UnityEngine;
using System.Collections;

public class Main : MonoBehaviour
{

public int count;
//在编辑器中预设一个游戏对象
public GameObject prefab;

void Start ()
{
StartCoroutine(loaditem());
}

void OnGUI()
{
GUILayout.Box(”游戏对象已经加载到 : ” + count);
}

IEnumerator loaditem()
{
//开始加载游戏对象
for(int i =0; i< 1000; i++)
{

Instantiate(prefab);
count = i;
//可以理解为刷新UI,显示新加载的游戏对象
yield return 0;
}
//结束
yield return 0;
}
}
using UnityEngine;
using System.Collections;

public class Main : MonoBehaviour
{

public int count;
//在编辑器中预设一个游戏对象
public  GameObject prefab;

void Start ()
{
StartCoroutine(loaditem());
}

void OnGUI()
{
GUILayout.Box("游戏对象已经加载到 : " + count);
}

IEnumerator loaditem()
{
//开始加载游戏对象
for(int i =0; i< 1000; i++)
{

Instantiate(prefab);
count = i;
//可以理解为刷新UI,显示新加载的游戏对象
yield return 0;
}
//结束
yield return 0;
}
}

运行游戏后该游戏对象会循环1000遍逐个创建,不影响主线程。那么今天我们其实学习最多的就是StartCoroutine(),其实就是开启一个异步线程,这里可能有朋友会问Thread可以代替它吗? 答案是不行, 比如查询数据库的时候如果用Thread的话Unity就会报错说不能在线程中查询,但是在StartCoroutine()中就可以完成,所以开发中大家可以尝试着使用它,我们还可以使用StopCoroutine(“name”)来关闭一个正在执行的异步线程。不早了晚安,MOMO祝大家学习愉快。

ok, 这个介绍是MOMO写的,我的第一本Unity书也是买的MOMO的。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  unity3d unity协程