简要分析unity3d中剪不断理还乱的yield
在学习unity3d的时候非常easy看到以下这个样例:
1 void Start () {
2 StartCoroutine(Destroy());
3 }
4
5 IEnumerator Destroy(){
6 yield return WaitForSeconds(3.0f);
7 Destroy(gameObject);
8 }
这个函数干的事情非常easy:调用StartCoroutine函数开启协程。yield等待一段时间后,销毁这个对象;因为是协程在等待。所以不影响主线程操作。
一般来说。看到这里的时候都还不会晕,yield就是延时一段时间以后继续往下运行呗。恩。学会了。看着还蛮好用的。
====================================================切割线====================================================
当然,yield能干的事情远远不止这样的简单的特定时间的延时。比如能够在下一帧继续运行这段代码(yield return null),能够在下一次运行FixedUpdate的时候继续运行这段代码(yield new WaitForFixedUpdate ();)。能够让异步操作(如LoadLevelAsync)在完毕以后继续运行,能够……能够让你看到头晕。
unity3d官方对于协程的解释是:一个协同程序在运行过程中,能够在任何位置使用yield语句。
yield的返回值控制何时恢复协同程序向下运行。协同程序在对象自有帧运行过程中堪称优秀。协同程序在性能上没有很多其它的开销。
StartCoroutine函数是立马返回的,可是yield能够延迟结果。直到协同程序运行完成。(原文:The execution of a coroutine can be paused at any point using
the yield statement. The yield return value specifies when the coroutine is resumed. Coroutines are excellent when modelling behaviour over several frames. Coroutines have virtually no performance overhead. StartCoroutine function always returns immediately,
however you can yield the result. This will wait until the coroutine has finished execution.)
假设仅仅是觉得yield用于延时,那么能够用的非常顺畅;可是若看到yield还有这么多功能,目測瞬间就凌乱了,更不要说活学活用了。只是,假设从原理上进行理解,就非常easy理清yield的各种功能了。
C#中的yield
1 public static IEnumerable<int> GenerateFibonacci()
2 {
3 yield return 0;
4 yield return 1;
5
6 int last0 = 0, last1 = 1, current;
7
8 while (true)
9 {
10 current = last0 + last1;
11 yield return current;
12
13 last0 = last1;
14 last1 = current;
15 }
16 }
yield return的作用是在运行到这行代码之后,将控制权马上交还给外部。
yield return之后的代码会在外部代码再次调用MoveNext时才会运行。直到下一个yield return——或是迭代结束。尽管上面的代码看似有个死循环,但其实在循环内部我们始终会把控制权交还给外部,这就由外部来决定何时中止这次迭代。有了yield之后,我们便能够利用“死循环”,我们能够写出含义明白的“无限的”斐波那契数列。转自老赵的博客。
IEnumerable与IEnumerator的差别比較小,在unity3d中仅仅用到IEnumerator。功能和IEnumerable类似。
至于他们的差别是什么。网上搜了半天。还是模糊不清,有童鞋能解释清楚的请留言。只是对于这段代码对于unity3d中yield的理解已经足够了。
游戏中须要使用yield的场景
既然要使用yield。就得给个理由吧,不能为了使用yield而使用yield。那么先来看看游戏中能够用得到yield的场景:
游戏结算分数时。分数从0逐渐上涨。而不是直接显示终于分数
- 人物对话时,文字一个一个非常快的出现。而不是一下突然出现
10、9、8……0的倒计时
某些游戏(如拳皇)掉血时血条UI逐渐降低。而不是突然降低到当前血量
…………………………
unity3d中yield应用举例
首先是官网的一段代码:
1 using UnityEngine;
2 using System.Collections;
3
4 public class yield1 : MonoBehaviour {
5
6 IEnumerator Do() {
7 print("Do now");
8 yield return new WaitForSeconds(2);
9 print("Do 2 seconds later");
10 }
11 void Awake() {
12 StartCoroutine(Do());
13 print("This is printed immediately");
14 }
15
16 // Use this for initialization
17 void Start () {
18
19 }
20
21 // Update is called once per frame
22 void Update () {
23
24 }
25 }
这个样例将运行Do,可是Do函数之后的print指令会立马运行。这个样例没有什么实际意义。仅仅是为了验证一下yield确实是有延时的。
以下来看看两段显示人物对话的代码(对话随便复制了一段内容)。功能是一样的,可是方法不一样:
1 using UnityEngine;
2 using System.Collections;
3
4 public class dialog_easy : MonoBehaviour {
5 public string dialogStr = "yield return的作用是在运行到这行代码之后。将控制权马上交还给外部。yield return之后的代码会在外部代码再次调用MoveNext时才会运行,直到下一个yield return——或是迭代结束。尽管上面的代码看似有个死循环,但其实在循环内部我们始终会把控制权交还给外部,这就由外部来决定何时中止这次迭代。有了yield之后,我们便能够利用“死循环”。我们能够写出含义明白的“无限的”斐波那契数列。 ";
6 public float speed = 5.0f;
7
8 private float timeSum = 0.0f;
9 private bool isShowing = false;
10 // Use this for initialization
11 void Start () {
12 ShowDialog();
13 }
14
15 // Update is called once per frame
16 void Update () {
17 if(isShowing){
18 timeSum += speed * Time.deltaTime;
19 guiText.text = dialogStr.Substring(0, System.Convert.ToInt32(timeSum));
20
21 if(guiText.text.Length == dialogStr.Length)
22 isShowing = false;
23 }
24 }
25
26 void ShowDialog(){
27 isShowing = true;
28 timeSum = 0.0f;
29 }
30 }
这段代码实现了在GUIText中逐渐显示一个字符串的功能。速度为每秒5个字,这也是新手经常使用的方式。假设仅仅是简单的在GUIText中显示一段文字,ShowDialog()函数能够做的非常好;可是假设要让字一个一个蹦出来,就须要借助游戏的循环了,最简单的方式就是在Update()中更新GUIText。
从功能角度看,这段代码全然没有问题;可是从代码封装性的角度来看。这是一段非常恶心的代码,由于本应由ShowDialog()完毕的功能放到了Update()中,而且在类中还有两个private变量为这个功能服务。假设将来要改动或者删除这个功能。须要在ShowDialog()和Update()中改动,而且还可能改动那两个private变量。
如今代码比較简单。感觉还不算太坏。一旦Update()中再来两个类似的的功能,预计写完代码一段时间之后自己改动都费劲。
假设通过yield return null实现帧与帧之间的同步。则代码优雅了非常多:
1 using UnityEngine;
2 using System.Collections;
3
4 public class dialog_yield : MonoBehaviour {
5 public string dialogStr = "yield return的作用是在运行到这行代码之后,将控制权马上交还给外部。yield return之后的代码会在外部代码再次调用MoveNext时才会运行,直到下一个yield return——或是迭代结束。尽管上面的代码看似有个死循环,但其实在循环内部我们始终会把控制权交还给外部。这就由外部来决定何时中止这次迭代。有了yield之后,我们便能够利用“死循环”,我们能够写出含义明白的“无限的”斐波那契数列。 ";
6 public float speed = 5.0f;
7
8 // Use this for initialization
9 void Start () {
10 StartCoroutine(ShowDialog());
11 }
12
13 // Update is called once per frame
14 void Update () {
15 }
16
17 IEnumerator ShowDialog(){
18 float timeSum = 0.0f;
19 while(guiText.text.Length < dialogStr.Length){
20 timeSum += speed * Time.deltaTime;
21 guiText.text = dialogStr.Substring(0, System.Convert.ToInt32(timeSum));
22 yield return null;
23 }
24 }
25 }
相关代码都被封装到了ShowDialog()中,这么一来,不论是要添加、改动或删除功能,都变得easy了非常多。
依据官网手冊的描写叙述,yield return null能够让这段代码在下一帧继续运行。在ShowDialog()中,每次更新文字以后yield return null,直到这段文字被完整显示。
看到这里,可能有童鞋不解:
- 为什么在协程中也能够用Time.deltaTime?
- 协程中的Time.deltaTime和Update()中的一样吗?
- 这样使用协程,会不会出现与主线程訪问共享资源冲突的问题?(线程的同步与相互排斥问题)
- yield return null太奇妙了,为什么会在下一帧继续运行这个函数?
- 这段代码是不是相当于为ShowDialog()构造了一个自己的Update()?
要解释这些问题,先看看unity3d中的协程是怎么执行的吧。
协程原理分析
本段内容转自这篇博客,想看的童鞋自己点击。
首先,请你牢记:协程不是线程,也不是异步运行的。协程和 MonoBehaviour 的 Update函数一样也是在MainThread中运行的。使用协程你不用考虑同步和锁的问题。
UnityGems.com给出了协程的定义:
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后去处理协程(检查协程的条件是否满足):
从上图的剖析就明确,协程跟Update()事实上一样的,都是Unity每帧对会去处理的函数(假设有的话)。假设MonoBehaviour 是处于激活(active)状态的并且yield的条件满足。就会协程方法的后面代码。
还能够发现:假设在一个对象的前期调用协程,协程会马上执行到第一个 yield return 语句处,假设是 yield return null ,就会在同一帧再次被唤醒。假设没有考虑这个细节就会出现一些奇怪的问题。
注:图和结论都是从UnityGems.com 上得来的,经过验证发现与实际不符,D.S.Qiu用的是Unity 4.3.4f1 进行測试的。经过測试验证,协程至少是每帧的LateUpdate()后去执行。
协程事实上就是一个IEnumerator(迭代器),IEnumerator 接口有两个方法 Current 和 MoveNext() ,迭代器方法运行到 yield return 语句时,会返回一个expression表达式并保留当前在代码中的位置。
当下次调用迭代器函数时运行从该位置又一次启动。
unity3d在每帧做的工作就是:调用协程(迭代器)MoveNext() 方法。假设返回 true ,就从当前位置继续往下运行。详情见这篇博客。
假设理解了这张图,之前显示人物对话的功能最后提到的那些疑惑也就非常easy理解了:
- 协程和Update()一样更新,自然能够使用Time.deltaTime了,并且这个Time.deltaTime和在Update()其中使用是一样的效果(使用yield return null的情况下)
- 协程并非多线程,它和Update()一样是在主线程中运行的。所以不须要处理线程的同步与相互排斥问题
- yield return null事实上没什么奇妙的。仅仅是unity3d封装以后,这个协程在下一帧就被自己主动调用了
- 能够理解为ShowDialog()构造了一个自己的Update(),由于yield return null让这个函数每帧都被调用了
简要分析unity3d中剪不断理还乱的yield的更多相关文章
- 【吐血推荐】简要分析unity3d中剪不断理还乱的yield
在学习unity3d的时候很容易看到下面这个例子: void Start () { StartCoroutine(Destroy()); } IEnumerator Destroy(){ yield ...
- 【Unity3D】简要分析unity3d中剪不断理还乱的yield
在学习unity3d的时候很容易看到下面这个例子: void Start () { StartCoroutine(Destroy()); } IEnumerator Destroy(){ yield ...
- 【转】简要分析unity3d中剪不断理还乱的yield
在学习unity3d的时候很容易看到下面这个例子: 1 void Start () { 2 StartCoroutine(Destroy()); 3 } 4 5 IEnumerator Destroy ...
- Unity 3D中不得不说的yield协程与消息传递
1. 协程 在Unity 3D中,我们刚开始写脚本的时候肯定会遇到类似下面这样的需求:每隔3秒发射一个烟花.怪物死亡后20秒再复活之类的.刚开始的时候喜欢把这些东西都塞到Update里面去,就像下面这 ...
- Java7中的ForkJoin并发框架初探(中)——JDK中实现简要分析
原文发表于 2013 年 8 月 28 日 由 三石 根据前文描述的Doug Lea的理论基础,在JDK1.7中已经给出了Fork Join的实现.在Java SE 7的API中,多了ForkJoin ...
- [转]Java7中的ForkJoin并发框架初探(中)——JDK中实现简要分析
详见: http://blog.yemou.net/article/query/info/tytfjhfascvhzxcytp85 根据前文描述的Doug Lea的理论基础,在JDK1.7中已经给 ...
- Unity3D之协程(Coroutines & Yield )
在Unity中StartCoroutine/yield return这个模式到底是怎么应用的? 比如你要一个方法进行一个比较耗时的复杂运算~同时又想让脚本流畅的进行其他操作而不是卡在那里等该方法执行完 ...
- 浅析游戏引擎的资源管理机制——扒一扒Unity3D中隐藏在背后的资源管理
游戏中通常有大量资源,如网格.材质.纹理.动画.着色器程序和音乐等,游戏引擎作为做游戏的工具,自然要提供良好的资源管理,让游戏开发者用最简单的方式使用资源.游戏引擎的资源管理包括两大部分:离线资源管理 ...
- 图文详解Unity3D中Material的Tiling和Offset是怎么回事
图文详解Unity3D中Material的Tiling和Offset是怎么回事 Tiling和Offset概述 Tiling表示UV坐标的缩放倍数,Offset表示UV坐标的起始位置. 这样说当然是隔 ...
随机推荐
- 源码安装 ipython
https://blog.csdn.net/huobanjishijian/article/details/51470898
- excel的隔行插入
https://wenda.so.com/q/1523455238213064 #公式 IF(ISODD(ROW()),OFFSET($B$1,INT((ROW(A1)-1)/2),),OFFSET( ...
- SpringMVC框架中的异常解析器-ExceptionHandler和HandlerExceptionResolver
SpringMVC框架中,处理异常还是挺方便的,提供了一个异常解析器. 处理局部异常 @Controller public class AccessController { /** * 处理这个Con ...
- Java Reflection - Getters and Setters
原文链接:http://tutorials.jenkov.com/java-reflection/getters-setters.html 通过使用 Java 反射,我们能够在程序执行时观察 clas ...
- [USACO08JAN]电话线Telephone Lines(分层图)/洛谷P1948
这道题其实是分层图,但和裸的分层图不太一样.因为它只要求路径总权值为路径上最大一条路径的权值,但仔细考虑,这同时也满足一个贪心的性质,那就是当你每次用路径总权值小的方案来更新,那么可以保证新的路径权值 ...
- springMVC注解用法:@modelattribute的用法
在Spring MVC里,@ModelAttribute通常使用在Controller方法的参数注解中,用于解释model entity,但同时,也可以放在方法注解里. 如果把@ModelAttrib ...
- 洛谷—— P1080 国王游戏
https://www.luogu.org/problem/show?pid=1080 题目描述 恰逢 H 国国庆,国王邀请 n 位大臣来玩一个有奖游戏.首先,他让每个大臣在左.右手上面分别写下一个整 ...
- ubuntu-软件解压方法(转载)
一下内容转载自 http://blog.csdn.net/zad522/article/details/2770446 今天用到了ubuntu解压,所以就在网上查找了下方法.自己菜鸟一枚,收录别人的文 ...
- ASP.Net中页面传值的几种方式
开篇概述 对于任何一个初学者来说,页面之间传值可谓是必经之路,却又是他们的难点.其实,对大部分高手来说,未必不是难点. 回想2016年面试的将近300人中,有实习生,有应届毕业生,有1-3年经验的,有 ...
- 推荐一款优雅高效的免费在线APP原型工具
有段时间没有推荐干货给大伙了,今天是时候把压箱底的东西拿出来分享给大家了! 想要学习原型图绘制的小伙伴可以看过来,适合零基础的小白,五分钟就可以上手,绘制自己想要的产品原型图. 官方介绍:用户只需输入 ...