Unity全面入门笔记16-异步

异步

Invoke

  • Invoke方法
public void MonoBehaviour.Invoke(string methodName, float time);
public void MonoBehaviour.InvokeRepeating(string methodName, float time, float repeatRate);
public void MonoBehaviour.CancelInvoke();
public void MonoBehaviour.CancelInvoke(string methodName);
public bool MonoBehaviour.IsInvoking(string methodName);

Invoke是一组将函数交给Unity架构运作的方法,使用Invoke调用的函数是脱离组件生命周期的。

Invoke委派了在物理时间经过time秒后调用methodName方法。

InvokeRepeating委派了物理时间经过time秒后调用methodName方法,随后每repeatRate秒再调用一次。

CancelInvoke可以停止由本脚本委派的所有Invoke函数和InvokeRepeating函数,如果加上参数,则取消对应类型的函数。

IsInvoking可以返回本脚本是否已经委派了至少一个Invoke函数。

以下是使用Invoke实现灯光闪烁的效果的例子:

public class LightBlink : MonoBehaviour
{
	Light light;
	private void Start()
	{
		light = GetComponent<Light>();
		TurnOnLight();
	}
	private void TurnOnLight()
	{
		light.enabled = true;
		Invoke("TurnOffLight",2);
	}
	private void TurnOffLight()
	{
		light.enabled = false;
		Invoke("TurnOnLight",2);
	}
}

以下是使用Invoke实现使物体逐渐缩小的效果的例子:

public class ShrinkEffect : MonoBehaviour
{
	private void Start()
	{
		InvokeRepeating("Shrink", 0, 0.1f);
		Invoke("StopShrink",5);
	}
	private void Shrink()
	{
		transform.localScale *= 0.95f;
	}
	private void StopShrink()
	{
		CancelInvoke("Shrink");
	}
}

如果Invoke时不能找到需要找到的函数,不会抛出运行时异常,但会在Console面板输出一条信息。

Invoke的最大缺点在于被委派的函数只能是无参数函数。但由于Invoke的调用是脱离生命周期的,所以当组件enable为false或物体activeSelf为false时,Invoke可以继续执行,相反的,协程是不可以的。如果需要在SetActive(true)前进行一些与本体无关的额外操作而需要推迟SetActive(true)时(如登场时的光效动画),可以使用Invoke代替Coroutine。

Coroutine

  • 协程和线程

协程和多线程不同,协程由Unity框架提供,在组件生命周期中被执行,协程的处理在物理循环或逻辑循环之后,具体在何时处理由协程的构建方式决定;而线程则由操作系统调度。所有涉及组件生命周期的功能都应该使用协程而不是线程实现。

协程和线程都可以达成表面上的并行效果,但协程的切片是开发者手动进行的,可以预期切片的次数和位置,以及每个切片在每一帧的运行时间,可以保证在功能A所有语句运行完后再执行功能B的语句;多线程中切片由操作系统控制,线程是交错运行的,难以保证语句的顺序。

协程始终属于同一个游戏线程,所以使用大量协程时并不能充分利用硬件多核特性,而多线程可以。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dauRy0gn-1569897360358)(C:\Users\UPiracy\AppData\Roaming\Typora\typora-user-images\1569516425528.png)]

使用线程而不是协程的情况:

(1) 在不用任何插件的情况下实现网络通讯或http请求。

(2) 加载外部文件,调用其他软件(比如内付调用支付软件)。

(3) 十分耗时的,在单线程中难以保证流畅度的运算(大规模查找和排序等)。

  • 迭代器IEnumerator

我们通过迭代器来实现程序的切片,使用不同yield语句可以将切片分配给不同的处理方式。程序在运行到yield语句时会"挂起",等待调节合适时再继续运行。

使用StartCoroutine来开启一个协程:

public Coroutine MonoBehaviour.StartCorountine(IEnumerator routine);
public Coroutine MonoBehaviour.StartCorountine(string methodName; object value = null);

协程开启后会由本组件的生命周期执行,开启协程之后的语句不会受协程暂停。

在组件A中使用StartCoroutine调用组件B中的迭代器开启协程,这个协程将会由组件A的生命周期执行。

