精华内容
下载资源
问答
  • NULL 博文链接:https://dsqiu.iteye.com/blog/2029701
  • unity 协程特点

    2021-07-12 16:41:07
    开始协程的两种方式 区别 方式一调用起来的协程不能用代码停止 方式二调用起来的协程可以停止 ↓ 停止协程 特点: 假如你在脚本A中开启了一个协程,如果你马上把脚本A的onEnable设置为false,那么协程就会立即结束...

    一. Unity中使用协程

    1. 什么是协程

    游戏里面经常会出现类似的淡入淡出效果:“一个游戏物体的颜色渐渐变淡,直至消失。” 当你碰上了类似的需求,也许需要实现一个Fade()来实现这样的淡入淡出效果。

    一个错误的实现是像这个样子的

    //错误实现
    void Fade() 
    {
        float alpha = 1.0f;
        while(alpha > 0)
        {
            alpha -= Time.deltaTime;
            Color c = renderer.material.color;
            c.a = alpha;
            renderer.material.color = c;
        }
    }
    

    这段代码的思路很简单:在函数中写一个循环,让渲染对象的alpha从1开始不断递减直至降为0。 但是,函数被调用后将运行到完成状态然后返回,函数内的一切逻辑都会在同一帧内完成。也就是说,上述Fade函数中的alpha从1递减到0的过程会在一帧内完成,如果你调用这个Fade函数,看到的将会是游戏物体瞬间消失,而不会渐渐淡出。

    要解决这个错误也很简单,只需将函数改写成协程即可。

    //正确实现
    IEnumerator Fade()
    {
        float alpha = 1.0f;
        while(alpha > 0)
        {
            alpha -= Time.deltaTime;
            Color c = renderer.material.color;
            c.a = alpha;
            renderer.material.color = c;
            yield return null;//协程会在这里被暂停,直到下一帧被唤醒。
        }
    }
    

    //Fade不能直接调用,需要使用StartCoroutine(方法名(参数列表))的形式进行调用。
    StartCoroutine(Fade());
    

    像这种以IEnumerator为返回值的函数,在C#里面被称之为迭代器函数,在Unity里面也可以被称为协程函数。 当协程函数运行到yield return null语句时,协程会被暂停,unity继续执行其它逻辑,并在下一帧唤醒协程。

    现在来说说什么是协程,Unity官方对协程的定义是这样的:

    A coroutine is like a function that has the ability to pause execution
    and return control to Unity but then to continue where it left off on
    the following frame. By default, a coroutine is resumed on the frame
    after it yields but it is also possible to introduce a time delay
    using
    WaitForSeconds

    简单的说,协程就是一种特殊的函数,它可以主动的请求暂停自身并提交一个唤醒条件,Unity会在唤醒条件满足的时候去重新唤醒协程。

    2. 如何使用

    MonoBehaviour.StartCoroutine()方法可以开启一个协程,这个协程会挂在该MonoBehaviour下。

    在MonoBehaviour生命周期的Update和LateUpdate之间,会检查这个MonoBehaviour下挂载的所有协程,并唤醒其中满足唤醒条件的协程。

    要想使用协程,只需要以IEnumerator为返回值,并且在函数体里面用yield return语句来暂停协程并提交一个唤醒条件。然后使用StartCoroutine来开启协程。

    下面这个实例展示了协程的用法。

    IEnumerator CoroutineA(int arg1, string arg2)
    {
        Debug.Log($"协程A被开启了");
        yield return null;
        Debug.Log("刚刚协程被暂停了一帧");
        yield return new WaitForSeconds(1.0f);
        Debug.Log("刚刚协程被暂停了一秒");
        yield return StartCoroutine(CoroutineB(arg1, arg2));
        Debug.Log("CoroutineB运行结束后协程A才被唤醒");
        yield return new WaitForEndOfFrame();
        Debug.Log("在这一帧的最后,协程被唤醒");
        Debug.Log("协程A运行结束");
    }IEnumerator CoroutineB(int arg1, string arg2)
    {
        Debug.Log($"协程B被开启了,可以传参数,arg1={arg1}, arg2={arg2}");
        yield return new WaitForSeconds(3.0f);
        Debug.Log("协程B运行结束");
    }
    

    3. 协程的应用场景

    创建补间动画

    补间动画指的是在给定若干个关键帧中插值来实现的动画。 如:给定两个时间点的Alpha值,可以插值出一个淡入淡出的动画效果。

    创建补间动画更常用的做法是使用Dotween插件。

    打字机效果

    很多游戏的人物对话界面中,文字并不是一开始就显示在对话框中的,而是一个一个显示出来的。这种将文本一个一个字的显示出来的效果称之为打字机(Typewriter)。

    使用协程,你可以每显示一个字符后等待若干时间,从而实现打字机效果。 b站上有一个基于协程的打字机效果的简单实现(https://www.bilibili.com/video/BV1cJ411Y7F6)

    异步加载资源

    资源加载指的是通过IO操作,将磁盘或服务器上的数据加载成内存中的对象。资源加载一般是一个比较耗时的操作,如果直接放在主线程中会导致游戏卡顿,通常会放到异步线程中去执行。

    举个例子,当你需要从服务器上加载一个图片并显示给用户,你需要做两件事情:

    通过IO操作从服务器上加载图片数据到内存中。
    当加载完成后,将图片显示在屏幕上。
    其中,2操作必须等待1操作执行完毕后才能开始执行。

    在传统的互联网应用中,一般会使用回调函数来实现类似功能:

    //伪代码//提供给用户的接口
    void ShowImageFromUrl(string url)
    {
        LoadImageAsync(url, Callback); //开启一个异步线程来加载图像,加载完成后会自动调用回调函数
    }//回调函数
    void Callback(Image image)
    {
        Show(image);
    }   
    

    我们也可以改写成协程的形式:

    //伪代码IEnumerator ShowImageFromUrl(string url)
    {
        Image image = null;
        yield return LoadImageAsync(url, image); //异步加载图像,加载完成后唤醒协程
        Show(image);
    }
    

    使用协程来进行异步加载在Unity中是一个很常用的写法。资源加载是一个很重的话题,有兴趣的同学可以研究研究。 这里贴两个参考链接: Unity官方的异步加载场景的示例 倩女幽魂手游中的资源加载与更新方案

    定时器操作
    当你需要延时执行一个方法或者是每隔一段时间就执行某项操作时,可以使用协程。不过对于这种情况,也可以考虑写一个TickManager来管理定时操作。Electricity项目中的定时器

    思考:协程能做的Update都能做,那为什么我们需要协程呢? 答:使用协程,我们可以把一个跨越多帧的操作封装到一个方法内部,代码会更清晰。

    4. 注意事项

    协程是挂在MonoBehaviour上的,必须要通过一个MonoBehaviour才能开启协程。
    MonoBehaviour被Disable的时候协程会继续执行,只有MonoBehaviour被销毁的时候协程才会被销毁。
    协程看起来有点像是轻量级线程,但是本质上协程还是运行在主线程上的,协程更类似于Update()方法,Unity会每一帧去检测协程需不需要被唤醒。一旦你在协程中执行了一个耗时操作,很可能会堵塞主线程。这里提供两个解决思路:(1) 在耗时算法的循环体中加入yield return null来将算法分到很多帧里面执行;(2) 如果耗时操作里面没有使用Unity API,那么可以考虑在异步线程中执行耗时操作,完成后唤醒主线程中的协程。

    二. Unity协程的底层原理

    协程分为两部分,协程与协程调度器:协程仅仅是一个能够中间暂停返回的函数,而协程调度是在MonoBehaviour的生命周期中实现的。 准确的说,Unity只实现了协程调度部分,而协程本身其实就是用了C#原生的”迭代器方法“。

    1. 协程本体:C#的迭代器函数

    许多语言都有迭代器的概念,使用迭代器我们可以很轻松的遍历一个容器。 但是C#里面的迭代器要屌一点,它可以“遍历函数”。
    C#中的迭代器方法其实就是一个协程,你可以使用yield来暂停,使用MoveNext()来继续执行。 当一个方法的返回值写成了IEnumerator类型,他就会自动被解析成迭代器方法(后文直接称之为协程),你调用此方法的时候不会真的运行,而是会返回一个迭代器,需要用MoveNext()来真正的运行。看例子:

    static void Main(string[] args)
    {
        IEnumerator it = Test();//仅仅返回一个指向Test的迭代器,不会真的执行。
        Console.ReadKey();
        it.MoveNext();//执行Test直到遇到第一个yield
        System.Console.WriteLine(it.Current);//输出1
        Console.ReadKey();
        it.MoveNext();//执行Test直到遇到第二个yield
        System.Console.WriteLine(it.Current);//输出2
        Console.ReadKey();
        it.MoveNext();//执行Test直到遇到第三个yield
        System.Console.WriteLine(it.Current);//输出test3
        Console.ReadKey();
    }static IEnumerator Test()
    {
        System.Console.WriteLine("第一次执行");
        yield return 1;
        System.Console.WriteLine("第二次执行");
        yield return 2;
        System.Console.WriteLine("第三次执行");
        yield return "test3";
    }
    

    执行Test()不会运行函数体,会直接返回一个IEnumerator
    调用IEnumerator的MoveNext()成员,会执行协程直到遇到第一个yield return或者执行完毕。
    调用IEnumerator的Current成员,可以获得yield return后面接的返回值,该返回值可以是任何类型的对象。
    这里有两个要注意的地方:

    IEnumerator中的yield return可以返回任意类型的对象,事实上它还有泛型版本IEnumerator,泛型类型的迭代器中只能返回T类型的对象。Unity原生协程使用普通版本的IEnumerator,但是有些项目(比如倩女幽魂)自己造的协程轮子可能会使用泛型版本的IEnumerator
    函数调用的本质是压栈,协程的唤醒也一样,调用IEnumerator.MoveNext()时会把协程方法体压入当前的函数调用栈中执行,运行到yield return后再弹栈。这点和有些语言中的协程不大一样,有些语言的协程会维护一个自己的函数调用栈,在唤醒的时候会把整个函数调用栈给替换,这类协程被称为有栈协程,而像C#中这样直接在当前函数调用栈中压入栈帧的协程我们称之为无栈协程。关于有栈协程和无栈协程的概念我们会在后文四. 跳出Unity看协程中继续讨论
    Unity中的协程是无栈协程,它不会维护整个函数调用栈,仅仅是保存一个栈帧。

    2. 协程调度:MonoBehaviour生命周期中实现

    仔细翻阅Unity官方文档中介绍MonoBehaviour生命周期的部分,会发现有很多yield阶段,在这些阶段中,Unity会检查MonoBehaviour中是否挂载了可以被唤醒的协程,如果有则唤醒它。

    通过对C#迭代器的了解,我们可以模仿Unity自己实现一个简单的协程调度。这里以YieldWaitForSeconds为例

    // 伪代码
    void YieldWaitForSeconds()
    {
        //定义一个移除列表,当一个协程执行完毕或者唤醒条件的类型改变时,应该从当前协程列表中移除。
        List<WaitForSeconds> removeList = new List<WaitForSeconds>();
        foreach(IEnumerator w in m_WaitForSeconds) //遍历所有唤醒条件为WaitForSeconds的协程
        {
            if(Time.time >= w.beginTime() + w.interval) //检查是否满足了唤醒条件
            {
                //尝试唤醒协程,如果唤醒失败,则证明协程已经执行完毕
                if(it.MoveNext();)
                {
                    //应用新的唤醒条件
                    if(!(it.Current is WaitForSeconds))
                    {
                        removeList.Add(it);
                           //在这里写一些代码,将it移到其它的协程队列里面去
                    }
                }
                else 
                {
                    removeList.Add(it);
                }
            }
        }
        m_WaitForSeconds.RemoveAll(removeList);
    }
    

    3. Unity协程的架构

    YieldInstructionWaitForEndOfFrameWaitForFixedUpdateCoroutineWaitForSecondsAsyncOperation
    基类:YieldInstruction 其它所有协程相关的类都继承自这个类。Unity的协程只允许返回继承自YieldInstruction的对象或者null。如果返回了其他对象则会被当成null处理。

    协程类:Coroutine 你可以通过yield return一个协程来等待一个协程执行完毕,所以Coroutine也会继承自YieldInstruction。 Coroutine仅仅代表一个协程实例,不含任何成员方法,你可以将Coroutine对象传到MonoBehaviour.StopCoroutine方法中去关闭这个协程。

    遗憾的是,Unity关于协程的这套都是在C++层实现的并且几乎没有暴露出C#接口,所以扩展起来会比较麻烦。

    三. 扩展Unity的协程

    了解了协程的底层原理后,就可以尝试去扩展协程啦!!!

    为什么要扩展协程? 那必然是Unity自带的协程系统无法满足我们的需求了,比如有的时候我们可能需要一个WaitForSceneLoaded或者WaitForAnimationDown之类东西,Unity就没有。

    扩展协程有两个思路,一种是自己另外写一套,可以有高度的定制性;另一种是对Unity现有的协程系统进行封装,可以兼容Unity现有的WaitForXXX。

    1. 思路一:另外写一套协程

    这种其实没什么说的,其实就是自己手写一个协程调度器,有点是可以高度定制调度器行为,缺点是不能兼容Unity自带的协程。

    倩女幽魂的协程轮子是将调度器写到了MonoBehaviour的Update里面,但是你也可以自己在另一个线程开一个死循环来完成协程调度。可以在GitHub上找找别人的代码看看实现,网上那些讲Unity协程的帖子也有很多都实现了简易协程调度。

    Github上的某个C#框架下的协程模块,不依赖于Unity

    2. 思路二:在Unity协程的基础上进行封装

    由于Unity并未暴露出协程的扩展接口,所以不能直接在Unity的协程上进行扩展。 一个折衷的方案是,使用一个代理协程,这个代理协程被Unity管理,然后所有的用户级协程被这个代理协程管理。本质上是对Unity现有协程模块进行封装。

    Electiricy项目中的协程模块:

    在Electricity项目中,有个单例CCoroutineMgr用来管理所有的协程。 使用CCoroutineMgr.Inst.StartCoroutine()即可开启协程。

    //In CCoroutineMgr.cs//所有扩展的WaitForXXX的接口
    public interface IWaitable
    {
        bool IsReady();
    }public class CCoroutineMgr : CSingletonBehaviour<CCoroutineMgr>
    {
        //开启一个协程,讲用户传进来的iter传入协程调度器中,协程调度器本身也是一个协程,被Unity管理
        public new WaitForCoroutine StartCoroutine(IEnumerator iter)
        {
            WaitForCoroutine wait = new WaitForCoroutine();
            //开启协程调度器,使用base.StartCoroutine可以开启Unity自带协程
            Coroutine co = base.StartCoroutine(CoScheduler(iter, wait));
            mCoroutines.Add(wait, co);
            return wait;
        }
        
        //核心:协程调度
        private IEnumerator CoScheduler(IEnumerator iter, WaitForCoroutine wait)
        {
            while (iter.MoveNext())
            {
                var res = iter.Current;
                if (res is IWaitable) //iter中返回了一个IWaitable
                {
                    IWaitable waitable = res as IWaitable;
                    while (!waitable.IsReady())
                        yield return null;
                }
                else //iter中返回了一个Unity自带的协程条件
                {
                    yield return res;
                }
            }
            mCoroutines.Remove(wait);
        }
    }
    

    完整代码 CCoroutineMgr.cs

    IWaitable是所有用户自定义WaitForXXX的公共接口;WaitForCoroutine实现了这个接口,用来表示某个协程有没有结束。

    当开启协程时,用户会传进来一个迭代器方法iter,iter会被传进协程调度器中被调度器管理,而调度器会作为一个Unity原生协程被Unity管理。

    在协程调度器CoScheduler中,当调度器被唤醒,会执行一次iter并检查yield return返回的结果res,如果res是一个IWaitable,就每帧检测res是否满足恢复条件;如果res不是IWaitable,就会将res作为Unity自带的协程唤醒条件返回。

    使用单例的协程管理器可能会导致一些问题,考虑这样一个情况: 对象A请求CCoroutineMgr开启了一个协程C,后来对象A被销毁了而协程C还在运行,协程C会访问已经被销毁了的对象A,从而引发错误。

    在使用单例的协程管理器时,应当注意在对象被销毁的时候将所有引用了这个对象的协程一并销毁。如果每个MonoBehaviour都写一套这样的逻辑可能会比较麻烦,Electricity项目中对MonoBehavioiur做了一些封装,可以自动管理协程的销毁。

    MemoBehaviour

    MemoBehaviour可以自动管理协程的销毁,实现比较简单,就直接上代码了。

    public class MemoBehaviour : MonoBehaviour
    {
        private List<WaitForCoroutine> mCoroutines = new List<WaitForCoroutine>();
        private int mNextCoroutineClearCount = 10;
        
        public new WaitForCoroutine StartCoroutine(IEnumerator iter)
        {
            WaitForCoroutine wait = CCoroutineMgr.Inst.StartCoroutine(iter);
            mCoroutines.Add(wait);
            if(mCoroutines.Count >= mNextCoroutineClearCount)
            {
                ClearCoroutines();
                mNextCoroutineClearCount  = mNextCoroutineClearCount * 2 + 1;
            }
            return wait;
        }//清理已经执行完毕的协程。
        private void ClearCoroutines()
        {
            mCoroutines.RemoveAll((WaitForCoroutine wait) => wait.IsReady());
        }
    }
    

    完整代码 MemoBehaviour.cs

    四. 跳出Unity看协程

    前面都是在Unity的基础上讲协程,实际上有很多开发框架中都有协程的概念,而且都有一些区别。这一节就跳出Unity,简单的介绍一下广义上的协程。

    1. 进程,线程与协程

    进程是操作系统资源分配的基本单位 线程是处理器调度与执行的基本单位
    这是操作系统书上对进程与线程的抽象描述。具体一点的说,进程其实就是程序运行的实例:程序本身只是存储在外存上的冷冰冰的二进制流,计算机将这些二进制流读进内存并解析成指令和数据然后执行,程序便成为了进程。

    每一个进程都独立拥有自己的指令和数据,所以称为资源分配的基本单位。其中数据又分布在内存的不同区域,我们在C语言课程中学习过内存四区的概念,一个运行中的进程所占有的内存大体可以分为四个区域:栈区、堆区、数据区、代码区。其中代码区存储指令,另外三个区存储数据。

    线程是处理器调度和执行的基本单位,一个线程往往和一个函数调用栈绑定,一个进程有多个线程,每个线程拥有自己的函数调用栈,同时共用进程的堆区,数据区,代码区。操作系统会不停地在不同线程之间切换来营造出一个并行的效果,这个策略称为时间片轮转法。

    那么协程在其中又处于什么地位呢? 一切用户自己实现的,类似于线程的轮子,都可以称之为是协程。

    C#中的迭代器方法是协程; Unity在迭代器的基础上扩展出来的协程模块是协程; 你在操作系统实验中模仿线程自己写出来的"线程"也是协程; …

    协程有什么样的行为,完全由实现协程的程序员来决定(线程和进程都是操作系统中写死的),这就导致了不同开发框架下的协程差别很大。有的协程有自己的函数调用栈,有的协程共用线程的函数调用栈;有的协程是单线程上的,有的协程可以多线程调度;有的协程和线程是一对多的关系,有的协程和线程是多对多的关系。

    操作系统可以有多个进程 一个进程对应一个或多个线程 线程和协程的对应关系,由具体的开发框架决定

    2. 不同框架下协程的共同点

    虽然不同开发框架下的协程各不一样,但是这些协程基本上还是有一些共性的

    (1) 协程有yield和resume操作

    协程可以通过yield操作挂起,通过resume操作恢复。yield一般是协程主动调用,resume一般是调度器调用。 大多数协程库都支持这两个操作,无非是可能API的名字不一样。 比如C#中,resume操作就是MoveNext

    (2) 协程调度是非抢占式的

    线程调度是抢占式的:操作系统会主动中断当前执行中的线程,然后把CPU控制权交给别的线程,就好像有很多线程去争抢CPU的控制权一样。

    协程调度是非抢占式的:协程需要主动调用yield来释放CPU控制权,协程的运行中间不会被系统中断打断。

    3. 如何在C语言中造一个协程

    两个概念:
    PC指针:指向CPU下一条运行的指令
    程序运行上下文:这是一个比较宽泛的概念,表示程序运行所需要的数据,比如当前栈帧(无栈协程)或者整个函数调用栈(有栈协程)

    协程的本质就是一个可以暂停执行的函数,使用yiled挂起,使用resume恢复。 只要在协程挂起的时候,保存当前的PC指针和程序运行上下文,然后在协程恢复的时候将保存的数据应用上,即可自己实现一个协程。 有兴趣的话可以去GitHub上找些别人自己写的协程看看源码,直接搜coroutine就好。

    -----------------------------------------------------------------------------------------------------***
    杂记:
    开始协程的两种方式
    在这里插入图片描述
    区别
    方式一调用起来的协程不能用代码停止
    方式二调用起来的协程可以停止


    停止协程
    在这里插入图片描述
    特点:
    假如你在脚本A中开启了一个协程,如果你马上把脚本A的onEnable设置为false,那么协程就会立即结束

    展开全文
  • NULL 博文链接:https://dsqiu.iteye.com/blog/2049743
  • 深入理解Unity协程

    2020-04-20 23:27:25
    在很多编程语言中都支持协程,例如在我们之前提到的lua中,协程是一个虚拟的线程技术。 简述 想一想我们平时购买电脑提及的,cpu是四核八线程,其实cpu原先只能处理处理一件事,也就是说cpu默认是一个核心对应一...

    在很多编程语言中都支持协程,例如在我们之前提到的lua中,协程是一个虚拟的线程技术。


    简述


    想一想我们平时购买电脑提及的,cpu是四核八线程,其实cpu原先只能处理处理一件事,也就是说cpu默认是一个核心对应一条线程的,但是如果我们需要同时处理多个任务,而我们并没有那么多的线程数量。
    然后前人们就提出了虚拟线程的概念,将cpu的单个线程,虚拟出多条线程,也就有了我们四核八线程,八核十六线程等的概念;在应用程序这边也有了线程和进程的概念,在把进程再细分,虚拟化软件的线程就得到了协程的概念。

    至此你知道,协程就是对线程的再细分,是线程的再虚拟化即可。

     unity的协程


    虽然说协程的概念并不新颖,但在现在大趋势的互联网开发领域可能很少涉及到`协程`这个词汇。
    协程就是协力去完成一件事,这很容易想到多线程的概念,例如我们进行一次网络请求我们需要等待response之后才能下一步操作,此时我们就会用到`互斥锁`、`线程安全`等概念。
    在unity中或者说在游戏引擎中,由于受到游戏主循环线程的制约,所以不能确保多线程的安全性,此时在同一线程下继续使用协程来承担多线程的工作就显得尤为重要。
    (unity也退出了以性能优先的ECS模式,摒弃Mono框架,实现了可多线程协助开发的开发模式)
    下面我们就主要以unity的协程详细介绍。

     unity协程示例


    看这个例子:

    //创建协程
    private IEnumerator Cor1()
    {
        yield return new WaitForSeconds(2f);
        Debug.Log("2s到了");
    }
    //启动协程
    StartCoroutine(Cor1());


    这是一个简单示例,可以看到协程需要返回一个`IEnumerator`可迭代对象,这本来是Csharp中的迭代器模式的实现,在unity中unity以此为原型实现了协程。

     协程的参数


    在上面我们使用了 `new WaitForSeconds()`,这表示等待指定的时间。【注意WaitForSeconds与Time.Scale相关】
    在上面使用的WaitForSeconds之外还有许多的参数,这些参数要么需要花费时间,要么返回bool,总之就是需要确定一个moveNext。


    协程的使用情况

     

    1. 用于不确定的时长情况(例如:网络请求,读写文件)
    2. 用于延迟执行
    3. 可当做简易计时器使用(例如:生产一批敌人)

     协程的嵌套


    协程支持嵌套,如下是一个利用协程实现的巡逻的简单实现。
    **注:在unity中,协程返回0或null表示等待下一帧。**

    using System;
    using System.Collections;
    using UnityEngine;
    
    namespace Coroutines
    {
        //协程测试
        public class CoroutTest : MonoBehaviour
        {
            public Transform[] wayPoints;
    
            private bool isLoop;
    
            private void Start()
            {
                isLoop = true;
                StartCoroutine(StartLoop());
            }
    
            private IEnumerator StartLoop()
            {
                do
                {
                    foreach (var point in wayPoints)
                    {
                        yield return StartCoroutine(MoveTarget(point.position));
                    }
                } while (isLoop);
            }
    
            private IEnumerator MoveTarget(Vector3 target)
            {
                while (transform.position!=target)
                {
                    transform.position = Vector3.MoveTowards(transform.position, target, 3 * Time.deltaTime);
                    yield return 0;
                }
            }
        }
    }


     让协程动起来

     

    + StartCoroutine(nameof(StartLoop));
    以字符串形式启动协程,能够在外部停止协程,无法传递参数。

    + StartCoroutine(StartLoop);
    以迭代器形式启动协程,能够传递参数,无法在外部使用stop停止协程。

    ### 让协程停下来
    协程本质是一个迭代器,当moveNext为false时即认为协程中所有的项目都已经执行完毕。
    在unity中有以下几种方式停止协程:

    1. StopCoroutine()  注意此方式只能停止以字符串形式启动的协程 【在协程外部使用】
    2. yield break   跳出协程【在协程内部使用】
    3. 通过逻辑来停止 【使其协程执行条件不满足】
    4. 设置物体不激活 【再次激活协程也不会恢复】
    5. StopAllCoroutine() 终止所有协程

    如上面协程嵌套的例子中,如果我们想要协程停止:
    1. 设置isLoop=false;让其在执行一次后不满足条件自动停下
    2. 在协程内部break
     

    if (transform.position==wayPoints[2].position)
    {
        yield break;
    }


    3. 在协程外部 stop
     

     StopCoroutine(nameof(StartLoop));

     协程的设计思想

     协程是否取代update?


    通过上面的例子,你大可发现,协程其实是对update的另一种实现,我们甚至可以只使用协程而不使用任何update和fixedUpdate完成程序的编写。
    但我们如果这样做不是本末倒置了吗?协程是unity推出的延迟执行的一种范式,其还是基于update为原理的上层实现。

    使用协程会大大提升程序效率吗?


    不会,协程本质上还是在一条线程上,尽管可以多条协程并行,但这些协程始终还是运行在一条线程上,速度和效率并不会得到很大的提升。反而开辟多条线程并行,线程需要多多协程的状态保持监听,在协程大量结束时会触发大量GC 回收,可能会降低程序的运行效率。

     总结


    协程是运行在线程上的线程,其运作方式任然基于单线程,并不会因为使用协程提高程序的运行效率,但协程方便的书写方式,强大的功能能够提高我们作为开发者的开发效率。
    从某种意义上来讲,协程更像是一个精美的语法糖

    展开全文
  • NULL 博文链接:https://dsqiu.iteye.com/blog/2022992
  • Unity怎么暂停协程✨Unity协程管理方案

    多人点赞 热门讨论 2021-07-02 20:05:15
    协程是游戏开发中非常常用的方法,类似一个子线程单独出来处理一些问题,性能开销较小。当然我们更多时候用的是他的延时执行功能???????????? 使用多了,难免有这样那样的需求。比如:暂停。比如:管理大量协程等 ...

    协程是游戏开发中非常常用的方法,类似一个子线程单独出来处理一些问题,性能开销较小。当然我们更多时候用的是他的延时执行功能😌😌😌

    使用多了,难免有这样那样的需求。比如:暂停。比如:管理大量协程等 协程要在Mono类执行,销毁物体会停止协程,咋办

    🤔🤔🤔

    下面提供协程的一个管理器方案。 核心类有3个:
    CoroutineItem 每个协程生成一个对象类
    CoroutineCtrl 每个对象由一个Ctrl控制
    CoroutineMgr 管理所有的Ctrl
    MonoSingleton 总所周知四大天王有五个,这个是送的🤔
    使用的时候只需要使用CoroutineMgr
    CoroutineItem
    using System.Collections;
    using UnityEngine;
    
    public class CoroutineItem : MonoBehaviour
    {
        public enum CoroutineState{
            WAITTING,
            RUNNING,
            PASUED,
            STOP
        }
        public CoroutineState State{get;set;}
        public IEnumerator Body(IEnumerator routine){
            while(State == CoroutineState.WAITTING){
                yield return null;
            }
            while(State == CoroutineState.RUNNING){
                if(State == CoroutineState.PASUED){
                    yield return null;
                }else{
                    if(routine!=null&&routine.MoveNext()){
                        yield return routine.Current;
                    }else{
                        State = CoroutineState.STOP;
                    }
                }
            }
        }
    }
    
    
    CoroutineCtrl
    using System.Collections;
    using UnityEngine;
    
    public class CoroutineCtrl
    {
        private static int _id;
        public int ID{get;private set;}
        private CoroutineItem _item;
        private MonoBehaviour _mono;
        private IEnumerator _routine;
        private Coroutine _coroutine;
        public CoroutineCtrl(MonoBehaviour mono, IEnumerator routine)
        {
            _item = new GameObject().AddComponent<CoroutineItem>();
            _mono = mono;
            _routine = routine;
            ResetData();
        }
        public void Start()
        {
            _item.State = CoroutineItem.CoroutineState.RUNNING;
            _coroutine = _mono.StartCoroutine(_item.Body(_routine));
        }
    
        public void Pause()
        {
            _item.State = CoroutineItem.CoroutineState.PASUED;
        }
    
        public void Stop()
        {
            _item.State = CoroutineItem.CoroutineState.STOP;
        }
    
        public void Continue()
        {
            _item.State = CoroutineItem.CoroutineState.RUNNING;
        }
    
        public void ResetStart()
        {
            if(_coroutine != null){
                _mono.StopCoroutine(_coroutine);
            }
            Start();
        }
    
        private void ResetData(){
            ID = _id++;
        }
    }
    
    
    CoroutineMgr
    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    
    public class CoroutineMgr : MonoSingleton<CoroutineMgr>
    {
        private Dictionary<int,CoroutineCtrl> _ctrlDic;
    
        public CoroutineMgr(){
            _ctrlDic = new Dictionary<int, CoroutineCtrl>();
        }
        public int Execute(IEnumerator routine,bool autoStart = true){
            CoroutineCtrl ctrl = new CoroutineCtrl(this,routine);
            _ctrlDic.Add(ctrl.ID,ctrl);
            if(autoStart){
                StartExecute(ctrl.ID);
            }
            
            return ctrl.ID;
        }
        public void ExecuteOnce(IEnumerator routine){
            CoroutineCtrl ctrl = new CoroutineCtrl(this,routine);
            ctrl.Start();
        }
    
        public void StartExecute(int id){
            var ctrl = GetCtrl(id);
            if(ctrl!=null){
                ctrl.Start();
            }
        }
        public void PauseExecute(int id){
            var ctrl = GetCtrl(id);
            if(ctrl!=null){
                ctrl.Pause();
            }
        }
        public void StopExecute(int id){
            var ctrl = GetCtrl(id);
            if(ctrl!=null){
                ctrl.Stop();
            }
        }
       public void RestartExecute(int id){
            var ctrl = GetCtrl(id);
            if(ctrl!=null){
                ctrl.ResetStart();
            }
        }
       private CoroutineCtrl GetCtrl(int id){
            if(_ctrlDic.ContainsKey(id)){
                return _ctrlDic[id];
            }else{
                Debug.LogError($"当前id不存在,id:{id}");
                return null;
            }
        }
    }
    
    
    MonoSingleton
    //简易的Mono单例类,如无必要,代码中尽量少用
    using UnityEngine;
    
    public class MonoSingleton<T> : MonoBehaviour where T:MonoBehaviour
    {
        private static T _instance;
        public static T Instance {
            get {
                if(_instance == null)
                {
                    GameObject go = new GameObject(typeof(T).Name);
                    DontDestroyOnLoad(go);
                    _instance = go.AddComponent<T>();
                }
                return _instance;
            }
        }
    }
    
    测试
    using System.Collections;
    using UnityEngine;
    using UnityEngine.UI;
    
    public class CoroutineTest : MonoBehaviour
    {
        public Button mBtnStart;
        public Button mBtnPause;
        public Button mBtnStop;
    
        private int id = 0;
    
        private void Awake()
        {
            mBtnStart.onClick.AddListener(ClickStart);
            mBtnPause.onClick.AddListener(ClickPause);
            mBtnStop.onClick.AddListener(ClickStop);
        }
    
        private void Start()
        {
            // CoroutineMgr.Instance.ExecuteOnce(DoSomething()); 只需要执行一次,不需要控制的用这个
            id = CoroutineMgr.Instance.Execute(DoSomething());//需要多次执行和管理的用这个,保存下id,用id调用控制方法
        }
    
        void ClickStart()
        {
             CoroutineMgr.Instance.RestartExecute(id);
        }
    
        void ClickPause()
        {
            CoroutineMgr.Instance.PauseExecute(id);
        }
    
        void ClickStop()
        {
            CoroutineMgr.Instance.StopExecute(id);
        }
        IEnumerator DoSomething()
        {
            print($"当前携程ID为{id}");
            print("协程开始");
            var timer = 0;
            while ( timer<30)
            {
                yield return new WaitForSeconds(1);
                timer++;
                print($"协程进行第{timer}秒");
            }
            print("协程完成");
        }
    }
    
    

    以上方案出自Siki学院Andy老师《全民飞机大战》框架设计系列

    展开全文
  • unity协程实现计时器的效果

    千次阅读 2021-11-12 11:29:20
    时间递减,递增可以自己写一写。 由于我是刚了解协程,所以还有点不熟练,希望对大家有帮助。

     

    时间递减,递增可以自己写一写。

    由于我是刚了解协程,所以还有点不熟练,希望对大家有帮助。

    展开全文
  • unity通过协程实现异步下载数据并加载
  • Unity协程是一个老生常谈的知识点了,但今天博主却差点被一篇文章忽悠了 那就是这句话: 为什么说这句话不对呢 那就是当遇到while时,我们再来检验这句话的正确性: 按上面那句话,应该每次yield return...
  • 曾被问道 Unity 协程是如何知道条件是否满足的。 直到看了一个讲师的视频后才明白一些。 IEnumerator 迭代器是一个容器,可以装一个一个的函数, 依次执行每个函数(即:MoveNext()),每个函数会有一个返回值, ...
  • } } 运行结果: 结果分析:在用WaitForSeconds时,用老的对象时,等待的时间是开协程的时间+传入的参数。 但是用WaitForSecondsRealtime时,用老的对象,等待的时间是线性的,是在之前时间节点结束后才开始新的时间...
  • Unity 协程用法总结

    千次阅读 2020-04-20 16:38:04
    Unity 协程用法总结 协程:协同程序,在主程序运行的同时,开启另外一段逻辑处理,来协同当前程序的执行,注意协程不是线程,只是具有线程特点的“伪协程”。 协程的使用需要直接或间接的继承MonoBehavior。 协程...
  • 首先Unity协程 大家都知道,这东西不是线程,只是模拟而已。。。(不太清楚的可以百度一下 unity协程 ,这里不做多余解释)协程并行 有一个理念要先理解一下,就是同时进行两个协程A、B,主线程是先走一帧A,再走一...
  • StopCoroutine的使用方法一共有三种: 1、StopCoroutine... 不能停止协程,应该这样: Coroutine c=StartCoroutine(Test(p1,p2)); StopCoroutine(c); 3、StopCoroutine(IEnumrator routine); 不常用,不做展开。
  • Unity协程

    2017-04-01 20:32:23
    所以为了解决这些问题,unity提出了协程这个东西。设计协程的目的就是为了同步并行的逻辑,避免多线程带来的麻烦,协程是假的多线程,可以将复杂的逻辑分到主线程不同帧去执行。协程的使用:using UnityEngine; ...
  • Unity协程与调用函数

    2020-10-29 18:13:31
    协程与调用函数 协程(Coroutine) 定义 //定义协程 IEnumerator name () // 启动协程 Public Coroutine StartCoroutine(methodName:String); Public Coroutine StartCoroutine(IEnumertator rountine); Public...
  • public class WaitForSecondsRealtime : CustomYieldInstruction { private float m_WaitUntilTime = -1f;...所以协程结束后可以用keepWaiting去判断该协程是否还在运行,但是一旦调用后,keepWaiting将变为true
  • using System.Collections; using System.Collections.Generic; using UnityEngine; public class SyncGetResources : MonoBehaviour { private GameObject go;... ResourceRequest request;... // Start is called ...
  • Unity 协程回调

    2020-06-01 10:58:16
    按照顺序Debug,1-5进行 //按照1-5顺序 //---------------------------------------------------- StartCoroutine(funcIEnumerator(ddd()...#region 协程回调事件 例子1-5 仅做参考 public IEnumerator ddd() { ..
  • Unity 协程计时器

    2020-10-02 15:35:43
    void Start() { StartCoroutine(Test()); } IEnumerator Test() { int a = 0; while (true) { yield return new WaitForSeconds(5); Debug.Log(“Coming!!!”); a++; if (a>=6) { yield break;...}
  • Unity3d协程实现

    2018-10-15 00:16:42
    Unity3d协程的自实现,版本2018.2.7f1。博客地址https://blog.csdn.net/here4one
  • 协程 循坏刷新扫光特效 using DG.Tweening; using Coffee.UIEffects; private UIShiny[] effList => transform.GetComponentsInChildren<UIShiny>();//获取数组 IEnumerator RefreshEff() { foreach ...
  • Unity中的协程是通过Update驱动的,通过yield return来打断,在声明周期中回调执行,也就是resume。在Lua里面其实只需要我们自己进行yield然后我们自己在update里面resume就和Unity协程一毛一样了。不过Lua的协程...
  • unity协程返回含义

    2020-10-20 20:32:22
    直接结束该协程的后续操作 yield return asyncOperation;等异步操作结束后再执行后续代码 yield return StartCoroution(/*某个协程*/);等待某个协程执行完毕后再执行后续代码 yield return WWW();等待WWW操作完成后...
  • Unity 协程

    2021-04-12 20:30:47
    协程前言调用方式停止方式yiled return语句执行时机WaitForSeconds(float Time)...协程unity提供的一个特殊的机制,他的特点就是可以方便的实现流程化的东西。但是就他的效率而言个人感觉并不乐观,
  • 在使用unity中有时会遇到一个问题:你希望在某一事件执行完毕改变某一个状态,这一状态会影响到你是否会执行这一事件,同时你又希望这一事件在update中执行。那么就会遇到一个问题:在这一帧执行完事件,然后改变了...
  • Unity协程的返回值

    千次阅读 2020-04-18 15:00:59
    unity协程返回值 yield return null; // 下一帧再执行后续代码 yield return 6;//(任意数字) 下一帧再执行后续代码 yield break; //直接结束该协程的后续操作 yield return asyncOperation;//等异步操作结束后再...
  • Unity 协程的原理

    千次阅读 2019-06-13 18:45:50
    协程不是多线程,协程还是在主线程里面(注:在Unity中非主线程是不可以访问Unity资源的) 1、线程、进程和协程的区别 进程有自己独立的堆和栈,即不共享堆也不共享栈,进程由操作系统调度 线程拥有自己独立的栈...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 7,252
精华内容 2,900
关键字:

unity协程