2017-02-15 09:31:12 ybhjx 阅读数 4212
  • Unity3D协程-基础篇

    本课程为Unity3D协程技术精讲中的基础部分,协程是UnityEngine技术里面为常用且设计为精髓的技术;基础部分主要讲解协成的前世今生,其中包括设计模式、实现模式、验证模式的递进式推进过程让同学们掌握协程原理与程序设计。

    1974 人正在学习 去看看 曾家海


能告诉我什么是协程吗?

协程的官方定义是一种具有暂停执行并将控制权返回给Unity,待下一帧时继续执行。通俗点讲就是,协程是一种可以分部执行的函数,即该函数不是每次调用时都会执行函数体内的全部方法,而是只调用其中部分代码。写到这里不知道您有没有发现,该定义有点像IEnumerator的延迟执行。举一个例子:

复制代码
void Start ()
{
    IEnumerator numbers = YieldThreeNumbers ();
    for (int i = 0; i < 3; i++)
        {
        if(!numbers.MoveNext())    
            break;
        Debug.Log((int)numbers.Current);
    }
}

IEnumerator YieldThreeNumbers()
{
    yield return 1;
    yield return 2;
    yield return 3;
}
复制代码

结果:

可以看到当我们执行一次MoveNext方法,才会取得当前当前的值,所以需要循环调用MoveNext才能将全部值取出。

协程也是同样的方法:每一帧都会调用MoveNext方法,期间保存了下一次执行的位置,等到下一帧时会在该位置继续执行。

PS: 在C#中,yield和IEnumerator一起使用时实际上是实现了Itertor模式,详情可参考这篇文章。http://csharpindepth.com/articles/chapter6/iteratorblockimplementation.aspx

由此也可以看到,协程其实与多线程一点关系都没有。协程是在主线程中执行的,且每次只能执行一个协程。

协程该怎么用呢?

启动协程

首先我们需要定义一个返回IEnumerator的方法,如:

IEnumerator GetEnumerator()
{
    Yield return null;
}

然后在调用StarCoroutine方法,其签名如下:

Coroutine StartCoroutine(string methodName,object value=null);

Coroutine StartCoroutine(IEnumerator routine);

在是使用第一个方法时,我们直接将传入上面定义的方法名:

StartCoroutine(“GetEnumerator”);

注意该方法的参数是IEnumerator,所以我们可以直接将调用上面定义的方法的返回值传入:

StartCoroutine(GetEnumertor());

下面看一个来自官网的例子:

复制代码
IEnumerator Fade()
{
    for (float f = 1f; f >= 0; f -= 0.1f)
    {
        Color c = renderer.material.color;
        //减少a值,即透明度
        c.a = f;
        renderer.material.color = c;
        yield return null;
    }
}

