找回密码
 立即注册
查看: 355|回复: 2

关于Unity协程函数的个人理解

[复制链接]
发表于 2021-12-26 12:50 | 显示全部楼层 |阅读模式
Hello,这里是Karmotrine之众所周知,Unity的协程函数一直Unity匪夷所思的难点之一。至少它在一开始就困扰了我很久.但是在现在,我好像悟了(bushi,就把自己的理解打在公屏上,希望dalao轻喷。~~想不到吧我竟然更了哈哈哈哈哈哈好吧其实我也妹想到~~
Unity的协程和线程

        在谈协程函数之前,我们先了解一下协程和线程的区别。线程相当于Unity中一直在进行的事物,只有在程序或者脚本结束时才能停止。比如说Update函数,它在程序开始时的每一帧都会被调用,而不是Start或者Awake那样只被调用一次。而协程函数就不一样了。它可以随意在某个时间被启用,也可以持续随便一段时间,也可以在任意时间暂停又开始,也可以在任意时间结束。比如说之前文章里的对话框系统。我只想这个脚本在对话框系统启用时才调用它,然后一结束就关闭。这个时候使用线程显然是不太行的,用协程函数写简直无比舒适。demone,协程函数也有所谓的缺点,它的开始,暂停,结束都需要我们手动进行操作,而不像线程那样只要一开程序就不用管了。而且协程改变的参量会被线程改变。比如说我协程写了冲刺给物体的速度赋值,然后它就被线程里的Move函数改回来了,导致根本冲不动~~(????????)~~的事情发生。
        综上所述,协程和线程的主要区别为:协程避免了无意义的调度,由此可以提高性能,但也因此,程序员必须自己承担调度的责任,同时,协程也失去了标准线程使用多CPU的能力。而线程就不需要管那么多,写就完事了,但是我们也要做好不能随意让它停止或者不干扰协程运行的准备。
IEnumerator的神仙妙用

在了解了协程的主要功能后,我们就可以来聊聊协程函数了?然而并不行,我们还要从一个功能开始:
如何完成cd的冷却/倒计时?

        众所周知,一个游戏肯定要有cd:闪现有cd,对话框一个字一个字出来有cd,~~就连手冲都要有cd(我有个朋友想要这个游戏)~~,但是如何实现这个功能呢?这显然是easygame:Update函数会每一帧调用,这样就能实现时间的流逝,而不像其他函数就调用一帧,指过一帧的时间就咋瓦鲁多了,这还怎么整嘛。所以,倒计时系统就应该这么写:
void Update()
{//这里timer要在函数外面赋值,这里就省略了
timer -= Time.deltaTime; //控制cd的变量随时间减小而自减
if(timer <= 0)
Debug.Log("结束辣!");
}
但是,这搞一个倒计时还好,但如果我要整114514个呢?~~(怎么臭起来了)~~这样我们只能多写几个变量一起算?
void Update()
{
shanxianTimer -= Time.deltaTime;
if(shanxianTimer <= 0)
Debug.Log("交闪现辣!");
tell -= Time.deltaTime;
if(tell <= 0)
Debug.Log("讲完辣!");
kkskkksk -= Time.deltaTime;
if(kkskkksk <= 0)
Debug.Log("冲完辣!"); //????????
}
demone,这样做岂不是有亿点繁琐,这样还是3个,如果是114514个~~(捂鼻)~~该怎么办?那不如写个循环???
for(float timer = 114514; timer >= 0; timer -= Time.deltaTime)
{
//交闪现了!blabla
}
Debug.Log("owari!");
欸,你这个写的就正好和协程一样了,因为瓦塔西的协程函数正好可以做到这一点!
协程函数的主要介绍

那我们就以协程函数来写一个倒计时系统吧(
void Start()
{
StartCoroutine(Countdown());
}
IEnumerator Countdown()
{
for(float shanxian = 3; shanxian >= 0; shanxian -= Time.deltaTime)
yield return 0;
Debug.Log("交闪现辣!");
}
在这中间:
StartCoroutine(Countdown())就指的是开始协程函数(协程函数这么nb,自然要有特殊的打开方式
IEnumerator是协程函数特有的开头,就类似于void、int之类的
for函数和Debug没什么好说的,也就是倒计时然后提醒而已。
然后结束协程函数就是StopCoroutine(Countdown())了,然后如果要全部停止就可以打StopAllCoroutine(),但是我们不是经常用,因为协程函数也和其它函数一样,一到末尾就结束了。
接下来才是我们重中之重的:
yield return 0了。
wtf is yield return???

关于yield return 0真的困扰了我很久,对于它的功能,我只能说是:停止执行方法,并且在下一帧从这里重新开始。这是什么意思呢?我们先看下面一段代码:
IEnumerator SayWDNMD()
{//模仿打csgo的语音对话(bushi
Yield return 0;
Debug.Log("W");
Yield return 0;
Debug.Log("D");
Yield return 0;
Debug.Log("N");
Yield return 0;
Debug.Log("M");
Yield return 0;
ebug.Log("D");
}
那么在调用这个方法的结果是什么呢,由于我懒得开Unity截图结果,再加上可能会出现偏差,所以这波直接高中理科实验吧:
时间 内容
0    W
0.02 D
0.04 N
0.06 M
0.08 D那么结果就显而易见了,每当yield return 0调用一次就会暂停0.02s(顺便一提,其中的0没有任何作用,改成1也是停一帧,2也是一帧,114514也是一帧)。我们把协程函数比喻你在彩虹六号:围攻作为防守的一次对局的各种动作,那么yield return 0就相当于lion开了技能时你的表现。你想继续行动,但是yield return 0卡着你硬要你等一帧才能继续。
常见提问:

为什么会出现偏差?
因为Unity每一帧的时间是不一样的,可能这帧时间为0.02s,但下一帧就可能不是。因此Unity提供了Update和FixedUpdate两个函数,其中FixedUpdate函数就是在每个固定一帧的时间内调用。这里为了方便就固定是0.02s~~(明明就是你想偷懒)~~
可不可以不停一帧,改成其他时间?
可以的,但是要改成yield return new WaitForSeconds(Time),其中Time表示你要暂停的时间,以秒为单位。
小细节,大作用

那么我们这样卡一下到底有什么用呢?作用可以说是很大了。因为:
我们可以将需要时间流逝的函数写成了一种单独的方法,不需要一股脑全部丢进Update函数里。并且我们还可以随时停止并规定停止的时间,不像Update函数一开始就停不下来。
协程函数最为简单的应用就是作为倒计时使用,比如进行冲刺后的冷却:
IEnumerator Recover()
    {
        for(;CoolDownTimer>=0f; CoolDownTimer -= Time.deltaTime)
        {
            yield return 0;
        }
        if (CoolDownTimer <= 0)
        {
            canDash = true;
            DashTimer = DashTime;
        }
    }
众所周知,我们在游戏时肯定不会一直冲啊,~~(一直冲现实也受不了吧)~~放进Update函数里肯定不太合适(但实际上放进去是可以的,写个if放里面就差不多,但是作为方法储存代码肯定更好看一点)。而这时候作为方法写进协程函数简直是再好不过了。
常见提问:

yield return 0放在循环里是否必要?
依照我个人理解,其实yield return 0放在哪里都可以。因为它只是借用了协程函数可以按照时间变化而变化的好处来写的,其实并不需要在哪里停一下。但是为什么要写呢,因为Unity规定协程函数至少要有一个yield进行结束,所以就随便放呗~~(大概)~~~
但是要注意一点,在计时的循环里一定要存在yield return 0这个语句,因为它的功能是在这一帧停止,然后在下一帧开始。如果没有加的话,它就一直继续,然后就能在一帧内处理你设定的几秒的计时,也就不存在你的计时了。。。
一些其他要注意的

协程函数可以嵌套 ?

可以的,比如在冲刺和冲刺后回复的两个协程函数
IEnumerator CountingDash()
    {
        if (VerticalMove != 0)
        {
            Dashing = true;
            PressedDash = false;
            CoolDownTimer = CoolDownTime;
            for (; DashTimer >= 0; DashTimer -= Time.deltaTime)
            {
                rb.velocity = new Vector2(rb.velocity.x, VerticalMove * dashSpeed);
                yield return 0;
            }
        }
        if (HorizontalMove != 0)
        {
            Dashing = true;
            PressedDash = false;
            CoolDownTimer = CoolDownTime;
            for (; DashTimer >= 0; DashTimer -= Time.deltaTime)
            {
                rb.velocity = new Vector2(HorizontalMove * dashSpeed, rb.velocity.y);
                yield return 0;
            }
        }
        Dashing = false;
        canDash = false;
        StartCoroutine(Recover()); //看,这里嵌套了
        yield break; //和yield return 0一样的结果
    }
注意

虽然协程函数有时候很好用,但是建议没有掌握的人还是全部丢进Update函数里,毕竟我之前因为协程已经卡死114514次,重做了114514次对话框了。。。
其他的以后想到了在更qwq
差不多讲完了,祝民那桑用协程函数“怎么那么熟练啊”,Unity不卡崩~
友情链接

欢迎来我的博客做做:http://www.karmotrine.com
发表于 2021-12-26 12:51 | 显示全部楼层
膜Karmotrine巨巨
发表于 2021-12-26 12:53 | 显示全部楼层
打错了淦,博客是www.karmotrine.fun[捂脸]
懒得打字嘛,点击右侧快捷回复 【右侧内容,后台自定义】
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Unity开发者联盟 ( 粤ICP备20003399号 )

GMT+8, 2024-11-16 08:25 , Processed in 0.091675 second(s), 25 queries .

Powered by Discuz! X3.5 Licensed

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表