构造一个迭代器,我们需要声明一个返回值为IEnumerator的方法,然后通过yield return语句来标记需要挂起的位置和唤醒的模式,下图中的组件展示了协程的逻辑和执行顺序:

public class CoroutineExample : MomoBehaviour
{
	private void Start()
	{
	    Debug.Log("Invoke corountine.");
	    StartCoroutine(RoutineExample());
	    Debug.Log("Finish invoke coroutine.");
	}

	private IEnumerator RoutineExample()
	{
	    Debug.Log("Coroutine Start.");
	    yield return new WaitForSeconds(3);
	    Debug.Log("Coroutine End");
	}
}

上述代码中yield return new WaitForSeconds(3)可以将协程挂起,在3秒后再执行后续代码。

yield return的方式不同,挂起的方式也不同

yield return 0;
yield return null;
yield return;

这三种方法可以将协程挂起,在下一个逻辑帧Update后继续执行后续代码。它们不会受timeScale影响。

yield return new WaitForFixedUpdate();

将协程挂起,在下一个物理帧FixedUpdate后继续执行后续代码。会受timeScale影响。

yield return new WaitForSeconds(float time);

将协程挂起,在物理循环经过time秒后继续执行后续代码。会受timeScale影响。

yield return new WaitForSecondsRealtime(float time);

将协程挂起,在逻辑循环经过time秒后继续执行后续代码。不会受timeScale影响。

yield return new WaitForEndOfFrame();

将协程挂起,在该逻辑帧的渲染结束后继续执行后续代码。

yield return new StartCoroutine(IEnumerator routine);

将该协程挂起,开启目标协程,待目标协程结束后,继续执行该协程的后续代码。

yield return new WaitUntil(()=>bool condition);

每个逻辑帧Update之后检测一次,当Lambda表达式返回ture时,继续执行后续代码。

yield return new WaitUntil(()=>bool condition);

每个逻辑帧Update之后检测一次,当Lambda表达式返回false时,继续执行后续代码。

yield break;

结束协程,等同于普通函数中的return。

注:生命周期中的一些事件可以改为IEnumerator来使用协程调用,如Start和Awake等。

  • 中断协程

中断协程时,所有该组件上正中被挂起的协程会被移除。中断协程的方法包括:

public void MonoBehaviour.StopAllCoroutines();
public void MonoBehaviour.StopCoroutine(string methodName);
public void MonoBehaviour.StopCoroutine(Coroutine routine);

其中,使用string作为参数的函数,不能用于停止不是由string作为参数开启的协程。

我们可以在开启协程时将其保存下来以方便将来定向的将其停止:

public class StopCoroutineExample : MonoBehaviour
{
	public IEnumerator Start()
	{
		Coroutine routine = StartCoroutine(LogMessage());
		yield return new WaitForSeconds(1.5f);
		StopCoroutine(routine);
		Debug.Log("Coroutine end manually.");
	}
	
	public IEnumerator LogMessage()
	{
		Debug.Log("Coroutine start.");
		yield return new WaitForSeconds(3);
		Debug.Log("Coroutine end automatically.");
	}
}

  • 协程的优势

使用协程有效的避免了一些垃圾变量和多余函数,让代码更优雅。我们使用协程重新实现了上文中使用Invoke的两个效果。

使用协程实现灯光闪烁的效果:

public class LightBlink : MonoBehaviour
{
	Light light;
	private void Start()
	{
		Coroutine controller = StartCoroutine(LightBlinking);
	}
	
	private IEnumerator LightBlinking()
	{
		for(;;)
		{
			light.enabled = true;
			yield return new WaitForSeconds(2);
			light.enabled = false;
			yield return new WaitForSeconds(2);
		}
	}
}

使用协程使物体逐渐缩小:

public class ShrinkEffect : MonoBehaviour
{
	private void Start()
	{
		StartCoroutine(Shrink(5,0.1f));
	}
	
	private IEnumerator Shrink(float time; float rate)
	{
		for(float t = 0; t <= time; t += rate)
		{
			transform.localScale *= 0.95f;
			yield return new WaitForSeconds(rate);
		}
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值