用一个异步方法来播放一个动画,正常情况是:动画播放结束时,异步方法宣告结束。那如果我提前取消这个异步任务,那在这个异步方法里面,我要怎么停止播放呢?!
一、播放animation动画的异步实现
- 1、用play播放动画片段
- 2、await一段时间,等动画播放结束
- 3、用stop停止动画播放
二、两种实现方式
1 、纯多任务模式的实现
实现原理:
定义了两个结束的事件(或者Task):
(1)第一个是播放时长到点了
(2)第二个是用户取消了异步任务
(3)用whenAny等待
/// <summary>/// 等待一个动画播放完毕/// 中间如果任务被取消,则停止播放动画/// </summary>/// <param name="Anim"></param>/// <param name="startTime"></param>/// <param name="endTime"></param>/// <param name="speed"></param>/// <param name="ctk">任务取消标志</param>/// <returns></returns>public static async UniTask<bool> PlayAnim(Animation Anim, float startTime, float endTime, float speed, CancellationToken ctk){Debug.Log($"当前Time.timeScale = {Time.timeScale}");float t = (endTime - startTime) * Time.timeScale; //考虑到动画时间倍率Debug.Log($"动画的时长为:{t}秒");Anim[Anim.clip.name].time = startTime;//跳过第几帧Anim[Anim.clip.name].speed = speed;Anim.Play(Anim.clip.name); //Play()//如果时间到点,结束,并停止动画Func<UniTask> timeFn = async () =>{ await UniTask.Delay(TimeSpan.FromSeconds(t), cancellationToken: ctk);Anim.Stop();};//用户取消任务,结束,并停止动画Func<UniTask> cancelFn = async () =>{Debug.Log("开始执行cancelFn的循环:");while (true){Debug.Log($"ctk.IsCancellationRequested = {ctk.IsCancellationRequested}");if (ctk.IsCancellationRequested){Debug.Log($"任务取消:{ctk.IsCancellationRequested}");Anim.Stop();break;};await UniTask.Yield(); //注意,这里不能随意加ctk,不然不能停,直接跳出了//await UniTask.Yield(ctk); }Debug.Log("结束cancelFn的循环");};//等待结束var idx = await UniTask.WhenAny(timeFn(), cancelFn()).AttachExternalCancellation(ctk);Debug.Log($"任务结束:结束方式为:{idx} 备注:0 = 动画播放结束,1 = 用户取消任务");return true;}
2 、手工启动一个循环,每帧检查结束条件
/// <summary>/// 等待一个动画播放完毕/// 中间如果任务被取消,则停止播放动画/// 改进了结束的判断方式/// </summary>/// <param name="Anim"></param>/// <param name="startTime"></param>/// <param name="endTime"></param>/// <param name="speed"></param>/// <param name="ctk">任务取消标志</param>/// <returns></returns>public static async UniTask<bool> PlayAnim2(Animation Anim, float startTime, float endTime, float speed, CancellationToken ctk){Debug.Log($"当前Time.timeScale = {Time.timeScale}");float t = (endTime - startTime) * Time.timeScale; //考虑到动画时间倍率float elapse = 0f;Debug.Log($"动画的时长为:{t}秒");Anim[Anim.clip.name].time = startTime;//跳过第几帧Anim[Anim.clip.name].speed = speed;Anim.Play(Anim.clip.name); //Play()//每帧进行结束判断while (true){elapse += Time.deltaTime; //任务被取消Debug.Log($"ctk.IsCancellationRequested = {ctk.IsCancellationRequested}");if (ctk.IsCancellationRequested){Debug.Log($"任务取消:{ctk.IsCancellationRequested}");//Anim.Stop();break;};//动画播放完毕if (elapse >= t){break;}await UniTask.Yield(); //注意,这里不能随意加ctk,不然不能停,直接return了//await UniTask.Yield(ctk); }Anim.Stop();return true;}
三、测试流程
- 1、启动一个“线程(异步任务)”——播放动画
- 2、等待2秒后,停止任务
- 3、停止【播放动画】的“线程”
//获取animation组件
if (anim == null) anim = this.GetComponent<Animation>();
var cti = TaskSignal.CreatCts();//启动一个“线程”——播放动画
PlayAnim2(anim, 0f, 5f, 1, cti.cts.Token).Forget();//等待2秒后,停止任务
await UniTask.Delay(1500);Debug.Log("停止任务......");
//停止【播放动画】的“线程”
TaskSignal.CancelTask(cti.id);
四、效果
1、等待全部播放完毕
2、播放2秒后取消任务(同时停止播放)
五、附录:测试用的代码
为了样例完整性,我把三个脚本并在一个脚本里,请忽略杂乱的代码组织
using System.Collections.Generic;
using System.Threading;
using Cysharp.Threading.Tasks;
using UnityEngine;
using System;
using System.Linq;public class TestPlayAnimation : MonoBehaviour
{public Animation anim;private async UniTask TestPlay(){//获取animation组件if(anim == null) anim = this.GetComponent<Animation>();var cti = TaskSignal.CreatCts();//启动一个“线程”——播放动画PlayAnim(anim, 0f, 5f, 1,cti.cts.Token).Forget();//等待2秒后,停止任务await UniTask.Delay(1500);Debug.Log("停止任务......");//停止【播放动画】的“线程”TaskSignal.CancelTask(cti.id);}private async UniTask TestPlay2(){//获取animation组件if (anim == null) anim = this.GetComponent<Animation>();var cti = TaskSignal.CreatCts();//启动一个“线程”——播放动画PlayAnim2(anim, 0f, 5f, 1, cti.cts.Token).Forget();//等待2秒后,停止任务await UniTask.Delay(1500);Debug.Log("停止任务......");//停止【播放动画】的“线程”TaskSignal.CancelTask(cti.id);}#if UNITY_EDITOR[ContextMenu("播放整个动画")]
#endifvoid test1(){PlayAnim2(anim, 0f, 5f, 1,this.GetCancellationTokenOnDestroy()).Forget();}#if UNITY_EDITOR[ContextMenu("停止测试")]
#endifvoid test2(){TestPlay().Forget();}#if UNITY_EDITOR[ContextMenu("停止测试2")]
#endifvoid test3(){TestPlay2().Forget();}#region =================用到的异步方法======================= /// <summary>/// 等待一个动画播放完毕/// 中间如果任务被取消,则停止播放动画/// </summary>/// <param name="Anim"></param>/// <param name="startTime"></param>/// <param name="endTime"></param>/// <param name="speed"></param>/// <param name="ctk">任务取消标志</param>/// <returns></returns>public static async UniTask<bool> PlayAnim(Animation Anim, float startTime, float endTime, float speed, CancellationToken ctk){Debug.Log($"当前Time.timeScale = {Time.timeScale}");float t = (endTime - startTime) * Time.timeScale; //考虑到动画时间倍率Debug.Log($"动画的时长为:{t}秒");Anim[Anim.clip.name].time = startTime;//跳过第几帧Anim[Anim.clip.name].speed = speed;Anim.Play(Anim.clip.name); //Play()//如果时间到点,结束,并停止动画Func<UniTask> timeFn = async () =>{await UniTask.Delay(TimeSpan.FromSeconds(t), cancellationToken: ctk);Anim.Stop();};//用户取消任务,结束,并停止动画Func<UniTask> cancelFn = async () =>{Debug.Log("开始执行cancelFn的循环:");while (true){//Debug.Log($"ctk.IsCancellationRequested = {ctk.IsCancellationRequested}");if (ctk.IsCancellationRequested){Debug.Log($"任务取消:{ctk.IsCancellationRequested}");Anim.Stop();break;};await UniTask.Yield(); //注意,这里不能随意加ctk,不然不能停,直接跳出了//await UniTask.Yield(ctk); }Debug.Log("结束cancelFn的循环");};//等待结束var idx = await UniTask.WhenAny(timeFn(), cancelFn()).AttachExternalCancellation(ctk);Debug.Log($"任务结束:结束方式为:{idx} 备注:0 = 动画播放结束,1 = 用户取消任务");return true;}/// <summary>/// 等待一个动画播放完毕/// 中间如果任务被取消,则停止播放动画/// 改进了结束的判断方式/// </summary>/// <param name="Anim"></param>/// <param name="startTime"></param>/// <param name="endTime"></param>/// <param name="speed"></param>/// <param name="ctk">任务取消标志</param>/// <returns></returns>public static async UniTask<bool> PlayAnim2(Animation Anim, float startTime, float endTime, float speed, CancellationToken ctk){Debug.Log($"当前Time.timeScale = {Time.timeScale}");float t = (endTime - startTime) * Time.timeScale; //考虑到动画时间倍率float elapse = 0f;Debug.Log($"动画的时长为:{t}秒");Anim[Anim.clip.name].time = startTime;//跳过第几帧Anim[Anim.clip.name].speed = speed;Anim.Play(Anim.clip.name); //Play()//每帧进行结束判断while (true){elapse += Time.deltaTime;//任务被取消//Debug.Log($"ctk.IsCancellationRequested = {ctk.IsCancellationRequested}");if (ctk.IsCancellationRequested){Debug.Log($"任务取消:{ctk.IsCancellationRequested}");break;};//动画播放完毕if (elapse >= t){break;}await UniTask.Yield(); //注意,这里不能随意加ctk,不然不能停,直接return了//await UniTask.Yield(ctk); }Anim.Stop();return true;}#endregion#region ===================异步任务管理脚本===============/// <summary>/// 任务管理/// </summary>public static class TaskSignal{/// 任务信息/// <summary>/// </summary>[Serializable]public class CtsInfo{/// <summary>/// 任务id/// </summary>[SerializeField] public int id;/// <summary>/// cst实例/// </summary>[SerializeField] public CancellationTokenSource cts;}/// <summary>/// 任务池子/// </summary>public static List<CtsInfo> ctsInfos = new List<CtsInfo>();/// <summary>/// 任务编号【自增】/// </summary>private static int id = 0;/// <summary>/// 创建一个任务/// </summary>/// <returns></returns>public static CtsInfo CreatCts(){var cts = new CancellationTokenSource();var ci = new CtsInfo { cts = cts, id = id };id++;ctsInfos.Add(ci);return ci;}/// <summary>/// 取消所有的任务/// </summary>public static void CancelAllTask(){Debug.Log($"开始执行:取消所有的任务CancelAllTask()");ctsInfos.ForEach(ci =>{Debug.Log($"CancelAllTask() : cts总数量为:{ctsInfos.Count}");try{Debug.Log($"ci.id = {ci.id},取消前 ci.cts = {ci.cts.IsCancellationRequested}");if (ci.cts.IsCancellationRequested == false){Debug.Log("开始执行ci.cts.Cancel()");ci.cts.Cancel();Debug.Log("执行完毕ci.cts.Cancel()");}else{//Debug.Log("ci.cts已经取消了");}Debug.Log($"ci.id = {ci.id},取消后 ci.cts = {ci.cts.IsCancellationRequested}");}catch (Exception e){Debug.Log($"TaskSingol.CancelAllTask():取消任务时报错:{e.Message}");}});Debug.Log($"结束执行:取消所有的任务CancelAllTask()");}/// <summary>/// 取消所有的任务/// </summary>public static void CancelAllTask10(){ctsInfos.ForEach(ci =>{if (ci.cts.Token.IsCancellationRequested == false) // if (ci.cts.IsCancellationRequested == false){ci.cts.Cancel();Debug.Log($"取消了任务:index = {ci.id}");}else{//Debug.Log("ci.cts已经取消了");}});}/// <summary>/// 取消指定的任务/// </summary>public static void CancelTask(int id){ctsInfos.Where(ci => ci.id == id).ToList().ForEach(ci => ci.cts.Cancel());}}#endregion
}