void Update()
{
    if (Input.GetKeyDown("f"))
    {
        //没按一次"f"键,gameObject的透明度都在减少,实现渐隐的效果
        StartCoroutine("Fade");
}
    
复制代码

当然,我们不仅仅可以yield null,还可以yield其它的表达式:

1. 其它的数值,如0:

和null类似,不过不推荐。因为会存在装箱,拆箱的问题,或多或少会影响性能。

2. WaitForEndOfFrame

等待至所有的Camera和GUI都呈现好了之后执行。

3. WaitForFixedUpdate

等待至所有物理都计算后执行

4. WaitForSeconds

在指定时间段内不执行

5. WWW

等待一个web请求完成

6. 另一个协程

这是比较有意思的一点。因为StartCoroutine的返回值是Coroutine,所以我们可以yield另一个协程。

要注意的是,如果设置Time.timeScale为0时,yield return new WaitForSeconds(x)是不会恢复继续执行。

停止协程

void StopCoroutine(string methodName);

void StopCoroutine(IEnumerator routine);

其中StopCortouine(string methodName)只能停止由与之相对应的StarCoroutine(string methodName)启动的协程。

还有其它的方法来停止协程,但都不是停止某个指定的协程,而是停止多个协程。

void StopAllCoroutines()

停止该behavior内的全部协程

void SetActive(bool value);

将behavior的active设为false后,其内部的协程也都会停止。

等等,我觉得还少了点什么...

协程可以将一个方法,放到多个帧内执行,在很大程度上提高了性能。但协程也是有缺陷的:

  1. 不支持返回值;
  2. 不支持异常处理;
  3. 不支持泛型;
  4. 不支持锁;

下面我们来解决前三个问题,为协程添加返回值、异常处理和泛型。关于第四个问题的解决方式,请参考最下方的链接:

返回值:

复制代码
public class ReturnValueCoroutine
{
    private object result;
    public object Result
    {
        get {return result;}
    }
    public UnityEngine.Coroutine Coroutine;
    
    public IEnumerator InternalRoutine(IEnumerator coroutine)
    {
        while(true)
        {
            if(!coroutine.MoveNext()){
                yield break;
            }
            object yielded = coroutine.Current;
            
            if(yielded != null){
                result = yielded;
                yield break;
            }
            else{
                yield return coroutine.Current;
            }
        }
    }
}

public class Demo : MonoBehaviour
{
    IEnumerator Start ()
    {
        ReturnValueCoroutine myCoroutine = new ReturnValueCoroutine ();
        myCoroutine.Coroutine = StartCoroutine (myCoroutine.InternalRoutine(TestNewRoutine()));
        yield return myCoroutine.Coroutine;
        Debug.Log (myCoroutine.Result);
    }
    
    IEnumerator TestNewRoutine()
    {
        yield return 10;
    }
}
复制代码

泛型:

复制代码
public static class MonoBehaviorExt
{
    public static Coroutine<T> StartCoroutine<T>(this MonoBehaviour obj, IEnumerator coroutine){
        Coroutine<T> coroutineObject = new Coroutine<T>();
        coroutineObject.coroutine = obj.StartCoroutine(coroutineObject.InternalRoutine(coroutine));
        return coroutineObject;
    }
}

public class Coroutine<T>{
    private T result;
    public T Result
    {
        get {return result;}
    }
    public Coroutine coroutine;
    
    public IEnumerator InternalRoutine(IEnumerator coroutine){
        while(true){
            if(!coroutine.MoveNext()){
                yield break;
            }
            object yielded = coroutine.Current;
            
            if(yielded != null && yielded.GetType() == typeof(T)){
                result = (T)yielded;
                yield break;
            }
            else{
                yield return coroutine.Current;
            }
        }
    }
}

public class Demo : MonoBehaviour
{    
    Coroutine<int> routine;
    IEnumerator Start () {
        routine = this.StartCoroutine<int>(TestNewRoutine()); //Start our new routine
        yield return routine; // wait as we normally can
    }
    
    IEnumerator TestNewRoutine(){
        yield return null;
        yield return new WaitForSeconds(2f);
        yield return 10;
    }

    void Update()
    {
        //因为延时,所以要等待一段时间才能显示
        Debug.Log(routine.Result); 
    }
}
复制代码

异常处理:

复制代码
public static class MonoBehaviorExt
{
    public static Coroutine<T> StartCoroutine<T>(this MonoBehaviour obj, IEnumerator coroutine){
        Coroutine<T> coroutineObject = new Coroutine<T>();
        coroutineObject.coroutine = obj.StartCoroutine(coroutineObject.InternalRoutine(coroutine));
        return coroutineObject;
    }
}

public class Coroutine<T>{
    public T Result {
        get{
            if(e != null){
                throw e;
            }
            return result;
        }
    }
    private T result;
    private Exception e;
    public UnityEngine.Coroutine coroutine;
    
    public IEnumerator InternalRoutine(IEnumerator coroutine){
        while(true){
            try{
                if(!coroutine.MoveNext()){
                    yield break;
                }
            }
            catch(Exception e){
                this.e = e;
                yield break;
            }
            object yielded = coroutine.Current;
            if(yielded != null && yielded.GetType() == typeof(T)){
                result = (T)yielded;
                yield break;
            }
            else{
                yield return coroutine.Current;
            }
        }
    }
}

public class Demo : MonoBehaviour
{
    IEnumerator Start () {
        var routine = this.StartCoroutine<int>(TestNewRoutineGivesException());
        yield return routine.coroutine;
        try{
            Debug.Log(routine.Result);
        }
        catch(Exception e){
            Debug.Log(e.Message);
            // do something
            Debug.Break();
        }
    }
    
    IEnumerator TestNewRoutineGivesException(){
        yield return null;
        yield return new WaitForSeconds(2f);
        throw new Exception("Bad thing!");
    }
}
复制代码

你说的我都知道了,还有别的吗?

1. 使用lamada表达式接受返回值:http://answers.unity3d.com/questions/207733/can-coroutines-return-a-value.html

2. Wrapping Unity C# Coroutines for Exception Handling, Value Retrieval, and Locking:http://zingweb.com/blog/2013/02/05/unity-coroutine-wrapper/

3. CoroutineScheduler:http://wiki.unity3d.com/index.php?title=CoroutineScheduler

转载:http://www.cnblogs.com/mezero/p/3953838.html
2017-08-09 12:43:07 SakuraLLj 阅读数 1698
  • Unity3D协程-基础篇

    本课程为Unity3D协程技术精讲中的基础部分,协程是UnityEngine技术里面为常用且设计为精髓的技术;基础部分主要讲解协成的前世今生,其中包括设计模式、实现模式、验证模式的递进式推进过程让同学们掌握协程原理与程序设计。

    1974 人正在学习 去看看 曾家海

什么是协程(Coroutine)

在unity中,协程的概念类似于线程,它是一种特殊的函数,能够中断(通过yield语句)执行当前的代码,直到中断指令(YieldInstruction)结束后再接着执行之前的代码。

典型用法

这里写图片描述

注意事项

  1. 协程的返回值必须是IEnunmerator
  2. 协程的参数不能加ref或out
  3. 在c#脚本中,必须通过StartCoroutine来驱动协程
  4. yield语句要用yield return 来代替
  5. StartCoroutine只能在monoBehaviour或其子类中使用
  6. StartCoroutine接收协程的名称(字符串)或IEnumerator实例作为参数
  7. 在Unity中,可以使用函数StopCoroutine来终止一个协程,但这种方法只适用于使用协程的名称作为参数启动的协程。使用StopAllCoroutine可以终止该monoBehaviour中的所有协程。还有一个不常用的方法来终止协程,即将协程所在的游戏对象的active属性设置为false,但是当再次将active改为TRUE时,协程不会自动启动。
  8. yield return 语句不能用在try-catch语句块中,但可以用在try-finally中的try语句块中
  9. yield return语句不能放在匿名方法中
  10. yield return语句不能放在unsafe语句块中

示例代码

 IEnumerator WaitAndPrint()
    {
        yield return new WaitForSeconds(1);//WaitForSeconds()受Time.timeScale影响,当Time.timeScale为0时,yield return new WaitForSeconds(n) 将失效
        print("WaitAndPrint");
    }
 IEnumerator Download()
    {
        string url = "http://download.csdn.net/detail/sakurallj/9926053";
        WWW w =new WWW(url);
        yield return w;
        if (w!=null)
        {
            Debug.Log(w.text);
        }
        StopAllCoroutines();
    }

参考
Unity官方案例精讲的协程章节,下载地址在这里

2016-07-21 14:59:13 tom_221x 阅读数 3683
  • Unity3D协程-基础篇

    本课程为Unity3D协程技术精讲中的基础部分,协程是UnityEngine技术里面为常用且设计为精髓的技术;基础部分主要讲解协成的前世今生,其中包括设计模式、实现模式、验证模式的递进式推进过程让同学们掌握协程原理与程序设计。

    1974 人正在学习 去看看 曾家海

     unity中使用协程的时候,会经常遇到两个问题。

  • 绑定在GameObject上的协程,会因为Active false而终止运行。
  • 当使用一个协程的时候,如果注册一个通过的回调函数,来处理结果,并且可以拿到协程返回的值。
      第一个问题,可以使用一个协程管理器。挂在独立的GameObject上,不会受到操作的GameObject的active状态影响。
      第二个问题,在协程函数里,自然我们拿到协程完成的结果,并调用回调。但是不够通用,需要在每个协程函数里写同样的调用。我们可以通过给协程注入一个回调函数来统一处理,并传入协程的返回值。
       
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using System;

public static class ACoroutineManager 
{
	private class  InnerCoroutine : MonoBehaviour {}
	private static InnerCoroutine coroutine;

	static ACoroutineManager()
	{
		if (coroutine == null)
		{
			coroutine = new GameObject("ACoroutineManager").AddComponent<InnerCoroutine>();
            GameObject.DontDestroyOnLoad(coroutine);
		}
        
	}
	
    public static Coroutine StartCoroutineTask(IEnumerator routine)
    {
		return coroutine.StartCoroutine(routine);
    }   

	private static IEnumerator StartInnerCoroutine(IEnumerator routine, Action<object> callback)
	{
		yield return StartCoroutineTask(routine);
		callback(routine.Current);
	}


	public static void StartCoroutineTask(IEnumerator routine, Action<object> callback)
	{
		StartCoroutineTask(StartInnerCoroutine(routine, callback));
	}



	public static void StopCoroutineTask(IEnumerator routine)
    {
		coroutine.StopCoroutine(routine);
    }

	public static void StopAllCoroutineTask()
    {
		coroutine.StopAllCoroutines();
    }


}

       协程管理器,会生成一个独立GameObject挂在一个MonoBehaviour,所有的协程执行都是由这个脚本发起。回调函数,就在于routine的Current属性。我们知道,协程其实是一个迭代器在执行。迭代器的当前值就是Current,这个值就是协程函数里yield return object 返回的这个object。


2017-10-11 20:42:01 yuewei19 阅读数 158
  • Unity3D协程-基础篇

    本课程为Unity3D协程技术精讲中的基础部分,协程是UnityEngine技术里面为常用且设计为精髓的技术;基础部分主要讲解协成的前世今生,其中包括设计模式、实现模式、验证模式的递进式推进过程让同学们掌握协程原理与程序设计。

    1974 人正在学习 去看看 曾家海

协程介绍

Unity的协程系统是基于C#的一个简单而强大的接口 ,IEnumerator,它允许你为自己的集合类型编写枚举器。

yield return是“停止执行方法,并且在下一帧从这里重新开始”。

 

简单计时器实例:StartCoroutine()并没有给它传入参数,但是这个方法调用了它自己(这是通过传递CoroutineMethod的return返回值来实现的)

 

	// Use this for initialization
	void Start ()
	{
		StartCoroutine(CoroutineMethod()); 
	}
	
	IEnumerator CoroutineMethod ()
	{
		for (float timer = 3; timer >= 0; timer -= Time.deltaTime) {
			yield return 0;
		}
		Debug.Log ("This message appears after 3 seconds!");  
	}

 

hello实例:

 

	//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 (int i = 0; i < 5; i++) {
			Debug.Log ("Hello");
			yield return 0;
		}
	}

 

 

 

