当前位置: 首页 > article >正文

第十八章 协程

我们知道脚本都是继承自MonoBehaviour类,而其中的Update方法里面放置了大部分的游戏逻辑处理代码。Update方法是游戏循环的每一帧都去执行,这就要求我们的代码“无时无刻”不在处理所有的可能发生的情况,并做出相应的处理。如果我们想要完成“一段时间”的逻辑代码,例如在游戏中发射一颗子弹,这个过程相对于游戏无限循环的时间来说,非常的短暂,如果我们在Update中去完成这样的逻辑,需要根据时间来进行判断,显然这个完成过程非常的复杂。这个时候,Unity给我们提供了协程,从字面意义上理解就是协助程序的意思,类似于“线程”,帮助我们在主任务进行的同时,需要一些分支任务配合工作来达到最终的效果。但是,协程不是线程,协程依旧是在主线程中进行。Unity中的协程由协程函数和协程调度器两部分构成。通过关键字IEnumerator来定义一个协程(迭代方法),然后再程序中通过StartCoroutine来开启一个协程。

接下来,我们创建一个“SampleScene8”的新创景,然后创建一个“Cube”和一个“CubeCoroutine.cs”的脚本文件,并将他们附加在一起。接下来,我们就在脚本文件中定义一个协程方法,如下所示

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class CubeCoroutine : MonoBehaviour
{
    // Update is called once per frame
    void Update()
    {
        // 按下空格键
        if (Input.GetKeyDown(KeyCode.Space))
        {
            // 启动协程
            StartCoroutine("MoveTest");
        }
    }

    // 定义协程方法
    IEnumerator MoveTest()
    {
        transform.Translate(new Vector3(1, 0, 0));
        yield return null;

        transform.Translate(new Vector3(1, 0, 0));
        yield return null;
    }
}

在上面的代码中,我们使用IEnumerator MoveTest来定义协程方法,方法名称为MoveTest。在协程方法中,我们让Cube沿X轴移动两次,每次移动1个单位长度。然后我们在Update方法中监听空格键的按下,使用StartCoroutine方法来启动协程,方法参数就是协程方法名称。接下来我们就Play当工程,查看运行效果

当我们按下一次空格的时候,Cube会移动两次,每次移动1个单位长度,由于代码执行太快,因此我们看不到两次移动之间的间隔。但是从Inspector检视面板中的X数值可以看到,我们每次按下空格的时候,Cube的确是移动了两个单位的长度。

当然,这样简单的代码并不能展示协程的强大之处。我们先从协程方法里面的return解释一下。在普通方法里面,我们经常使用return方法来返回方法的执行的结果值,但是在协程方法里面,这个return并不是返回结果值。上文代码中的“yield return null;”表示暂停协程等待下一帧继续执行。这样的返回设计,让我们能够控制在指定数量的帧中来执行不同的代码,而不是在所有的帧中执行复杂的条件代码。什么意思呢?

        transform.Translate(new Vector3(1, 0, 0));
        yield return null;

        transform.Translate(new Vector3(1, 0, 0));
        yield return null;

我们的协程方法执行后,会在Update方法中执行transform.Translate(new Vector3(1, 0, 0))代码,也就是让Cueb沿X轴移动1个单位的距离。然后使用yield return null停止协程方法等待下一次Update方法。下一次Update方法执行后,就会执行协程中第二个transform.Translate(new Vector3(1, 0, 0))代码,继续让Cueb沿X轴移动1个单位的距离。然后使用yield return null停止协程方法。至此,协程方法执行完毕。

我们回头思考一下发射子弹的协程该如何实现呢?当我们按下空格键代表发射动作的时候,我们可以启动一个子弹飞行协程,在这个协程方法中,我们可以根据指定的速度和射程来移动子弹,协程结束也就代表子弹的飞行逻辑结束。但是,这个需要借助精确的时间来实现。此时,我们可以借助使用“yield return new WairForSeconds(时间);”来表示等待规定时间后继续执行,这样让我们从时间角度出发来执行我们的代码,而不用考虑他们到底在那一帧中去执行。我们稍微改动我们的代码。

    // 定义协程方法
    IEnumerator MoveTest()
    {
        transform.Translate(new Vector3(1, 0, 0));
        //yield return null;
        yield return new WaitForSeconds(1.0f);

        transform.Translate(new Vector3(1, 0, 0));
        yield return null;
    }

