unity3d 协程 带返回值_unity 带返回值的协程 - CSDN
  • Unity3D协程入门

    2016-12-08 18:17:07
    Unity协程系统是基于C#的一个简单而强大的接口 ,IEnumerator,它允许你为自己的集合类型编写枚举器。这一点你不必关注太多,我们直接进入一个简单的例子来看看协程到底能干什么。首先,我们来看一下这段简单的...
    协程介绍
    Unity的协程系统是基于C#的一个简单而强大的接口 ,IEnumerator,它允许你为自己的集合类型编写枚举器。这一点你不必关注太多,我们直接进入一个简单的例子来看看协程到底能干什么。首先,我们来看一下这段简单的代码...
     
    倒计时器
    这是一个简单的脚本组件,只做了倒计时,并且在到达0的时候log一个信息。

    using Unity Engine;  
    using System.Collections;  
       
    public class Countdown : MonoBehaviour  
    {  
        public float timer = 3;  
       
        void Update()  
        {  
            timer -= Time.deltaTime;  
            if(timer <= 0)  
                Debug.Log("Timer has finished!");  
        }  
    }  

    还不错,代码简短实用,但问题是,如果我们需要复杂的脚本组件(像一个角色或者敌人的类),拥有多个计时器呢?刚开始的时候,我们的代码也许会是这样的:
    using UnityEngine;  
    using System.Collections;  
       
    public class MultiTimer : MonoBehaviour  
    {  
        public float firstTimer = 3;  
        public float secondTimer = 2;  
        public float thirdTimer = 1;  
       
        void Update()  
        {  
            firstTimer -= Time.deltaTime;  
            if(firstTimer <= 0)  
                Debug.Log("First timer has finished!");  
       
            secondTimer -= Time.deltaTime;  
            if(secondTimer <= 0)  
                Debug.Log("Second timer has finished!");  
       
            thirdTimer -= Time.deltaTime;  
            if(thirdTimer <= 0)  
                Debug.Log("Third timer has finished!");  
        }  
    } 

    尽管不是太糟糕,但是我个人不是很喜欢自己的代码中充斥着这些计时器变量,它们看上去很乱,而且当我需要重新开始计时的时候还得记得去重置它们(这活我经常忘记做)。
     
    如果我只用一个for循环来做这些,看上去是否会好很多?

    for(float timer = 3; timer >= 0; timer -= Time.deltaTime)  
    {  
        //Just do nothing...  
    }  
    Debug.Log("This happens after 5 seconds!");  

    现在每一个计时器变量都成为for循环的一部分了,这看上去好多了,而且我不需要去单独设置每一个跌倒变量。
     
    好的,你可能现在明白我的意思:协程可以做的正是这一点!
     
    码入你的协程!
    现在,这里提供了上面例子运用协程的版本!我建议你从这里开始跟着我来写一个简单的脚本组件,这样你可以在你自己的程序中看到它是如何工作的。
    using UnityEngine;  
    using System.Collections;  
       
    public class CoroutineCountdown : MonoBehaviour  
    {  
        void Start()  
        {  
            StartCoroutine(Countdown());  
        }  
       
        IEnumerator Countdown()  
        {  
            for(floattimer = 3; timer >= 0; timer -= Time.deltaTime)  
                Yield return 0;  
       
            Debug.Log("This message appears after 3 seconds!");  
        }  
    }  

    这看上去有点不一样,没关系,接下来我会解释这里到底发生了什么。
    StartCoroutine(Countdown());   
    这一行用来开始我们的Countdown程序,注意,我并没有给它传入参数,但是这个方法调用了它自己(这是通过传递Countdown的return返回值来实现的)。
     
    Yield
    在Countdown方法中其他的都很好理解,除了两个部分:
    l IEnumerator 的返回值
    l For循环中的yield return
     
    为了能在连续的多帧中(在这个例子中,3秒钟等同于很多帧)调用该方法,Unity必须通过某种方式来存储这个方法的状态,这是通过IEnumerator 中使用yield return语句得到的返回值,当你“yield”一个方法时,你相当于说了,“现在停止这个方法,然后在下一帧中从这里重新开始!”。
     
    注意:用0或者null来yield的意思是告诉协程等待下一帧,直到继续执行为止。当然,同样的你可以继续yield其他协程,我会在下一个教程中讲到这些。
     
    一些例子
    协程在刚开始接触的时候是非常难以理解的,无论是新手还是经验丰富的程序员我都见过他们对于协程语句一筹莫展的时候。因此我认为通过例子来理解它是最好的方法,这里有一些简单的协程例子:
     
    多次输出“Hello”
    记住,yield return是“停止执行方法,并且在下一帧从这里重新开始”,这意味着你可以这样做:
    //This will say hello 5 times, once each frame for 5 frames  
    IEnumerator SayHelloFiveTimes()  
    {  
        Yield return 0;  
        Debug.Log("Hello");  
        Yield return 0;  
        Debug.Log("Hello");  
        Yield return 0;  
        Debug.Log("Hello");  
        Yield return 0;  
        Debug.Log("Hello");  
        Yield return 0;  
        Debug.Log("Hello");  
    }  
    //下面的方法跟上面作用一样
    IEnumerator SayHello5Times()  
    {  
        for(inti = 0; i < 5; i++)  
        {  
            Debug.Log("Hello");  
            Yield return 0;  
        }  
    }  
    每一帧输出“Hello”,无限循环。。。
    通过在一个while循环中使用yield,你可以得到一个无限循环的协程,这几乎就跟一个Update()循环等同。。。

    //一旦开启,将一直执行下去,知道手动停止或脚本销毁
    IEnumerator SayHelloEveryFrame()  
    {  
        while(true)  
        {  
            //1. Say hello  
            Debug.Log("Hello");  
       
            //2. Wait until next frame  
            Yield return 0;  
       
        }//3. This is a forever-loop, goto 1  
    }  

    计时
    ...不过跟Update()不一样的是,你可以在协程中做一些更有趣的事:

    IEnumerator CountSeconds()  
    {  
        int seconds = 0;  
       
        while(true)  
        {  
            for(float timer = 0; timer < 1; timer += Time.deltaTime)  
                Yield return 0;  
       
            seconds++;  
            Debug.Log(seconds +" seconds have passed since the Coroutine started.");  
        }  
    }  
    这个方法突出了协程一个非常酷的地方:方法的状态被存储了,这使得方法中定义的这些变量都会保存它们的值,即使是在不同的帧中。还记得这个教程开始时那些烦人的计时器变量吗?通过协程,我们再也不需要担心它们了,只需要把变量直接放到方法里面!
     
    开始和终止协程
    之前,我们已经学过了通过 StartCoroutine()方法来开始一个协程,就像这样:
    StartCoroutine(Countdown());  
    如果我们想要终止所有的协程,可以通过StopAllCoroutines()方法来实现,它的所要做的就跟它的名字所表达的一样。注意,这只会终止在调用该方法的对象中(应该是指调用这个方法的类吧)开始的协程,对于其他的MonoBehavior类中运行的协程不起作用。
     
    如果我们有以下这样两条协程语句:
    StartCoroutine(FirstTimer());  
    StartCoroutine(SecondTimer());  

    那我们怎么终止其中的一个协程呢?在这个例子里,这是不可能的。

    如果你想要终止某一个特定的协程,那么你必须得在开始协程的时候保存协程的引用或者将它的方法名作为字符串,就像这样:

    using System.Collections;
    using UnityEngine;
    
    public class CoroutineExample : MonoBehaviour {
    
        private IEnumerator coroutine;
        void Start () {
    
            //1.字符串开启
            StartCoroutine("FirstTimer");
            //保存协程引用
            coroutine = FirstTimer();
            StartCoroutine(coroutine);
        }
        void Stop()
        {
            //1.字符串开启
            StopCoroutine("FirstTimer");
            //2.保存协程引用
            StopCoroutine(coroutine);
        }
    
        IEnumerator FirstTimer() {
            while(true)
            {
                yield return new WaitForSeconds(1f);
                Debug.Log("log....");
            }
    	}
    }
    

    协程的参数
    抽象化一个协程的第一个方法是给它传递参数,协程作为一个函数方法来说,它自然能够传递参数。这里有一个协程的例子,它在特定的地方输出了特定的信息。
    Using UnityEngine;  
    Using System.Collections;  
       
    Public class TimerExample : MonoBehaviour  
    {  
        Void Start()  
        {  
            //Log "Hello!" 5 times with 1 second between each log  
            StartCoroutine(RepeatMessage(5, 1.0f,"Hello!"));  
        }  
       
        IEnumerator RepeatMessage(int count,float frequency,string message)  
        {  
            for(int i = 0; i < count; i++)  
            {  
                Debug.Log(message);  
                for(float timer = 0; timer < frequency; timer += Time.deltaTime)  
                    Yield return 0;  
                   
            }  
        }  
    } 

    嵌套的协程
    在此之前,我们yield的时候总是用0(或者null),仅仅告诉程序在继续执行前等待下一帧。协程最强大的一个功能就是它们可以通过使用yield语句来相互嵌套。
    眼见为实,我们先来创建一个简单的Wait()程序,不需要它做任何事,只需要在运行的时候等待一段时间就结束。
    IEnumerator Wait(float duration)  
    {  
        for(float timer = 0; timer < duration; timer += Time.deltaTime)  
            Yield return 0;  
    } 

    接下来我们要编写另一个协程,如下:

    Using UnityEngine;  
    Using System.Collections;  
       
    Public class TimerExample : MonoBehaviour  
    {  
        voidStart()  
        {  
            StartCoroutine(SaySomeThings());  
        }  
       
        //Say some messages separated by time  
        IEnumerator SaySomeThings()  
        {  
            Debug.Log("The routine has started");  
            Yield return StartCoroutine(Wait(1.0f));  
            Debug.Log("1 second has passed since the last message");  
            Yield return StartCoroutine(Wait(2.5f));  
            Debug.Log("2.5 seconds have passed since the last message");  
        }  
       
        //Our wait function  
        IEnumerator Wait(float duration)  
        {  
            for(float timer = 0; timer < duration; timer += Time.deltaTime)  
                Yield return 0;  
        }  
    }  


    第二个方法用了yield,但它并没有用0或者null,而是用了Wait()来yield,这相当于是说,“不再继续执行本程序,直到Wait程序结束”。
    现在,协程在程序设计方面的能力要开始展现了。
    控制对象行为的例子
    在最后一个例子中,我们就来看看协程如何像创建方便的计时器一样来控制对象行为。协程不仅仅可以使用可计数的时间来yield,它还能很巧妙地利用任何条件。将它与嵌套结合使用,你会得到控制游戏对象状态的最强大工具。
    运动到某一位置
    对于下面这个简单脚本组件,我们可以在Inspector面板中给targetPosition和moveSpeed变量赋值,程序运行的时候,该对象就会在协程的作用下,以我们给定的速度运动到给定的位置。
    usingUnityEngine;  
    Using System.Collections;  
       
    Public class MoveExample : MonoBehaviour  
    {  
        ublic Vector3 targetPosition;  
        ublic float moveSpeed;  
       
        Void Start()  
        {  
            StartCoroutine(MoveToPosition(targetPosition));  
        }  
       
        IEnumerator MoveToPosition(Vector3 target)  
        {  
            while(transform.position != target)  
            {  
                transform.position = Vector3.MoveTowards(transform.position, target, moveSpeed * Time.deltaTime);  
                Yield return 0;  
            }  
        }  
    }  


    这样,这个程序并没有通过一个计时器或者无限循环,而是根据对象是否到达指定位置来yield。
    按指定路径前进

    我们可以让运动到某一位置的程序做更多,不仅仅是一个指定位置,我们还可以通过数组来给它赋值更多的位置,通过MoveToPosition() ,我们可以让它在这些点之间持续运动。

    Using UnityEngine;  
    Using System.Collections;  
       
    Public class MoveExample : MonoBehaviour  
    {  
        ublic Vector3[] path;  
        ublic float moveSpeed;  
       
        Void Start()  
        {  
            StartCoroutine(MoveOnPath(true));  
        }  
       
        IEnumerator MoveOnPath(bool loop)  
        {  
            do  
            {  
                foreach(var point in path)  
                    Yield return StartCoroutine(MoveToPosition(point));  
            }  
            while(loop);  
        }  
       
        IEnumerator MoveToPosition(Vector3 target)  
        {  
            while(transform.position != target)  
            {  
                transform.position = Vector3.MoveTowards(transform.position, target, moveSpeed * Time.deltaTime);  
                Yield return 0;  
            }  
        }  
    }  



    我还加了一个布尔变量,你可以控制在对象运动到最后一个点时是否要进行循环。

    把Wait()程序加进来,这样就能让我们的对象在某个点就可以选择是否暂停下来,就像一个正在巡逻的AI守卫一样,这真是锦上添花啊!


    注意:
    如果你刚接触协程,我希望这两个教程能帮助你了解它们是如何工作的,以及如何来使用它们。以下是一些在使用协程时须谨记的其他注意事项:


    1. 多个协程可以同时运行,它们会根据各自的启动顺序来更新;
    2. 协程可以嵌套任意多层(在这个例子中我们只嵌套了一层);
    3. 如果你想让多个脚本访问一个协程,那么你可以定义静态的协程;
    4. 协程不是多线程(尽管它们看上去是这样的),它们运行在同一线程中,跟普通的脚本一样;
    5. 如果你的程序需要进行大量的计算,那么可以考虑在一个随时间进行的协程中处理它们;
    6. IEnumerator类型的方法不能带ref或者out型的参数,但可以带被传递的引用;



    如果你对协程的使用掌握到一定程度,可以继续学习Unity3D协程进阶-原理剖析
    展开全文
  • Unity如何使用带返回值的函数

    千次阅读 2018-07-11 16:45:20
    教程地址(观看视频需翻墙):https://unity3d.com/cn/learn/tutorials/topics/scripting/variables-and-functions?playlist=17117教程代码实例:using UnityEngine; using System.Collections; public class ...

    教程地址(观看视频需翻墙):https://unity3d.com/cn/learn/tutorials/topics/scripting/variables-and-functions?playlist=17117

    教程代码实例:

    using UnityEngine;

    using System.Collections;

     publicclass VariablesAndFunctions : MonoBehaviour

    {  

        int myInt = 5;

       

        void Start ()

        {

            myInt = MultiplyByTwo(myInt);

            Debug.Log(myInt);

        }

         

        int MultiplyByTwo (int number)

        {

            int ret;

            ret = number * 2;

            return ret;

        }

    }

    以前从来没有用过带返回值的函数,学习了!

    和不带返回值函数的区别是:

    1.没有void;

    2.返回值的类型需要和定义的函数类型相同;

    3.需要在函数最后写return你要的东西。

    展开全文
  • Unity3D协同程序(Coroutine)

    万次阅读 2013-08-27 11:34:50
    摘要下: 1. coroutine, 中文翻译“协程”。这个概念可能有点冷门,不过百度之,说是一种很古老的编程模型了,以前的操作系统里进程调度里用到过,现在操作系统...协程和线程差不多,线程的调度是由操作系统完成的

    摘要下:

    1.

    coroutine, 中文翻译“协程”。这个概念可能有点冷门,不过百度之,说是一种很古老的编程模型了,以前的操作系统里进程调度里用到过,现在操作系统的进程调度都是根据 时间片和优先级来进行轮换,以前是要程序自己来释放cpu的控制权,一直不释放一直也就占用着cpu,这种要求程序自己来进行调度的编程模型应该就叫“协 程”了。

    协程和线程差不多,线程的调度是由操作系统完成的,协程把这项任务交给了程序员自己实现,当然也就可以提高灵活性,另外协程的开销比线程要小,在程序里可以开更多的协程。

    一些语言里自带了对coroutine的实现,比如lua。c里面虽然没有coroutine,不过windows下提供了一种叫fiber的机制,叫做“纤程”,算是一种轻量级线程。

     

    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中,这样我们在编程时就可以方便的调用指定脚本 中的协同程序,而不是无法去管理,特别是对于只根据方法名来判断线程的方式在多人开发中很容易出错,这样的设计保证了对象、脚本的条理化管理,并防止了重 名。

    我的一些粗浅小结:

    1.Coroutines顾名思议是用来协助主要进程的,在Unity中感觉就是一个可动态添加和移除的Update()函数。它的调用在所有Update函数之后。

    Unity原文:

    • If you start a coroutine in LateUpdate it will also be called after LateUpdate just before rendering.
    • Coroutines are executed after all Update functions.

    2.yield就像是一个红绿灯,在满足紧跟在它后面的条件之前,这个协程会挂起,把执行权交给调用它的父函数,满足条件时就可以执行yield下面的代码。

    Unity原文:

    Normal coroutine updates are run after the Update function returns. A coroutine is function that can suspend its execution (yield) until the given given YieldInstruction finishes. Different uses of Coroutines:

    • yield;等待 all Update functions 已被call过,The coroutine will continue on the next frame.
    • yield WaitForSeconds(2);Continue after a specified time delay, after all Update functions have been called for the frame
    • yield WaitForFixedUpdate();Continue after all FixedUpdate has been called on all scripts
    • yield WWWContinue after a WWW download has completed.
    • yield StartCoroutine(MyFunc); Chains the coroutine, and will wait for the MyFunc coroutine to complete first.

     

    协同的用法

    Yield中断:(有中断就代表程序停在该处,等待yield后的时间结束再继续执行后面的语句。)

    http://unity3d.com/support/documentation/ScriptReference/index.Coroutines_26_Yield.html

    http://game.ceeger.com/Script/Overview/Overview.Coroutines_Yield.html

    注意:

    • 在unity C#中yield(中断)语句必须要在IEnumerator类型里

    • C#方法,方法的返回类型为IEnumerator,返回值如(eg: yield return new WaitForSeconds(2); 或者 yield return null;)。

    • yields不可以在Update或者FixedUpdate里使用。


    示例:
    这个例子将执行Do,但是do之后的print会立刻执行:

    复制代码
    using UnityEngine;
    using System.Collections;
    
    public class example : MonoBehaviour {
    public static IEnumerator Do() {
      print("Do now");
      yield return new WaitForSeconds(2);
      print("Do 2 seconds later");
    }
    void Awake() {
      Do();    //执行DO,但是do后的语句继续执行
      print("This is printed immediately");
    }
    复制代码

    这个例子将执行Do,并等待,直到Do完成再执行其他语句:

    复制代码
    using UnityEngine;
    using System.Collections;
    
    public class example : MonoBehaviour {
        IEnumerator Do() {
            print("Do now");
            yield return new WaitForSeconds(2);
            print("Do 2 seconds later");
        }
        IEnumerator Awake() {
            yield return StartCoroutine("Do");    //Yield StartCoroutine就代表中断式的协同工作
            print("Also after 2 seconds");
            print("This is after the Do coroutine has finished execution");
        }
    }
    复制代码

    Coroutine协同:(为什么要有Coroutine的概念? 因为有中断的存在,试想一个程序都靠Yield来暂停的话,如何做到一个事件暂停的过程中,暂停过程中继续执行后面的程序? 那么就要靠Coroutine来实现。)

    http://unity3d.com/support/documentation/ScriptReference/MonoBehaviour.StartCoroutine.html?from=YieldInstruction 

    http://game.ceeger.com/Script/MonoBehaviour/MonoBehaviour.StartCoroutine.html

    • 可以在UPdate或者FixedUpdate里使用coroutine
    • 这里有两种:StartCoroutine(协同工作) 和 yield return StartCoroutine(中断式的协同工作)
    • 有yield的代表先执行完本语句(不管多长时间),或者执行完本yield方法调用,才执行后续语句。例如StartCoroutine(WaitAndPrint(2.0F)),继续执行StartCoroutine后面的语句,
    • 没有yield的代表继续顺序执行。例如:yield return StartCoroutine(WaitAndPrint(2.0F)),代表StartCoroutine(WaitAndPrint(2.0F))函数里等待全部执行完,再执行StartCoroutine后面的语句

     StartCoroutine的例子:

    复制代码
    // In this example we show how to invoke a coroutine and continue executing
    // the function in parallel.
    // 此例演示如何调用协同程序和它的执行
    function Start() {
        // - After 0 seconds, prints "Starting 0.0"
        // - After 0 seconds, prints "Before WaitAndPrint Finishes 0.0"
        // - After 2 seconds, prints "WaitAndPrint 2.0"
        // 先打印"Starting 0.0"和"Before WaitAndPrint Finishes 0.0"两句,2秒后打印"WaitAndPrint 2.0"
        print ("Starting " + Time.time );
        // Start function WaitAndPrint as a coroutine. And continue execution while it is running
        // this is the same as WaintAndPrint(2.0) as the compiler does it for you automatically
        // 协同程序WaitAndPrint在Start函数内执行,可以视同于它与Start函数同步执行.
        StartCoroutine(WaitAndPrint(2.0)); 
        print ("Before WaitAndPrint Finishes " + Time.time );
    }
    
    function WaitAndPrint (waitTime : float) {
        // suspend execution for waitTime seconds
        // 暂停执行waitTime秒
        yield WaitForSeconds (waitTime);
        print ("WaitAndPrint "+ Time.time );
    }
    复制代码

    C# 版本

    复制代码
    using UnityEngine;
    using System.Collections;
    
    public class example : MonoBehaviour {
        void Start() {
        print("Starting " + Time.time);
            StartCoroutine(WaitAndPrint(2.0F));
            print("Before WaitAndPrint Finishes " + Time.time);
        }
        IEnumerator WaitAndPrint(float waitTime) {
            yield return new WaitForSeconds(waitTime);
            print("WaitAndPrint " + Time.time);
        }
    }
    复制代码

    yield return StartCoroutine的例子:

    复制代码
    // In this example we show how to invoke a coroutine and wait until it 
    // is completed
    // 在这个例子中我们演示如何调用协同程序并直到它执行完成.
    function Start() {
        // - After 0 seconds, prints "Starting 0.0"
        // - After 2 seconds, prints "WaitAndPrint 2.0"
        // - After 2 seconds, prints "Done 2.0"
        // 0秒时打印"Starting 0.0",2秒后打印"WaitAndPrint 2.0"和"Done 2.0"
        print ("Starting " + Time.time );
        // Start function WaitAndPrint as a coroutine. And wait until it is completed.
        // the same as yield WaitAndPrint(2.0);
        // 运行WaitAndPrint直到完成
        yield StartCoroutine(WaitAndPrint(2.0));
        print ("Done " + Time.time );
    }
    
    function WaitAndPrint (waitTime : float) {
        // suspend execution for waitTime seconds
        // 等待waitTime秒
        yield WaitForSeconds (waitTime);
        print ("WaitAndPrint "+ Time.time );
    }
    复制代码

    C#版本

    复制代码
    using UnityEngine;
    using System.Collections;
    
    public class example : MonoBehaviour {
        IEnumerator Start() {
            print("Starting " + Time.time);
            yield return StartCoroutine(WaitAndPrint(2.0F));
            print("Done " + Time.time);
        }
        IEnumerator WaitAndPrint(float waitTime) {
            yield return new WaitForSeconds(waitTime);
            print("WaitAndPrint " + Time.time);
        }
    }
    复制代码

     C#中的Coroutine与JavaScript中的区别:

    1:Coroutines 必须要是 IEnumerator 返回类型:

    IEnumerator MyCoroutine()
    {
        //This is a coroutine
    }

    2:在C#中要使用 yield return而不是yield :

    Remember that we need to return an IEnumerable, so the Javascript yield; becomes yield return 0; in C#

    IEnumerator MyCoroutine()
    {
        DoSomething();
        yield return 0; //Wait one frame, the 0 here is only because we need to return an IEnumerable
        DoSomethingElse();
    }

    since C# requires you to use the new operator to create objects, if you want to use WaitForSeconds you have to use it like this:

    IEnumerator MyCoroutine()
    {
        DoSomething();
        yield return new WaitForSeconds(2.0f);  //Wait 2 seconds
        DoSomethingElse();
    }

    3:调用Coroutine要使用 StartCoroutine 方法:

    复制代码
    public class MyScript : MonoBehaviour
    {
        void Start()
        {
            StartCoroutine(MyCoroutine());
        }
     
        IEnumerator MyCoroutine()
        {
            //This is a coroutine
        }
    }
    复制代码

     

    展开全文
  • Unity3D协程介绍 以及 使用

    万次阅读 多人点赞 2014-08-11 15:07:30
    Unity中,协程(Coroutines)的形式是我最喜欢的功能之一,几乎在所有的项目中,我都会使用它来控制运动,序列,以及对象的行为。在这个教程中,我将会说明协程是如何工作的,并且会附上一些例子来介绍


    作者ChevyRay 2013928日,snaker7译  原文地址:http://unitypatterns.com/introduction-to-coroutines/

    Unity中,协程(Coroutines)的形式是我最喜欢的功能之一,几乎在所有的项目中,我都会使用它来控制运动,序列,以及对象的行为。在这个教程中,我将会说明协程是如何工作的,并且会附上一些例子来介绍它的用法。

     

     

     

    协程介绍


    Unity的协程系统是基于C#的一个简单而强大的接口 ,IEnumerator,它允许你为自己的集合类型编写枚举器。这一点你不必关注太多,我们直接进入一个简单的例子来看看协程到底能干什么。首先,我们来看一下这段简单的代码...

     

    倒计时器

    这是一个简单的脚本组件,只做了倒计时,并且在到达0的时候log一个信息。


    using Unity Engine;
    using System.Collections;
     
    public class Countdown : MonoBehaviour
    {
        public float timer = 3;
     
        void Update()
        {
            timer -= Time.deltaTime;
            if(timer <= 0)
                Debug.Log("Timer has finished!");
        }
    }


     

    还不错,代码简短实用,但问题是,如果我们需要复杂的脚本组件(像一个角色或者敌人的类),拥有多个计时器呢?刚开始的时候,我们的代码也许会是这样的:


    using UnityEngine;
    using System.Collections;
     
    public class MultiTimer : MonoBehaviour
    {
        public float firstTimer = 3;
        public float secondTimer = 2;
        public float thirdTimer = 1;
     
        void Update()
        {
            firstTimer -= Time.deltaTime;
            if(firstTimer <= 0)
                Debug.Log("First timer has finished!");
     
            secondTimer -= Time.deltaTime;
            if(secondTimer <= 0)
                Debug.Log("Second timer has finished!");
     
            thirdTimer -= Time.deltaTime;
            if(thirdTimer <= 0)
                Debug.Log("Third timer has finished!");
        }
    }


     

    尽管不是太糟糕,但是我个人不是很喜欢自己的代码中充斥着这些计时器变量,它们看上去很乱,而且当我需要重新开始计时的时候还得记得去重置它们(这活我经常忘记做)。

     

    如果我只用一个for循环来做这些,看上去是否会好很多?


    for(float timer = 3; timer >= 0; timer -= Time.deltaTime)
    {
        //Just do nothing...
    }
    Debug.Log("This happens after 5 seconds!");


     

    现在每一个计时器变量都成为for循环的一部分了,这看上去好多了,而且我不需要去单独设置每一个跌倒变量。

     

    好的,你可能现在明白我的意思:协程可以做的正是这一点!

     

    码入你的协程!

    现在,这里提供了上面例子运用协程的版本!我建议你从这里开始跟着我来写一个简单的脚本组件,这样你可以在你自己的程序中看到它是如何工作的。


    using UnityEngine;
    using System.Collections;
     
    public class CoroutineCountdown : MonoBehaviour
    {
        void Start()
        {
            StartCoroutine(Countdown());
        }
     
        IEnumerator Countdown()
        {
            for(floattimer = 3; timer >= 0; timer -= Time.deltaTime)
                Yield return 0;
     
            Debug.Log("This message appears after 3 seconds!");
        }
    }


     

    这看上去有点不一样,没关系,接下来我会解释这里到底发生了什么。

     


    StartCoroutine(Countdown());


     

    这一行用来开始我们的Countdown程序,注意,我并没有给它传入参数,但是这个方法调用了它自己(这是通过传递Countdown的return返回值来实现的)。

     

    Yield

    在Countdown方法中其他的都很好理解,除了两个部分:

    l IEnumerator 的返回值

    l For循环中的yield return

     

    为了能在连续的多帧中(在这个例子中,3秒钟等同于很多帧)调用该方法,Unity必须通过某种方式来存储这个方法的状态,这是通过IEnumerator 中使用yield return语句得到的返回值,当你“yield”一个方法时,你相当于说了,“现在停止这个方法,然后在下一帧中从这里重新开始!”。

     

    注意:用0或者nullyield的意思是告诉协程等待下一帧,直到继续执行为止。当然,同样的你可以继续yield其他协程,我会在下一个教程中讲到这些。

     

    一些例子

    协程在刚开始接触的时候是非常难以理解的,无论是新手还是经验丰富的程序员我都见过他们对于协程语句一筹莫展的时候。因此我认为通过例子来理解它是最好的方法,这里有一些简单的协程例子:

     

    多次输出“Hello

    记住,yield return是“停止执行方法,并且在下一帧从这里重新开始”,这意味着你可以这样做:


    //This will say hello 5 times, once each frame for 5 frames
    IEnumerator SayHelloFiveTimes()
    {
        Yield return 0;
        Debug.Log("Hello");
        Yield return 0;
        Debug.Log("Hello");
        Yield return 0;
        Debug.Log("Hello");
        Yield return 0;
        Debug.Log("Hello");
        Yield return 0;
        Debug.Log("Hello");
    }
     
    //This will do the exact same thing as the above function!
    IEnumerator SayHello5Times()
    {
        for(inti = 0; i < 5; i++)
        {
            Debug.Log("Hello");
            Yield return 0;
        }
    }


     

    每一帧输出“Hello”,无限循环。。。

    通过在一个while循环中使用yield,你可以得到一个无限循环的协程,这几乎就跟一个Update()循环等同。。。


    //Once started, this will run until manually stopped or the object is destroyed
    IEnumerator SayHelloEveryFrame()
    {
        while(true)
        {
            //1. Say hello
            Debug.Log("Hello");
     
            //2. Wait until next frame
            Yield return 0;
     
        }//3. This is a forever-loop, goto 1
    }


     

    计时

    ...不过跟Update()不一样的是,你可以在协程中做一些更有趣的事:


    IEnumerator CountSeconds()
    {
        int seconds = 0;
     
        while(true)
        {
            for(float timer = 0; timer < 1; timer += Time.deltaTime)
                Yield return 0;
     
            seconds++;
            Debug.Log(seconds +" seconds have passed since the Coroutine started.");
        }
    }


     

    这个方法突出了协程一个非常酷的地方:方法的状态被存储了,这使得方法中定义的这些变量都会保存它们的值,即使是在不同的帧中。还记得这个教程开始时那些烦人的计时器变量吗?通过协程,我们再也不需要担心它们了,只需要把变量直接放到方法里面!

     

    开始和终止协程

    之前,我们已经学过了通过 StartCoroutine()方法来开始一个协程,就像这样:


    StartCoroutine(Countdown());


    如果我们想要终止所有的协程,可以通过StopAllCoroutines()方法来实现,它的所要做的就跟它的名字所表达的一样。注意,这只会终止在调用该方法的对象中(应该是指调用这个方法的类吧)开始的协程,对于其他的MonoBehavior类中运行的协程不起作用。

     

    如果我们有以下这样两条协程语句:


    StartCoroutine(FirstTimer());
    StartCoroutine(SecondTimer());


    。。。那我们怎么终止其中的一个协程呢?在这个例子里,这是不可能的,如果你想要终止某一个特定的协程,那么你必须得在开始协程的时候将它的方法名作为字符串,就像这样:


    //If you start a Coroutine by name...
    StartCoroutine("FirstTimer");
    StartCoroutine("SecondTimer");
     
    //You can stop it anytime by name!
    StopCoroutine("FirstTimer");


     

    更多关于协程的学习

    即将为你带来:“Scripting with Coroutines”,一个更深入的介绍,关于如何使用协程以及如何通过协程编写对象行为。


    扩展链接

    Coroutines – Unity Script Reference

    如果你知道其他很棒的关于协程的Unity教程,或者相关的主题,请在回复中分享链接!当然,如果在教程有什么问题,比如链接无效或者其他一些问题,欢迎给我发邮件




    作者ChevyRay ,2013年9月28日,snaker7译 原文地址:http://unitypatterns.com/scripting-with-coroutines/

    请注意:这个关于协程的教程共有两部分,这是第二部分,如果您未曾看过第一部分——协程介绍,那么在阅读这部分内容之前建议您先了解一下。

    计时器例子

    第一个教程中,我们已经了解了协程如何让一个方法“暂停”下来,并且让它yield直到某些值到达我们给定的数值;并且利用它,我们还创建了一个很棒的计时器系统。协程一个很重要的内容是,它可以让普通的程序(比方说一个计时器)很容易地被抽象化并且被复用。

    协程的参数

    抽象化一个协程的第一个方法是给它传递参数,协程作为一个函数方法来说,它自然能够传递参数。这里有一个协程的例子,它在特定的地方输出了特定的信息。


    [csharp] view plaincopy
    1. Using UnityEngine;  
    2. Using System.Collections;  
    3.    
    4. Public class TimerExample : MonoBehaviour  
    5. {  
    6.     Void Start()  
    7.     {  
    8.         //Log "Hello!" 5 times with 1 second between each log  
    9.         StartCoroutine(RepeatMessage(5, 1.0f,"Hello!"));  
    10.     }  
    11.    
    12.     IEnumerator RepeatMessage(int count,float frequency,string message)  
    13.     {  
    14.         for(int i = 0; i < count; i++)  
    15.         {  
    16.             Debug.Log(message);  
    17.             for(float timer = 0; timer < frequency; timer += Time.deltaTime)  
    18.                 Yield return 0;  
    19.                
    20.         }  
    21.     }  
    22. }  


    嵌套的协程

    在此之前,我们yield的时候总是用0(或者null),仅仅告诉程序在继续执行前等待下一帧。协程最强大的一个功能就是它们可以通过使用yield语句来相互嵌套。

    眼见为实,我们先来创建一个简单的Wait()程序,不需要它做任何事,只需要在运行的时候等待一段时间就结束。


    [csharp] view plaincopy
    1. IEnumerator Wait(float duration)  
    2. {  
    3.     for(float timer = 0; timer < duration; timer += Time.deltaTime)  
    4.         Yield return 0;  
    5. }  


    接下来我们要编写另一个协程,如下:


    [csharp] view plaincopy
    1. Using UnityEngine;  
    2. Using System.Collections;  
    3.    
    4. Public class TimerExample : MonoBehaviour  
    5. {  
    6.     voidStart()  
    7.     {  
    8.         StartCoroutine(SaySomeThings());  
    9.     }  
    10.    
    11.     //Say some messages separated by time  
    12.     IEnumerator SaySomeThings()  
    13.     {  
    14.         Debug.Log("The routine has started");  
    15.         Yield return StartCoroutine(Wait(1.0f));  
    16.         Debug.Log("1 second has passed since the last message");  
    17.         Yield return StartCoroutine(Wait(2.5f));  
    18.         Debug.Log("2.5 seconds have passed since the last message");  
    19.     }  
    20.    
    21.     //Our wait function  
    22.     IEnumerator Wait(float duration)  
    23.     {  
    24.         for(float timer = 0; timer < duration; timer += Time.deltaTime)  
    25.             Yield return 0;  
    26.     }  
    27. }  


    第二个方法用了yield,但它并没有用0或者null,而是用了Wait()来yield,这相当于是说,“不再继续执行本程序,直到Wait程序结束”。

    现在,协程在程序设计方面的能力要开始展现了。

    控制对象行为的例子

    在最后一个例子中,我们就来看看协程如何像创建方便的计时器一样来控制对象行为。协程不仅仅可以使用可计数的时间来yield,它还能很巧妙地利用任何条件。将它与嵌套结合使用,你会得到控制游戏对象状态的最强大工具。

    运动到某一位置

    对于下面这个简单脚本组件,我们可以在Inspector面板中给targetPosition和moveSpeed变量赋值,程序运行的时候,该对象就会在协程的作用下,以我们给定的速度运动到给定的位置。


    [csharp] view plaincopy
    1. usingUnityEngine;  
    2. Using System.Collections;  
    3.    
    4. Public class MoveExample : MonoBehaviour  
    5. {  
    6.     ublic Vector3 targetPosition;  
    7.     ublic float moveSpeed;  
    8.    
    9.     Void Start()  
    10.     {  
    11.         StartCoroutine(MoveToPosition(targetPosition));  
    12.     }  
    13.    
    14.     IEnumerator MoveToPosition(Vector3 target)  
    15.     {  
    16.         while(transform.position != target)  
    17.         {  
    18.             transform.position = Vector3.MoveTowards(transform.position, target, moveSpeed * Time.deltaTime);  
    19.             Yield return 0;  
    20.         }  
    21.     }  
    22. }  


    这样,这个程序并没有通过一个计时器或者无限循环,而是根据对象是否到达指定位置来yield

    按指定路径前进

    我们可以让运动到某一位置的程序做更多,不仅仅是一个指定位置,我们还可以通过数组来给它赋值更多的位置,通过MoveToPosition() ,我们可以让它在这些点之间持续运动。


    [csharp] view plaincopy
    1. Using UnityEngine;  
    2. Using System.Collections;  
    3.    
    4. Public class MoveExample : MonoBehaviour  
    5. {  
    6.     ublic Vector3[] path;  
    7.     ublic float moveSpeed;  
    8.    
    9.     Void Start()  
    10.     {  
    11.         StartCoroutine(MoveOnPath(true));  
    12.     }  
    13.    
    14.     IEnumerator MoveOnPath(bool loop)  
    15.     {  
    16.         do  
    17.         {  
    18.             foreach(var point in path)  
    19.                 Yield return StartCoroutine(MoveToPosition(point));  
    20.         }  
    21.         while(loop);  
    22.     }  
    23.    
    24.     IEnumerator MoveToPosition(Vector3 target)  
    25.     {  
    26.         while(transform.position != target)  
    27.         {  
    28.             transform.position = Vector3.MoveTowards(transform.position, target, moveSpeed * Time.deltaTime);  
    29.             Yield return 0;  
    30.         }  
    31.     }  
    32. }  


    我还加了一个布尔变量,你可以控制在对象运动到最后一个点时是否要进行循环。

    Wait()程序加进来,这样就能让我们的对象在某个点就可以选择是否暂停下来,就像一个正在巡逻的AI守卫一样,这真是锦上添花啊!

    注意:

    如果你刚接触协程,我希望这两个教程能帮助你了解它们是如何工作的,以及如何来使用它们。以下是一些在使用协程时须谨记的其他注意事项:


    • l 在程序中调用StopCoroutine()方法只能终止以字符串形式启动(开始)的协程;
    • l 多个协程可以同时运行,它们会根据各自的启动顺序来更新;
    • l 协程可以嵌套任意多层(在这个例子中我们只嵌套了一层);
    • l 如果你想让多个脚本访问一个协程,那么你可以定义静态的协程;
    • l 协程不是多线程(尽管它们看上去是这样的),它们运行在同一线程中,跟普通的脚本一样;
    • l 如果你的程序需要进行大量的计算,那么可以考虑在一个随时间进行的协程中处理它们;
    • l IEnumerator类型的方法不能带ref或者out型的参数,但可以带被传递的引用;
    • l 目前在Unity中没有简便的方法来检测作用于对象的协程数量以及具体是哪些协程作用在对象上。


    如果您发现教程中存在问题和错误的信息,或者有任何建议又或者您想要在这里看到其他需要的教程,可以发邮件或者在评论中留言。




    展开全文
  • 协程的官方定义是一种具有暂停执行并将控制权返回给Unity,待下一帧时继续执行。通俗点讲就是,协程是一种可以分部执行的函数,即该函数不是每次调用时都会执行函数体内的全部方法,而是只调用其中部分代码。写到...
  • Unity3D协程进阶-原理剖析

    千次阅读 2017-01-04 22:03:21
    本文主要分为三部分:  1)yield return, IEnumerator 和 Unity StartCoroutine 的关系和理解  2)Cortoutine 扩展——Extending Coroutines: Return Values and Error Handling  3)Co
  • Unity——协程(暂停功能)

    千次阅读 2018-05-17 15:52:18
    unity中,协程的概念类似于线程,是一种特殊的函数。可以中断现在正在执行的代码。中断指令结束后在开始执行之前的代码继续执行。所以协程可以用来实现暂停功能。在c#脚本中,使用协程应该注意:(1)协程返回值...
  • 一些必须通过异步处理,又需要赋值的情况下,可以用使用action委托来操作。一个简单的例子: public Texture2D texture2D; void Start() { StartCoroutine(LoadBundleTexAsync(value =&...
  • unity3D 协程如何模拟Update

    千次阅读 2016-05-17 11:36:35
    Unity协程系统是基于C#的一个简单而强大的接口 ,IEnumerator,它允许你为自己的集合类型编写枚举器。这一点你不必关注太多,我们直接进入一个简单的例子来看看协程到底能干什么。首先,我们来看一下这段简单的...
  • Unity3D协程

    2015-06-16 18:39:57
    协程:当开启一个协程后,Unity会进入协程函数执行,当遇到各种类型的yield语句时,Unity的执行权会回到开启协程的代码位置,继续这一帧的执行,当满足yield后面的语句条件时,Unity会从yield语句后继续执行。...
  • Unity3D 协程管理器和协程回调函数

    千次阅读 2017-08-05 22:12:56
    unity中使用协程的时候,会经常遇到两个问题。 绑定在GameObject上的协程,会因为Active false而终止运行。当使用一个协程的时候,如果注册一个通过的回调函数,来处理结果,并且可以拿到协程返回的值。  第...
  • 1. StartCoroutine 通常用于实现协程,你如果明白多线程编程的话就比较好理解了。 当然,协程不是多线程,而是在一个线程中实现的,启动一个辅助的线程。使用线程的好处就是不会出现界面卡死的情况,如果有一次...
  • unity3D协程Coroutine

    2016-11-28 10:58:31
    第二次写博客,对于unity3D的学习已经持续了近两个月,最近被其中的协程困惑很久,研究下简单写个记录供大家参考,欢迎各位大神指正错误。 一个最简单的实现协程的代码: using UnityEngine; using System....
1 2 3 4 5 ... 20
收藏数 621
精华内容 248
关键字:

unity3d 协程 带返回值