用一个异步方法来播放一个动画,正常情况是:动画播放结束时,异步方法宣告结束。那如果我提前取消这个异步任务,那在这个异步方法里面,我要怎么停止播放呢?!
一、播放animation动画的异步实现
- 1、用play播放动画片段
- 2、await一段时间,等动画播放结束
- 3、用stop停止动画播放
二、两种实现方式
1 、纯多任务模式的实现
实现原理:
定义了两个结束的事件(或者Task):
(1)第一个是播放时长到点了
(2)第二个是用户取消了异步任务
(3)用whenAny等待
csharp
/// <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 、手工启动一个循环,每帧检查结束条件
csharp
/// <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、停止【播放动画】的"线程"
csharp
//获取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秒后取消任务(同时停止播放)
五、附录:测试用的代码
为了样例完整性,我把三个脚本并在一个脚本里,请忽略杂乱的代码组织
csharp
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("播放整个动画")]
#endif
void test1()
{
PlayAnim2(anim, 0f, 5f, 1,this.GetCancellationTokenOnDestroy()).Forget();
}
#if UNITY_EDITOR
[ContextMenu("停止测试")]
#endif
void test2()
{
TestPlay().Forget();
}
#if UNITY_EDITOR
[ContextMenu("停止测试2")]
#endif
void 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
}