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

Unity 协程

概述

定义

1、定义:协同程序,即在主程序运行的同时开启另一段逻辑处理来协同当前的程序执行,所有的协同程序都是在主线程中运行的。

2、说明:在进行主任务的过程中,我们需要一个对资源消耗极大的操作时候。如果在一帧中实现这样的操作,游戏就会变得十分卡顿。这个时候,我们就可以通过协程,将任务分散到多个帧中,同时不影响主任务的进行。

协程和线程

1、在Unity中一般不考虑使用多线程,因为在Unity中只能在主线程中获取物体的组件和方法,如果脱离这些,Unity的很多功能无法实现。

2、对于协程而言,同一时间只能执行一个协程,而线程则是并发的,可以同时有多个线程在运行。

协程和Update

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

协程能做的Update都能做,那为什么还需要协程呢? 

答:使用协程,可以把一个跨越多帧的操作封装到一个方法内部,代码会更清晰。

协程的优缺点

优点

1、协程是辅助主线程的操作,避免游戏卡顿

2、协程可以在不创建新线程的情况下实现异步等待和延迟执行,避免了线程切换和同步等问题,从而提高了程序的性能和效率。

缺点

1、依赖于MonoBehaviour

2、不能有返回值

代码使用

协程的开启和调用

在Unity中通过StartCoroutine方法来启动协同程序,使用yield关键字来中断协同程序。

方法一

StartCoroutine(SayHi());

IEnumerator SayHi()
{
  yield return null;
  Debug.Log("Hello World!");
}

方法二

StartCoroutine("SayHi",1);

IEnumerator SayHi(int a)
{
  yield return null;
  Debug.Log($"Hello World! {a}");
}

方法三

private IEnumerator coroutine;
private void Start()
{
  coroutine = SayHi(1, 2, 3);
  StartCoroutine(coroutine);
}

IEnumerator SayHi(int a,int b,int c)
{
  yield return null;
  Debug.Log($"Hello World! {a} {b} {c}");
}

Yield Return

描述

null

下一帧再执行后续代码

数字

任意数字,意义和null相同,下一帧再执行后续代码,数字没有具体含义

asyncOperation

异步操作结束后再执行后续代码

StartCoroution

某个协程执行完毕后再执行后续代码

WWW

等待WWW操作完成后再执行后续代码

WaitForEndOfFrame

程序中该帧执行的事件都结束了,再执行后续代码

WaitForSeconds

等待几秒,指定延迟时间后执行后续代码(会受Time.timeScale影响)

WaitForSecondsRealtime

等待几秒,指定延迟时间后执行后续代码(不受Time.timeScale影响)

WaitForFixedUpdate

等到FixedUpdate结束后执行后面的代码

WaitUntil

将协程执行直到输入的参数或委托为true的时候

WaitWhile

将协程执行直到输入的参数或委托为false的时候

停止协程

//*****************第一种  StopCoroutine*****************
///第一个重载  StopCoroutine(IEnumerator routine)
private IEnumerator enumerator;

coroutine = TryTimer();
StartCoroutine(enumerator);

StopCoroutine(enumerator);

//第二个重载  StopCoroutine(string methodName)
StartCoroutine("TryTimer");
StopCoroutine("TryTimer");

//第三个重载  StopCoroutine(Coroutine routine)
private Coroutine coroutine;
coroutine = StartCoroutine(TryTimer());
StopCoroutine(coroutine);


//*****************第二种  销毁脚本*****************
Destroy(this);

//*****************第三种  禁用或销毁脚本所在对象*****************
this.gameObject.SetActive(false);
Destroy(this.gameObject);


//(注意)禁用脚本不能停止协程
this.enabled = false;

应用

1、复杂程序分帧执行

如果一个复杂的函数对于一帧的性能需求很大,我们就可以通过yield return null将步骤拆除,从而将性能压力分摊开来,最终获取一个流畅的过程。

未用协程

 for (var i = 1; i <= 10000; i++)
 {
    Instantiate(child, root.transform);
 }

使用协程

StartCoroutine(CreateObj());

IEnumerator CreateObj()
{
  for (var i = 1; i <= 10000; i++)
  {
     Instantiate(child, root.transform);
     yield return null;
  }
}

2、实现计时器

void Start()
{
  //每2秒执行一次doSomething
  StartCoroutine(UpdateTimer(2f, DoSomething));
}

IEnumerator UpdateTimer(float timer,Action callBack)
{
  var wait = new WaitForSeconds(timer);
  while (true)
  {
     yield return wait;
     callBack();
  }
}

void DoSomething()
{
  Debug.Log("每2秒执行一次");
}

3、淡入淡出

using System.Collections;
using UnityEngine;
using UnityEngine.UI;

public class Test : MonoBehaviour
{
    public Image fadePlane;
    private float fadeTime = 1;
    
    private void Update()
    {
        if (Input.GetKeyDown(KeyCode.A))
        {
            StartCoroutine(Fade(true));//测试淡入
        }
        
        if (Input.GetKeyDown(KeyCode.D))
        {
            StartCoroutine(Fade(false));//测试淡出
        }
    }
    
