Unity协程(Coroutine)原理解析

发表于2017-07-15
评论0 1.2k浏览

线程(Thread)和协程(Coroutine)

使用协程的作用一共有两点:1)延时(等待)一段时间执行代码;2)等某个操作完成之后再执行后面的代码。总结起来就是一句话:控制代码在特定的时机执行。

很多初学者,都会下意识地觉得协程是异步执行的,都会觉得协程是C# 线程的替代品,是Unity不使用线程的解决方案。

所以首先,请你牢记:协程不是线程,也不是异步执行的。协程和 MonoBehaviour 的 Update函数一样也是在MainThread中执行的。使用协程你不用考虑同步和锁的问题。

Unity中协程的执行原理

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)都会去处理对象上的协程。

『今天意外发现Monobehaviour的函数执行顺序图,发现协程的运行确实是在LateUpdate之后,下面附上:』

整理得到 :通过设置MonoBehaviour脚本的enabled对协程是没有影响的,但如果 gameObject.SetActive(false) 则已经启动的协程则完全停止了,即使在Inspector把gameObject 激活还是没有继续执行。也就说协程虽然是在MonoBehvaviour启动的(StartCoroutine)但是协程函数的地位完全是跟MonoBehaviour是一个层次的,不受MonoBehaviour的状态影响,但跟MonoBehaviour脚本一样受gameObject 控制,也应该是和MonoBehaviour脚本一样每帧“轮询” yield 的条件是否满足。

 

协成中最少需要执行两帧..所以最好不要在协成做一些判断..去执行yield

 

 

yield 后面可以有的表达式:

a) null – the coroutine executes the next time that it is eligible

b) WaitForEndOfFrame – the coroutine executes on the frame, after all of the rendering and GUI is complete

c) WaitForFixedUpdate – causes this coroutine to execute at the next physics step, after all physics is calculated

d) WaitForSeconds – causes the coroutine not to execute for a given game time period

e) WWW – waits for a web request to complete (resumes as if WaitForSeconds or null)

f) Another coroutine – in which case the new coroutine will run to completion before the yielder is resumed

值得注意的是 WaitForSeconds()受Time.timeScale影响,当Time.timeScale = 0f 时,yield return new WaitForSecond(x) 将不会满足。

IEnumerator & Coroutine

协程其实就是一个IEnumerator(迭代器),IEnumerator 接口有两个方法 Current 和 MoveNext() ,这里在介绍一个协程的交叉调用类 Hijack(参见附件):

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);
    }
  }
}

上面的代码实现是两个协程交替调用,对有这种需求来说实在太精妙了。

如社区发表内容存在侵权行为,您可以点击这里查看侵权投诉指引

标签:

0个评论