Unity 协程
概述
定义
1、定义:协同程序,即在主程序运行的同时开启另一段逻辑处理来协同当前的程序执行,所有的协同程序都是在主线程中运行的。
2、说明:在进行主任务的过程中,我们需要一个对资源消耗极大的操作时候。如果在一帧中实现这样的操作,游戏就会变得十分卡顿。这个时候,我们就可以通过协程,将任务分散到多个帧中,同时不影响主任务的进行。
协程和线程
1、在Unity中一般不考虑使用多线程,因为在Unity中只能在主线程中获取物体的组件和方法,如果脱离这些,Unity的很多功能无法实现。
2、对于协程而言,同一时间只能执行一个协程,而线程则是并发的,可以同时有多个线程在运行。
协程和Update
在MonoBehaviour
生命周期的Update
和LateUpdate
之间,会检查这个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();
}