开始和终止协程

 

		//If you start a Coroutine by name...  
		StartCoroutine("FirstTimer");  
		StartCoroutine("SecondTimer");  

		//You can stop it anytime by name!  
		StopCoroutine("FirstTimer");  

		//You can stop all anytime!
		StopAllCoroutines ();

 

2015-06-16 18:39:56 damenhanter 阅读数 717
  • Unity3D协程-基础篇

    本课程为Unity3D协程技术精讲中的基础部分,协程是UnityEngine技术里面为常用且设计为精髓的技术;基础部分主要讲解协成的前世今生,其中包括设计模式、实现模式、验证模式的递进式推进过程让同学们掌握协程原理与程序设计。

    1974 人正在学习 去看看 曾家海

协程:当开启一个协程后,Unity会进入协程函数执行,当遇到各种类型的yield语句时,Unity的执行权会回到开启协程的代码位置,继续这一帧的执行,当满足yield后面的语句条件时,Unity会从yield语句后继续执行。这只是个大致的概念,如果你没有看懂,后面会细说。


与C#协程不同的是,Unity的协程函数不需要规定函数返回值为IEnumerator,协程函数中可以不是yield return语句。
其yield有如下类型,根据类型不同,作用功能不同:
yield:是指开启协程函数后,系统会马上执行协程函数的内容,会执行到yield处停止,系统会保留yield代码处的执行环境,返回开启协程函数的地方,继续执行,在程序进入下一帧,Unity的Update函数执行后,系统会进入协程函数,找到上一帧保留的yield代码处,继续执行。
yield WaitForSeconds(2.f):和上面yield大致一样,只是Unity的Update进入执行yield语句后面的代码,需要满足2秒钟以后。
yield WaitForFixedUpdate:和yield大致一样,只是进入协程函数继续执行的时间为FixedUpdate后,Update之前。
yield WWW:和yield大致一样,进入协程函数继续执行的时间为Update()后,但是必须保证WWW类下载完成。
yield StartCoroutine :链式yield,继续执行的时间为子yield完成后。


大家一定要注意的是,yield以后,这一帧会返回协程函数开始的地方执行,yield后面的代码的执行,是在下一帧或者下几帧执行,而执行是由Unity系统负责,在Update()或者FixedUpdate()完成后,执行。
最后,看了不少网上关于协程的资料,可惜都没看到十分明白、详尽的,所以今天花了40分钟整理了一下,希望对大家有益处。
关于协程的更详细资料附上官网链接:
http://docs.unity3d.com/Manual/Coroutines.html
http://docs.unity3d.com/Manual/ExecutionOrder.html

unity协程简介

阅读数 481

没有更多推荐了,返回首页