Unity3D 学习笔记6 ——协程
2016-04-09 13:17
731 查看
一、协程的概述:
1.定义:
官方的定义是:
A coroutine is a function that is executed partially and, presuming suitable conditions are met, will be resumed at some point in the future until its work is done.
即协程是一个分部执行,遇到条件(yield return 语句)会挂起,直到条件满足才会被唤醒继续执行后面的代码。
Unity在每一帧(Frame)都会去处理对象上的协程。Unity主要是在Update后去处理协程(检查协程的条件是否满足),但也有些特例。
2.作用:
使用协程的作用一共有两点:
1)延时(等待)一段时间执行代码;
2)等某个操作完成之后再执行后面的代码——通常用于控制运动,序列,以及对象的行为(充当状态机)
总结起来就是一句话:控制代码在特定的时机执行。
注:协程不是线程,也不是异步执行的。协程和
MonoBehaviour 的 Update函数一样也是在MainThread中执行的。使用协程你不用考虑同步和锁的问题。经过测试验证,协程至少是每帧的LateUpdate()后去运行。
以下就是MonoBehaviour函数的执行流程图:
二、使用方式:
其实协程最简单的调用就是用StartCoroutine去开始一个协程处理函数,这个函数可以是带参也可是无参的,但是其返回值类型必须是IEnumerator类型的。
1.yield语句:
是一个特殊的返回类型,它确保函数从yield语句的下一行继续执行。
你也可以传递时间值到yield语句,Update函数会在yield结束后执行下一语句:
不能在Update或FixedUpdate函数内使用yield,但是你能使用 StartCoroutine开始一个函数。yield return可以看做是一种特殊的return,会返回到父类继续执行,但是yield return后面的类型或方法会有一个执行条件,当条件满足时会回调包含yield的子函数。例如:
在执行yield return new WaitForSeconds(waitTime)时暂停的条件没有满足,故返回到start函数中继续执行,直到满足条件后再回调WaitAndPrint,所以输出为:
但是,假如yield return语句所在的函数已经是顶级函数,即不存在父类,则此时程序会堵塞等待yield return条件满足之后再继续执行语句之后的内容。
2.IEnumerator(迭代器):
协程其实就是一个Enumerator(迭代器),IEnumerator接口有两个方法:Current和MoveNext(),只有当MoveNext()返回true时才可以访问Current,否则会报错。迭代器方法运行到yield
return语句时,会返回一个expression表达式并保留当前在代码中的位置。当下次调用迭代器函数时,执行从该位置重新启动。
Unity在每帧所做的工作就是:调用协程(迭代器)的MoveNext方法,如果返回true,就从当前位置继续往下执行。
Hijack
这里在介绍一个协程的交叉调用类 Hijack(参见附件):
C#代码
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using System.Collections;
[RequireComponent(typeof(GUIText))]
public class Hijack : MonoBehaviour {
//This will hold the counting up coroutine
IEnumerator _countUp;
//This will hold the counting down coroutine
IEnumerator _countDown;
//This is the coroutine we are currently
//hijacking
IEnumerator _current;
//A value that will be updated by the coroutine
//that is currently running
int value = 0;
void Start()
{
//Create our count up coroutine
_countUp = CountUp();
//Create our count down coroutine
_countDown = CountDown();
//Start our own coroutine for the hijack
StartCoroutine(DoHijack());
}
void Update()
{
//Show the current value on the screen
guiText.text = value.ToString();
}
void OnGUI()
{
//Switch between the different functions
if(GUILayout.Button("Switch functions"))
{
if(_current == _countUp)
_current = _countDown;
else
_current = _countUp;
}
}
IEnumerator DoHijack()
{
while(true)
{
//Check if we have a current coroutine and MoveNext on it if we do
if(_current != null && _current.MoveNext())
{
//Return whatever the coroutine yielded, so we will yield the
//same thing
yield return _current.Current;
}
else
//Otherwise wait for the next frame
yield return null;
}
}
IEnumerator CountUp()
{
//We have a local increment so the routines
//get independently faster depending on how
//long they have been active
float increment = 0;
while(true)
{
//Exit if the Q button is pressed
if(Input.GetKey(KeyCode.Q))
break;
increment+=Time.deltaTime;
value += Mathf.RoundToInt(increment);
yield return null;
}
}
IEnumerator CountDown()
{
float increment = 0f;
while(true)
{
if(Input.GetKey(KeyCode.Q))
break;
increment+=Time.deltaTime;
value -= Mathf.RoundToInt(increment);
//This coroutine returns a yield instruction
yield return new WaitForSeconds(0.1f);
}
}
}
上面的代码实现是两个协程交替调用。
3.协程暂停:
由于协程与MonoBehaviour一样,也是绑在对象Object上的,所以协程的运行和停止与绑定对象的active有关,MonoBehaviour.enabled
= false 协程会照常运行,但 gameObject.SetActive(false) 后协程却全部停止,即使在Inspector把 gameObject 激活还是没有继续执行。
也就是说:协程虽是在MonoBehaviour启动的(使用StartCoroutine启动),但是协程函数的地位完全跟MonoBehaviour是同一个层次的,不受MonoBehaviour的状态影响,但是跟MonoBehaviour一样受到绑定的gameObject控制,也应该是和MonoBehaviour脚本一样每帧“轮询”yield的条件是否满足。
1.定义:
官方的定义是:
A coroutine is a function that is executed partially and, presuming suitable conditions are met, will be resumed at some point in the future until its work is done.
即协程是一个分部执行,遇到条件(yield return 语句)会挂起,直到条件满足才会被唤醒继续执行后面的代码。
Unity在每一帧(Frame)都会去处理对象上的协程。Unity主要是在Update后去处理协程(检查协程的条件是否满足),但也有些特例。
2.作用:
使用协程的作用一共有两点:
1)延时(等待)一段时间执行代码;
2)等某个操作完成之后再执行后面的代码——通常用于控制运动,序列,以及对象的行为(充当状态机)
总结起来就是一句话:控制代码在特定的时机执行。
注:协程不是线程,也不是异步执行的。协程和
MonoBehaviour 的 Update函数一样也是在MainThread中执行的。使用协程你不用考虑同步和锁的问题。经过测试验证,协程至少是每帧的LateUpdate()后去运行。
以下就是MonoBehaviour函数的执行流程图:
二、使用方式:
其实协程最简单的调用就是用StartCoroutine去开始一个协程处理函数,这个函数可以是带参也可是无参的,但是其返回值类型必须是IEnumerator类型的。
using UnityEngine; using System.Collections; public class NewBehaviourScript : MonoBehaviour { public Transform fish; // Use this for initialization void Start () { StartCoroutine(Move(100)); } IEnumerator Move(int speed) { float ut = 0; while (true) { ut += Time.deltaTime; if (ut >= 2) { ut = 0; var angles = fish.transform.localEulerAngles; angles.z += 180; fish.transform.localEulerAngles = angles; } fish.Translate(speed * Time.deltaTime, 0, 0); yield return 0; } } }协程允许你的代码执行到一半的时候,先去做完别的事情再回来,这样处理许多事情就会变得简单。比如做俄罗斯方块的时候,但算法满足消去一行的时候,你需要播放一个动画,再消去,通常的做法就是把游戏状态转到动画状态,这样才能保证消去的时候暂停其他动作,比如继续下落方块。如果用协程就直接把权限交给协程就ok。
1.yield语句:
是一个特殊的返回类型,它确保函数从yield语句的下一行继续执行。
while(true) { // 做步骤0 yield return 0; // 等待一帧 // 做步骤1 yield return 2; // 等待两帧 // ... }
你也可以传递时间值到yield语句,Update函数会在yield结束后执行下一语句:
// do something yield return WaitForSeconds (5.0); //等待5秒 // do something more...
不能在Update或FixedUpdate函数内使用yield,但是你能使用 StartCoroutine开始一个函数。yield return可以看做是一种特殊的return,会返回到父类继续执行,但是yield return后面的类型或方法会有一个执行条件,当条件满足时会回调包含yield的子函数。例如:
void Start () { print("Starting:" + Time.time); StartCoroutine(WaitAnPrint(2.0F)); print("Before WaiAndPrint:" + Time.time); } IEnumerator WaitAndPrint(float waitTime) { yield return new WaitForSeconds(waitTime); print("WaitAndPrint:" + Time.time); }
在执行yield return new WaitForSeconds(waitTime)时暂停的条件没有满足,故返回到start函数中继续执行,直到满足条件后再回调WaitAndPrint,所以输出为:
Starting:0 Before WaiAndPrint:0 WaitAndPrint:2.12291
但是,假如yield return语句所在的函数已经是顶级函数,即不存在父类,则此时程序会堵塞等待yield return条件满足之后再继续执行语句之后的内容。
2.IEnumerator(迭代器):
协程其实就是一个Enumerator(迭代器),IEnumerator接口有两个方法:Current和MoveNext(),只有当MoveNext()返回true时才可以访问Current,否则会报错。迭代器方法运行到yield
return语句时,会返回一个expression表达式并保留当前在代码中的位置。当下次调用迭代器函数时,执行从该位置重新启动。
Unity在每帧所做的工作就是:调用协程(迭代器)的MoveNext方法,如果返回true,就从当前位置继续往下执行。
Hijack
这里在介绍一个协程的交叉调用类 Hijack(参见附件):
C#代码
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using System.Collections;
[RequireComponent(typeof(GUIText))]
public class Hijack : MonoBehaviour {
//This will hold the counting up coroutine
IEnumerator _countUp;
//This will hold the counting down coroutine
IEnumerator _countDown;
//This is the coroutine we are currently
//hijacking
IEnumerator _current;
//A value that will be updated by the coroutine
//that is currently running
int value = 0;
void Start()
{
//Create our count up coroutine
_countUp = CountUp();
//Create our count down coroutine
_countDown = CountDown();
//Start our own coroutine for the hijack
StartCoroutine(DoHijack());
}
void Update()
{
//Show the current value on the screen
guiText.text = value.ToString();
}
void OnGUI()
{
//Switch between the different functions
if(GUILayout.Button("Switch functions"))
{
if(_current == _countUp)
_current = _countDown;
else
_current = _countUp;
}
}
IEnumerator DoHijack()
{
while(true)
{
//Check if we have a current coroutine and MoveNext on it if we do
if(_current != null && _current.MoveNext())
{
//Return whatever the coroutine yielded, so we will yield the
//same thing
yield return _current.Current;
}
else
//Otherwise wait for the next frame
yield return null;
}
}
IEnumerator CountUp()
{
//We have a local increment so the routines
//get independently faster depending on how
//long they have been active
float increment = 0;
while(true)
{
//Exit if the Q button is pressed
if(Input.GetKey(KeyCode.Q))
break;
increment+=Time.deltaTime;
value += Mathf.RoundToInt(increment);
yield return null;
}
}
IEnumerator CountDown()
{
float increment = 0f;
while(true)
{
if(Input.GetKey(KeyCode.Q))
break;
increment+=Time.deltaTime;
value -= Mathf.RoundToInt(increment);
//This coroutine returns a yield instruction
yield return new WaitForSeconds(0.1f);
}
}
}
上面的代码实现是两个协程交替调用。
3.协程暂停:
由于协程与MonoBehaviour一样,也是绑在对象Object上的,所以协程的运行和停止与绑定对象的active有关,MonoBehaviour.enabled
= false 协程会照常运行,但 gameObject.SetActive(false) 后协程却全部停止,即使在Inspector把 gameObject 激活还是没有继续执行。
也就是说:协程虽是在MonoBehaviour启动的(使用StartCoroutine启动),但是协程函数的地位完全跟MonoBehaviour是同一个层次的,不受MonoBehaviour的状态影响,但是跟MonoBehaviour一样受到绑定的gameObject控制,也应该是和MonoBehaviour脚本一样每帧“轮询”yield的条件是否满足。
相关文章推荐
- Unity协程(Coroutine)原理深入剖析
- Unity3d发布WebGL 部署在IIS
- Unity frameIndex = (int)(timer / (1f / framePerSecond));
- unity源码解析Material
- Disunity_V0.5.0 提取Unity生成的APK资源的后续探索
- [Unity3D]Unity3D游戏开发之使用EasyTouch虚拟摇杆控制人物移动
- Unity删除工程记录
- Unity3D中继承在ios上的UIApplicationDelegate
- [Unity] 协同程序可以在Update或者FixedUpdate中调用。
- Unity3D教程宝典之Shader篇:特别讲 CG函数
- Unity3d 动态加载fbx模型文件
- Unity读取本地图片资源
- [Unity] 官方文档Execution Order of Event Functions
- Unity3d发布webplayer 部署到IIS
- [Unity] 官方文档Coroutines的应用场景
- Unity3d发布webplayer 部署到IIS
- Unity3D 中PlayerPrefs保存或读取数组的方法
- unity3d 状态存储 PlayerPrefs高级用法
- Unity3D游戏开发之数据持久化PlayerPrefs的使用
- unity显示FPS