简介
协程(Coroutine)在C#中是一种特殊的函数,它允许开发者编写可以暂停执行并在未来某个时刻恢复执行的代码块。协程通常用于实现异步操作,如延时执行、等待某个事件发生、或者分段执行复杂的任务。在Unity游戏引擎中,协程尤为重要,因为它们可以在不阻塞主线程的情况下执行这些操作。
C#协程的实现依赖于迭代器(Iterator)和状态机(State Machine)。当编写一个协程时,你实际上是定义了一个返回IEnumerator
类型的方法。这个方法内部可以包含yield
语句,用于控制协程的执行流程。yield return
语句可以返回一个值并暂停协程,直到下一帧或特定条件满足时再次执行。yield break
语句则会完全结束协程的执行。
Unity 已经有了Async Operation,为什么还要二次封装或桥接呢?
当然,它在某些情况下可以帮助我们解决很多令人很头疼的问题!例如:要等待一组Async Operation对象,同时要监控他们的进度;再例如,需要同时等待数目不确定的Async Operation,但是他们却来自于不同的系统;又或者,希望一段逻辑去控制Yield的完成状态,那么在这种情况下,桥接和封装是最好的选择。
它可以实现什么效果呢?
下面这段代码展示了如何使用逻辑控制Yield Completed State,当W按键被按下后,yield return new .... 下面的代码才会被执行。
private IEnumerator Start ()
{
Debug.Log($"Start: {Time.time}");
yield return new XAsyncOperationCondition(a=>Pass);
Debug.Log($"End: {Time.time}");
}
private void Update ()
{
if ( Input.GetKey(KeyCode.W) )
{
Pass = true;
}
}
下面这段代码展示了如何将来自不同系统的Async Operation 整合在一起,这对于同时处理不同管线的资产加载进度时非常有帮助。
XAsyncOperationGroup HandleGroup;
private IEnumerator Start ()
{
var url = "https://img0.baidu.com/it/u=1799694557,1475747482&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=750";
var fromWebHandle = new XAsyncOperationWebTextureRequest(UnityWebRequestTexture.GetTexture(url).SendWebRequest());
var fromResourceHandle = new XAsyncOperationResourceRequest<Texture>(Resources.LoadAsync("default.jpg"));
var fromAddressableHandle = new XAsyncOperationAddressable<Texture>(Addressables.LoadAssetAsync<Texture>("default"));
HandleGroup = new XAsyncOperationGroup(fromWebHandle, fromResourceHandle, fromAddressableHandle);
yield return HandleGroup;
//> fromWebHandle.Result
//> fromResourceHandle.Result
//> fromAddressableHandle.Result
}
private void Update ()
{
if ( HandleGroup != null )
{
Debug.Log($"载入进度:{HandleGroup.PercentComplete}");
}
}
如果你想进一步了解他们,下面的UML展示了他们是如何工作的
UML
他被分成了两部分,绿色节点主要用来统计进度,灰色节点主要用来桥接不同管线的资源,绿色的节点通过访问一个全局静态的GameObject实现了在不需要MonoBehaviour的情况下投递协程作业。
核心代码示例
//> XGlobalCoroutine.cs
//> Create by UniMark
using System.Collections;
using UnityEngine;
namespace XF.Internal
{
internal class XGlobalCoroutine : MonoBehaviour
{
internal static XGlobalCoroutine Behaviour;
public static XGlobalCoroutine Instance
{
get
{
if (Behaviour == null)
{
GameObject root = new GameObject($"[Global]{typeof(XGlobalCoroutine).Name}");
GameObject.DontDestroyOnLoad(root);
Behaviour = root.AddComponent<XGlobalCoroutine>();
}
return Behaviour;
}
}
}
internal static class XAsyncOperationExtends
{
internal static void StartCoroutine(this XAsyncOperation operation, IEnumerator routine)
{
if (routine == null) return;
XGlobalCoroutine.Instance.StartCoroutine(routine);
}
internal static void StopCoroutine(this XAsyncOperation operation, IEnumerator routine)
{
if (routine == null) return;
XGlobalCoroutine.Instance.StopCoroutine(routine);
}
}
}
//> XAsyncOperation.Implement.cs
//> Create by UniMark
using System;
using System.Collections;
namespace XF
{
/// <summary>
/// 异步操作的抽象类
/// </summary>
public abstract class XAsyncOperation : IEnumerator
{
/// <summary>
/// 是否完成
/// </summary>
public bool IsComplete { get; private set; }
/// <summary>
/// 完成百分比,取值[0~1]
/// </summary>
public virtual float PercentComplete { get; protected set; }
/// <summary>
/// 期间发生的错误信息,如果没有返回string.empty
/// </summary>
public string Error { get; internal set; }
/// <summary>
/// 完成时调用的回调
/// 在完成前注册的回调会在完成时统一调用一次
/// 在完成后注册的回调会在注册时立即执行一次
/// </summary>
public virtual event Action<XAsyncOperation> OnCompleted
{
add
{
if (IsComplete)
value?.Invoke(this);
else
CompletedEvent += value;
}
remove
{
CompletedEvent -= value;
}
}
protected Action<XAsyncOperation> CompletedEvent;
protected virtual void SetComplete()
{
IsComplete = true;
PercentComplete = 1;
CompletedEvent?.Invoke(this);
CompletedEvent = null;
}
public virtual void Release() { }
#region 接口
object IEnumerator.Current => null;
bool IEnumerator.MoveNext() => !IsComplete;
void IEnumerator.Reset() { }
#endregion
}
}
完整代码示例
unity_custom_async_operation