说白了,我们让两次移动间隔1秒钟。接下来,我们在来Play当前工程,查看运行效果

这次当我们按下空格键的时候,两次移动之间由于间隔了1秒钟,因此我们看的比较清楚了。从左边的Inspector检视视图中我们也能够看到X从0变到1,再变到2的过程了。接下来,我们回到子弹发射的问题上来。注意,子弹的速度我们可以固定为一个数值,然后射击的距离也可以固定为一个数值。那么,在那个时间点,子弹飞行到哪里,我们就能够计算出来了。如果觉得1秒钟不精确的话,我们可以减小这个时间,这样子弹位置的更新就比较连续了。最后,协程方法也可以定义形参,那么在使用StartCoroutine调用的时候,可以不使用字符串而直接调用方法。代码如下:

    // Start is called before the first frame update
    void Start()
    {
        StartCoroutine(ParamTest(100));
    }

    // 定义包含参数的协程方法
    IEnumerator ParamTest(int x)
    {
        Debug.Log(x);
        yield return null;
    }

运行结果如下:

注意:可以使用 StopCoroutine 和 StopAllCoroutines 来停止协程。 当用 SetActive(false) 禁用某个协程所附加到的游戏对象时,该协程也将停止。调用 Destroy销毁游戏对象时候,也会停止自身的协程。但通过在 MonoBehaviour 实例上将 enabled 设置为 false 来禁用 MonoBehaviour 时,协程不会停止。

总结:协程只启动一次。而后Unity会在后续的update中执行协程中的循环体。有多次循环,就会在多少次update去分别执行循环体代码。说白了,协程就是将一个过程化的行为分解到了update方法中执行。Unity中的协程可以有两种用途:第一,延迟调用;第二,分解操作,把一个过程分解执行。例如,敌人死亡后淡出消失,改变材质颜色透明度即可,让透明度由1变成0,这是一个过程,可以使用协程来实现。再比如,我们可以通过协程进行寻路A点,B点,C端(巡逻)。其实现方式就是协程嵌套协程,一个协程完成从一个点A移动到另一个点B后,本协程结束后再开启下一个协程,然后从当前点B在继续移动到下一个点C。

本课程涉及的内容已经共享到百度网盘:https://pan.baidu.com/s/1e1jClK3MnN66GlxBmqoJWA?pwd=b2id


http://www.kler.cn/news/16717.html

相关文章:

  • Vue父组件生命周期和子组件生命周期触发顺序
  • Reactive响应式编程系列:解密reactor-netty如何实现响应式
  • Java 基础入门篇(一)—— Java 概述
  • CF1060E Sergey and Subway
  • 并发编程之Atomic原子操作类
  • 【华为OD机试真题】计算网络信号 (javaC++python)100%通过率 超详细代码注释
  • 【计算机视觉】ViT:代码逐行解读
  • linux入门---软硬链接
  • 支持轴体热插拔的平价机械键盘,全尺寸带灯效,雷柏V700DIY上手
  • linux 设置开机启动不同方式
  • Linux系统中查看日志的命令
  • CentOS软件那么老为什么大家还要用它?
  • 为什么在马云成功前就有那么多影像留下来?
  • SpringBoot调取OpenAi接口实现ChatGpt功能
  • rac部署前配置互信
  • CUDA编程(六):代码分析与调试
  • 死信队列
  • Vue3透传Attributes
  • Crowdsoure的简单介绍
  • Android Signal 使用
  • 关于使用Notion的board做工作安排这件事
  • 『Linux』第九讲:Linux多线程详解(一)_ 线程概念 | 线程控制之线程创建 | 虚拟地址到物理地址的转换
  • 云原生技术概谈
  • 医院安全(不良)事件报告系统 PHP语言实现
  • 【华为/华三】PPP
  • springbean 并发安全
  • Vue3中如何实现数字翻牌效果?
  • Redis-哈希
  • 互联网摸鱼日报(2023-04-29)
  • Docker基本管理