    IEnumerator Fade(bool isFadeIn)
    {
        var from = fadePlane.color;
        var to = new Color(from.r, from.g, from.b, isFadeIn ? 0 : 1);
        
        float speed = 1 / fadeTime;
        float percent = 0;

        while (percent < 1)
        {
            percent += Time.deltaTime * speed;
            fadePlane.color = Color.Lerp(from, to, percent);
            yield return null;
        }
    }
}

4、打字机效果

using System.Collections;
using UnityEngine;
using UnityEngine.UI;

public class Test : MonoBehaviour
{
   public Text txt;
   public WaitForSeconds timer;
   
   void Start()
   {
      timer = new WaitForSeconds(0.1f);
   }

   private void Update()
   {
      if (Input.GetKeyDown(KeyCode.Space))
      {
         var str = "好好学习,天天向上!";
         StartCoroutine(Typewriter(str));
      }
   }
   
   //打字机效果
   IEnumerator Typewriter(string str)
   {
      txt.text = "";
      var count = str.Length;
      var len = 1;
      while (len < count)
      {
         txt.text = str.Substring(0,len);
         len++;
         yield return timer;
      }
   }
}

4、异步加载

Resources异步加载

using System.Collections;
using UnityEngine;
using UnityEngine.UI;

public class Test : MonoBehaviour
{
   public Image _image;

   private void Start()
   {
      // 异步加载--协程模式加载
      StartCoroutine(LoadOver());
   }

   // 使用协程加载资源
   IEnumerator LoadOver()
   {
      ResourceRequest rq = Resources.LoadAsync<Sprite>("icon_land_1_9_2");
      /*
       加载完毕继续执行, 为什么yield return rq呢? 因为ResourceRequest继承了AsyncOperation
      而AsyncOperation 继承了 YieldInstruction 且协程Coroutine中也是继承了 YieldInstruction
      这意味着协程会在这里等待,直到资源加载完成。
      一旦资源加载完成,协程就会从 yield return 语句的下一条语句继续执行。
      */
      yield return rq;
      // 资源加载完毕后,继续执行
      _image.sprite = rq.asset as Sprite;
   }
}

AB包资源的异步加载

private IEnumerator LoadAssetBundle(string path)
{
    AssetBundleCreateRequest rest = AssetBundle.LoadFromFileAsync(path);
    yield return rest;
    
    Debug.Log("协程加ab包完成");
    
}

场景的异步加载

    
private Slider loadingSlider;
private TMP_Text loadText;
public void LoadMultiplay()
{
   
    StartCoroutine(LoadAsync(1));
}

IEnumerator LoadAsync(int sceneIndex)
{
    AsyncOperation operation = UnityEngine.SceneManagement.SceneManager.LoadSceneAsync(sceneIndex);
    while (!operation.isDone)
    {
        loadingSlider.value = operation.progress;
        int loadingValue = Convert.ToInt32(loadingSlider.value);
        loadText.text = $"{loadingValue * 100}%";
        yield return null;
    }
}

UnityWebRequest请求

/// <summary>
/// 协程:下载文件
/// </summary>
IEnumerator DownloadFile()
{
    UnityWebRequest uwr = UnityWebRequest.Get("http://www.xxxx.mp4"); //创建UnityWebRequest对象,将Url传入
    uwr.SendWebRequest();//开始请求
    if (uwr.isNetworkError || uwr.isHttpError)//如果出错
    {
        Debug.Log(uwr.error); //输出 错误信息
    }
    else
    {
        while (!uwr.isDone) //只要下载没有完成,一直执行此循环
        {
            
            yield return 0;
        }

        if (uwr.isDone) //如果下载完成了
        {
            
        }
    }
}

WWW模块的异步请求

public Image img;
public void Start()
{
    StartCoroutine(DownloadImage("", img));
}

IEnumerator DownloadImage(string url, Image image)
{
    WWW www = new WWW(url);
    yield return www;

    Texture2D tex2d = www.texture;
    //将图片保存至缓存路径
    byte[] pngData = tex2d.EncodeToPNG();
    File.WriteAllBytes(Application.dataPath + url.GetHashCode(), pngData);

    Sprite m_sprite = Sprite.Create(tex2d, new Rect(0, 0, tex2d.width, tex2d.height), new Vector2(0, 0));
    image.sprite = m_sprite;
    image.SetNativeSize();
}

http://www.kler.cn/a/560874.html

相关文章:

  • 【NLP 26、实践 ⑥ 引入bert,判断文本中是否有特定字符出现】
  • Linux 命令大全完整版(12)
  • 论文笔记:Scaling Sentence Embeddings with Large Language Models
  • 服务器能否拒绝非浏览器发起的HTTP请求?
  • 0224-leetcode-459.重复的子字符串、283. 移动零
  • unity学习53:UI的子容器:面板panel
  • 【网络安全】从零开始的CTF生活
  • 一文讲解Redis中的基本数据类型
  • postman并发测试某个接口
  • 计算机毕业设计SpringBoot+Vue.jst在线文档管理系统(源码+LW文档+PPT+讲解)
  • Dify部署无法拉取镜像
  • docker compose安装redis
  • 速通HTML
  • XML XML约束 三、Schema
  • 修改/etc/hosts并生效
  • 一篇文章学懂Vuex
  • ESP32系列芯片模组方案,设备物联网无线通信,智能化交互响应控制
  • ubuntu磁盘挂载
  • Websock Demo(二) Java后端代码
  • SQL:DQL数据查询语言以及系统函数(oracle)