一节课彻底掌握Unity中协程的用法

本文详细介绍了Unity中的协程,强调其与多线程的区别,通过实例展示了如何使用协程实现定时器、控制对象运动等。协程允许在主线程中执行代码逻辑,通过yield保留方法状态,实现跨帧操作。文章还探讨了协程的开启、终止以及返回值,提供了多个实用的案例,如定时打印和物体平滑移动。
摘要由CSDN通过智能技术生成


前言

协程在Unity开发中非常重要,但注意:协程跟多线程没有任何关系,不要将两者混为一谈,接下来就跟大家分享一下我对协程的理解及用法!


一、协程是什么?

协程是一段在主线程中执行的代码逻辑,协程不是多线程。Unity的协程在每帧结束后去检测yiled的条件是否满足。


二、学习使用协程

1.首先定义多个定时器,去实现游戏中的逻辑...

代码如下:

  float timer1 = 3f;

  float timer2 = 5f;

  float timer3 = 8f;

  void Update()

    {

        timer1 -= Time.deltaTime;

        if (timer1 <= 0)

        {

            Debug.Log("3s过后...");

            timer1 = 3f;

        }

    }

相信大家都写过类似代码,这种代码如果项目中需要多个定时器时,会显得非常臃肿,并且我们经常忘记做一件事情,比如忘记充值定时器...

我们都学过循环,for循环中是将变量i定义为局部变量,封装成一个代码块,那我们是否可以将定时器也封装成一个代码块呢?如果可以的话,那么代码应该是这样的:

 for (float timer = 3; timer >= 0; timer -= Time.deltaTime)

        {

            

        }

现在每一个计时器变量都成为for循环的一部分了,这看上去好多了,而且我不需要去单独设置每一个迭代变量。 但是这段代码放在哪里去执行呢?start?update?显然都不可以,所以恰好协程可以做到这一点。我们回顾一下协程的概念,

为了能在连续的多帧中(在这个例子中,3秒钟等同于很多帧)调用该方法,Unity必须通过某种方式来存储这个方法的状态,

     -这是通过IEnumerator 中使用yield return语句得到的返回值,当你“yield”一个方法时,你相当于说了,“现在暂停这个方法,

     -然后在下一帧中从这里继续执行!”。

    注意:用0或者null来yield的意思是告诉协程等待下一帧,直到继续执行为止。

    当然,同样的你可以继续yield其他协程,我会在下一个教程中讲到这些。

代码如下:

    IEnumerator CountDown(){

        for (float timer = 3; timer >= 0; timer -= Time.deltaTime)

        {

            yield return 0;//现在停止这个方法,然后在下一帧中从这里继续执行!

        }

        Debug.Log("3s以后...");

    }

2.案例2

  /*

     * 接下来通过实例

     * 1.实现打印5次--我要学游戏开发!

     * 2.实现将这5次输出分到每一帧里去实现:每帧打印1次,共打印5次!

     * 3.每一帧输出“我要学游戏开发!”,无限循环。。。

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

*/

