赞
踩
UniTask是Unity中的Task实现,Task是C#中实现异步操作的一个模块(类)。UniTask与Task有着同样的使用思路(使用习惯,常用API等),可以说UniTask是借鉴Task而开发出来的。
以前有一个实验,操作就是点击物体,执行动画,点击物体,执行动画…如此子子孙孙无穷循环,直到地球爆炸(实验结束)。
于是很容易就用UniTask的await把所有操作连成一片,写在一个脚本里,甚至一整个实验就一个脚本。
比如下面:
面板用到的参数全部释放在Inspector上面,代码的话带上注释和空格将近1800行
操作流程的话,写在一个异步方法里,浩浩荡荡写了上千行…写起来倒是比较滑溜了,但是调试和复用的话,就有点…
如果用户说他要【上一步】、【下一步】和【跳步】
思路:把一串流程按照操作逻辑分块,不同的块编一个号,放到一个执行列表里面。
/// <summary> /// 主流程:所有流程连成一片,无法分步执行 /// </summary> /// <returns></returns> public async UniTask Flow() { //第一步 await UniTask.Delay(1000); //... //第二步 await UniTask.Delay(1000); //... //第三步 await UniTask.Delay(1000); //... //... //第N步 await UniTask.Delay(1000); //... }
/// <summary>
/// 步骤信息表:系列化到面板,用于调试和观察,后期执行
/// </summary>
[Header("步骤信息表")]
public List<StepInfo> StepInfosList = new List<StepInfo>();
每一个节点StepInfo的结构如下:
/// <summary> /// 步骤信息Class /// </summary> [Serializable] public class StepInfo { /// <summary> /// 当前序号 /// </summary> [Header("当前步骤的序号")] public int index; /// <summary> /// 步骤的名字 /// </summary> [Header("步骤的名字")] public string name; /// <summary> /// 正常流程 /// </summary> public Func<UniTask> Flow; /// <summary> /// 恢复到【初始状态】的流程 /// </summary> public Action Init; /// <summary> /// 跳到【结束状态】的流程 /// </summary> public Action Final; }
把脚本按照逻辑分成不同的块(步骤),装入到执行步骤列表里
/// <summary> /// 添加步骤:新改的写法——各个步骤单独分开,逐个添加到执行列表里 /// </summary> /// <param name="ctk"></param> /// <returns></returns> public async UniTask AddSteps(CancellationToken ctk) { //第一步 var step = StepInfosList.AddFlow("第一步"); step.Flow = async () => { Debug.Log("Flow() - 第一步"); await UniTask.Delay(1000, cancellationToken: ctk); }; //第二步 step = StepInfosList.AddFlow("第二步"); step.Flow = async () => { Debug.Log("Flow() - 第二步"); await UniTask.Delay(1000, cancellationToken: ctk); }; //第三步 step = StepInfosList.AddFlow("第三步"); step.Flow = async () => { Debug.Log("Flow() - 第三步"); await UniTask.Delay(1000, cancellationToken: ctk); }; //第N步 step = StepInfosList.AddFlow("第N步"); step.Flow = async () => { Debug.Log("Flow() - 第N步"); await UniTask.Delay(1000, cancellationToken: ctk); }; }
/// <summary> /// 给定步骤名字,执行指定的步骤【所谓任意跳步】 /// </summary> /// <param name="taskName"></param> async UniTask RunTask(string taskName) { if (cts.IsCancellationRequested) { Debug.Log("所有的任务已经被取消了!!"); return; } //【1】获取目标步骤信息 var targetStep = StepInfosList.First(x => x.name.Equals(taskName.Trim())); //【2】处理中间步骤的状态 Debug.Log($"#################### 要执行的目标步骤为:targetStep = {targetStep.index} {targetStep.name} currentIndex = {currentIndex} "); if (currentIndex == targetStep.index) //本步骤重新执行:恢复到本步骤初始状态 { Debug.Log($"回到【{targetStep.name}】初始状态"); if (targetStep.Init == null) { Debug.LogWarning($"步骤【{targetStep.index} {targetStep.name}】的Init函数为空,请补全!"); } else { targetStep.Init(); } } if (currentIndex < targetStep.index) //往后跳步:中间步骤的状态快速补足 { StepInfosList .Where(x => (x.index >= currentIndex) && (x.index < targetStep.index)).ToList() .ForEach(s => { Debug.Log($"回到【{s.name}】结束状态"); if (s.Final == null) { Debug.LogWarning($"步骤【{s.index} {s.name}】的Final函数为空,请补全!"); } else { s.Final(); } }); } if (currentIndex > targetStep.index) //往前跳步:中间步骤的状态快速撤销 { StepInfosList .Where(x => (x.index >= targetStep.index) && (x.index <= currentIndex)) .OrderByDescending(x=>x.index).ToList() //注意倒序排列——由后往前执行 .ForEach(s => { Debug.Log($"回到【{s.name}】初始状态"); if (s.Init == null) { Debug.LogWarning($"步骤【{s.index} {s.name}】的Init函数为空,请补全!"); } else { s.Init(); } }); } //【3】执行目标步骤的流程 await targetStep.Flow(); currentIndex = targetStep.index; Debug.Log($"执行步骤:{targetStep.index} {targetStep.name} 执行完毕,当前步骤currentIndex = {currentIndex}!"); }
用异步等待来逐个执行所有的步骤,如下所示:
/// <summary>
/// 执行所有的步骤
/// </summary>
/// <returns></returns>
private async UniTask RunAllTasks()
{
foreach (var step in StepInfosList)
{
Debug.Log($"当前执行的步骤:{step.index} {step.name}");
currentIndex = step.index;
await step.Flow();
}
}
举个不恰当的例子:你在家里玩电脑,然后让你的5岁的娃娃去楼下接妈妈回家。结果你媳妇从地下室坐电梯上来了,结局就是你的娃娃一直没等到他妈妈,结果呢,他呆坐在门口一直等,一直等,天黑也不回家。
问题在哪里:你指派给你小孩接妈妈这个异步操作,没有附加异步取消的令牌。下次你应该对他说:天黑还没等到妈妈的话,你就直接回家吃饭。或者我打电话给你,你就回家。
/// <summary>
/// 异步任务取消的标记
/// </summary>
private CancellationTokenSource cts;
/// <summary>
/// 取消所有的任务
/// </summary>
void CancelAllTasks()
{
cts.Cancel();
}
var cts = new CancellationTokenSource();
cancelButton.onClick.AddListener(() =>
{
cts.Cancel();
});
await UnityWebRequest.Get("http://google.co.jp").SendWebRequest().WithCancellation(cts.Token);
await UniTask.DelayFrame(1000, cancellationToken: cts.Token);
/// <summary>
/// 创建任务,用一个新的【CancellationTokenSource】控制这些异步任务
/// </summary>
/// <returns>cts</returns>
CancellationTokenSource CreatTasks()
{
var cts = new CancellationTokenSource();
StepInfosList.Clear();
AddSteps(cts.Token).Forget();
return cts;
}
//加载步骤流程到执行列表中 Debug.Log($"【1】************加载步骤流程到执行列表中:{Time.realtimeSinceStartup}"); cts = CreatTasks(); //执行一遍所有的任务 Debug.Log($"【2】************执行一遍所有的任务:{Time.realtimeSinceStartup}"); await RunAllTasks(); //等待3秒钟,跳步到第三步执行 Debug.Log($"【3】************等待3秒钟,跳步到第三步执行:{Time.realtimeSinceStartup}"); await UniTask.Delay(3000, cancellationToken: cts.Token); await RunTask("第三步"); //取消所有任务 Debug.Log($"【4】************取消所有任务:{Time.realtimeSinceStartup}"); CancelAllTasks(); //等待3秒钟,跳步到第1步执行 Debug.Log($"【5】************等待3秒钟,跳步到第1步执行:{Time.realtimeSinceStartup}"); await UniTask.Delay(3000, cancellationToken: cts.Token); await RunTask("第一步");
using System; using System.Collections; using System.Collections.Generic; using System.Linq; using System.Threading; using Cysharp.Threading.Tasks; using UnityEngine; /// <summary> /// 步骤信息Class /// </summary> [Serializable] public class StepInfo { /// <summary> /// 当前序号 /// </summary> [Header("当前步骤的序号")] public int index; /// <summary> /// 步骤的名字 /// </summary> [Header("步骤的名字")] public string name; /// <summary> /// 正常流程 /// </summary> public Func<UniTask> Flow; /// <summary> /// 恢复到【初始状态】的流程 /// </summary> public Action Init; /// <summary> /// 跳到【结束状态】的流程 /// </summary> public Action Final; } /// <summary> /// 扩展方法 /// </summary> public static class ExtensionMethods { /// <summary> /// 在步骤列表MySteps中生成一个只包含步骤名的空步骤 /// </summary> /// <param name="MySteps">步骤列表</param> /// <param name="stepName">步骤名字</param> /// <returns></returns> public static StepInfo AddFlow(this List<StepInfo> MySteps, string stepName) { int idx = MySteps.Count == 0 ? 0 : MySteps.Max(x => x.index) + 1; var item = new StepInfo(); item.index = idx; item.name = stepName; item.Flow = null; item.Final = null; item.Init = null; MySteps.Add(item); return item; } } /// <summary> /// 步骤分步,任意跳转:一段操作流程,包含多个步骤,可以任意跳转到某个步骤执行。 /// 点击【上一步】时,需要对当前步骤所操作的内容进行撤销或者状态还原。 /// 点击【下一步】时,需要把跳过的步骤中的状态补足。 /// </summary> public class FlowsDemo : MonoBehaviour { /// <summary> /// 步骤信息表:系列化到面板,用于调试和观察,后期执行 /// </summary> [Header("步骤信息表")] public List<StepInfo> StepInfosList = new List<StepInfo>(); /// <summary> /// 当前执行的步骤 /// </summary> private int currentIndex; /// <summary> /// 异步任务取消的标记 /// </summary> private CancellationTokenSource cts; /// <summary> /// 取消所有的任务 /// </summary> void CancelAllTasks() { cts.Cancel(); } /// <summary> /// 创建任务,用一个新的【CancellationTokenSource】控制这些异步任务 /// </summary> /// <returns>cts</returns> CancellationTokenSource CreatTasks() { var cts = new CancellationTokenSource(); StepInfosList.Clear(); AddSteps(cts.Token).Forget(); return cts; } /// <summary> /// 执行所有的步骤 /// </summary> /// <returns></returns> private async UniTask RunAllTasks() { foreach (var step in StepInfosList) { Debug.Log($"当前执行的步骤:{step.index} {step.name}"); currentIndex = step.index; await step.Flow(); } } /// <summary> /// 主流程:所有流程连成一片,无法分步执行 /// </summary> /// <returns></returns> public async UniTask Flow() { //第一步 await UniTask.Delay(1000); //... //第二步 await UniTask.Delay(1000); //... //第三步 await UniTask.Delay(1000); //... //... //第N步 await UniTask.Delay(1000); //... } /// <summary> /// 添加步骤:新改的写法——各个步骤单独分开,逐个添加到执行列表里 /// </summary> /// <param name="ctk"></param> /// <returns></returns> public async UniTask AddSteps(CancellationToken ctk) { //第一步 var step = StepInfosList.AddFlow("第一步"); step.Flow = async () => { Debug.Log("Flow() - 第一步"); await UniTask.Delay(1000, cancellationToken: ctk); }; step.Init = () => { Debug.Log("Init() - 回到第一步的初始状态");}; step.Final = () => { Debug.Log("Final() - 跳到第一步的结束状态"); }; //第二步 step = StepInfosList.AddFlow("第二步"); step.Flow = async () => { Debug.Log("Flow() - 第二步"); await UniTask.Delay(1000, cancellationToken: ctk); }; step.Init = () => { Debug.Log("Init() - 回到第二步的初始状态"); }; step.Final = () => { Debug.Log("Final() - 跳到第二步的结束状态"); }; //第三步 step = StepInfosList.AddFlow("第三步"); step.Flow = async () => { Debug.Log("Flow() - 第三步"); await UniTask.Delay(1000, cancellationToken: ctk); }; //第N步 step = StepInfosList.AddFlow("第N步"); step.Flow = async () => { Debug.Log("Flow() - 第N步"); await UniTask.Delay(1000, cancellationToken: ctk); }; } /// <summary> /// 给定步骤名字,执行指定的步骤【所谓任意跳步】 /// </summary> /// <param name="taskName"></param> async UniTask RunTask(string taskName) { if (cts.IsCancellationRequested) { Debug.Log("所有的任务已经被取消了!!"); return; } //【1】获取目标步骤信息 var targetStep = StepInfosList.First(x => x.name.Equals(taskName.Trim())); //【2】处理中间步骤的状态 Debug.Log($"#################### 要执行的目标步骤为:targetStep = {targetStep.index} {targetStep.name} currentIndex = {currentIndex} "); if (currentIndex == targetStep.index) //本步骤重新执行:恢复到本步骤初始状态 { Debug.Log($"回到【{targetStep.name}】初始状态"); if (targetStep.Init == null) { Debug.LogWarning($"步骤【{targetStep.index} {targetStep.name}】的Init函数为空,请补全!"); } else { targetStep.Init(); } } if (currentIndex < targetStep.index) //往后跳步:中间步骤的状态快速补足 { StepInfosList .Where(x => (x.index >= currentIndex) && (x.index < targetStep.index)).ToList() .ForEach(s => { Debug.Log($"回到【{s.name}】结束状态"); if (s.Final == null) { Debug.LogWarning($"步骤【{s.index} {s.name}】的Final函数为空,请补全!"); } else { s.Final(); } }); } if (currentIndex > targetStep.index) //往前跳步:中间步骤的状态快速撤销 { StepInfosList .Where(x => (x.index >= targetStep.index) && (x.index <= currentIndex)) .OrderByDescending(x=>x.index).ToList() //注意倒序排列——由后往前执行 .ForEach(s => { Debug.Log($"回到【{s.name}】初始状态"); if (s.Init == null) { Debug.LogWarning($"步骤【{s.index} {s.name}】的Init函数为空,请补全!"); } else { s.Init(); } }); } //【3】执行目标步骤的流程 await targetStep.Flow(); currentIndex = targetStep.index; Debug.Log($"执行步骤:{targetStep.index} {targetStep.name} 执行完毕,当前步骤currentIndex = {currentIndex}!"); } /// <summary> /// 要测试的【步骤名字】 /// </summary> [Header("要执行的【步骤名字】")] public string flowName; #if UNITY_EDITOR [ContextMenu("跳到指定的步骤")] #endif void TestFlow() { RunTask(flowName).Forget(); } #if UNITY_EDITOR [ContextMenu("取消所有任务")] #endif void TestFlow1() { CancelAllTasks(); } #if UNITY_EDITOR [ContextMenu("Demo测试")] #endif void TestFlow3() { Func<UniTask> TestDemo = async () => { //加载步骤流程到执行列表中 Debug.Log($"【1】************加载步骤流程到执行列表中:{Time.realtimeSinceStartup}"); cts = CreatTasks(); //执行一遍所有的任务 Debug.Log($"【2】************执行一遍所有的任务:{Time.realtimeSinceStartup}"); await RunAllTasks(); //等待3秒钟,跳步到第三步执行 Debug.Log($"【3】************等待3秒钟,跳步到第三步执行:{Time.realtimeSinceStartup}"); await UniTask.Delay(3000, cancellationToken: cts.Token); await RunTask("第三步"); //取消所有任务 Debug.Log($"【4】************取消所有任务:{Time.realtimeSinceStartup}"); CancelAllTasks(); //等待3秒钟,跳步到第1步执行 Debug.Log($"【5】************等待3秒钟,跳步到第1步执行:{Time.realtimeSinceStartup}"); await UniTask.Delay(3000, cancellationToken: cts.Token); await RunTask("第一步"); }; Debug.Log($"开始测试:{Time.realtimeSinceStartup}"); TestDemo(); } }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。