2.代码如下:

  IEnumerator SayHello5Times()

    {

        for (int i = 0; i < 5; i++)

        {

            Debug.Log("我要学游戏开发!");

            yield return 0;

        }

3.类似Update,代码如下:

 IEnumerator SayHello5Times()

 {

      while (true)

        {

            //1.输出结果

            Debug.Log("我要学游戏开发!");

            //2.等待下一帧

            yield return 0;

        }

//3. 这里永远没有机会执行

  }

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

 接下来做一个定时器 每隔几秒完成某一件事

    IEnumerator CountSeconds()

    {

        int seconds = 0;

        while (true)

        {

            for (float timer = 0; timer < 1; timer += Time.deltaTime)

            {

                yield return 0;

            }

            seconds++;

            Debug.Log("自协程启动以来已经过了"+ seconds+"秒");

        }

    }

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

3.开启和终止协程

 之前,我们已经学过了通过 StartCoroutine()方法来开始一个协程。

 如果我们想要终止所有的协程,可以通过StopAllCoroutines()方法来实现,

 注意,这只会终止在调用该方法的对象中开始的协程,对于其他的MonoBehavior类中运行的协程不起作用。

          那我们怎么终止其中的一个协程呢?在这个例子里,这是不能的,如果你想要终止某一个特定的协程,

        那么你必须得在开始协程的时候将它的方法名作为字符串,就像这样:

        1、以字符串开启/关闭,缺点:只能有一个参数

        StartCoroutine("FirstTimer");

        StopCoroutine("FirstTimer”);

        2、开启带有参数的协程的两种方式:

        StartCoroutine(Sayhi("hi")) 

        StartCoroutine("Sayhi","hi")

        3、如何终止多个参数的协程呢?接受返回值

    Coroutine stopCor_2 = StartCoroutine(Cor_2());

    StopCoroutine(stopCor_2);

        4、StopAllCoroutines

        5、通知禁用或者销毁方式

      gameObject.SetActive(false); 

      //通过销毁游戏对象方式和禁用同效果

      //Destroy(gameobject)

4.协程的返回值

        协程一旦被开启后 总是试图将方法内的代码执行完 之后停止

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

       2.yield return new WaitForSeconds(n) 表示在n秒后执行后面的代码 但是会收到time.timescale 影响 ,如下代码:

    //隔一定时间完成某件事 

    IEnumerator SaySomeThings()

    {

        Debug.Log("协程开始执行");

        yield return StartCoroutine(Wait(1.0f));

        Debug.Log("距离上一条消息已经过去1秒了");

        yield return StartCoroutine(Wait(2.5f));

        Debug.Log("距离上一条消息已经过去2.5秒了");

    }

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

等待的方法还可以使用下面方式来实现: 

    IEnumerator Wait(float duration)

    {

        for (float timer = 0; timer < duration; timer += Time.deltaTime)

            yield return 0;

    }

       3.在协程内 如果遇到yield return StartCoroutine(test) 剩余的代码将在子协程执行完毕后才能继续执行

       4.如果遇到 yield return new WaitForFixedUpdate 表示剩余代码将在FixedUpdate 执行完毕后执行

       5.如果遇到  yield return WWW 等待一个网络请求完成后继续向下执行

       6.如果遇到 yield return gameObject; 表示在gameobj不为空时向下执行

 5.案例应用

控制对象行为的例子

        在最后一个例子中,我们就来看看协程如何像创建方便的计时器一样来控制对象行为。协程不仅仅可以使用计数的时间来yield,它还能很巧妙地利用任何条件。将它与嵌套结合使用,你会得到控制游戏对象状态的最强大工具。运动到某一位置,对于下面这个简单脚本组件,我们可以在Inspector面板中给targetPosition和moveSpeed变量赋值,程序运行的时候,该对象就会在协程的作用下,以我们给定的速度运动到给定的位置。

代码如下:

public Vector3 targetPosition;  

    public float moveSpeed;

    void Start1()

    {

        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() ,我们可以让它在这些点之间持续运动。

 代码如下:

 public Vector3[] path;  

    void Start2()

    {

        StartCoroutine(MoveOnPath(true));

    }

    IEnumerator MoveOnPath(bool loop)

    {

        do

        {

            foreach (var point in path)

                yield return StartCoroutine(MoveToPosition(point));

        }

        while (loop);

    }

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

课堂练习:尝试让物体在某个点停留3s

如果把Wait()程序加进来,这样就能让我们的对象在某个点就可以选择是否暂停下来,就像一个正在巡逻的AI守卫一样,并且这种实现方式看起来非常优雅! 


三、总结

        l 多个协程可以同时运行,它们会根据各自的启动顺序来更新;

        l 协程可以嵌套任意多层(在这个例子中我们只嵌套了一层);

        l 如果你想让多个脚本访问一个协程,那么你可以定义静态的协程;

        l 协程不是多线程(尽管它们看上去是这样的),它们运行在同一线程中,跟普通的脚本一样;

        l 如果你的程序需要进行大量的计算,那么可以考虑在一个随时间进行的协程中处理它们;

        l IEnumerator类型的方法不能带ref或者out型的参数,但可以带被传递的引用;

        l 协程有多种开启和终止的方法,但是最好用哪种方式开启,就是用哪种方式终止。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值