Unity中的异步编程【7】——在一个异步方法里播放了animation动画,取消任务时,如何停止动画播放
用一个异步方法来播放一个动画,正常情况是:动画播放结束时,异步方法宣告结束。那如果我提前取消这个异步任务,那在这个异步方法里面,我要怎么停止播放呢?!
一、播放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
}
相关文章:

Unity中的异步编程【7】——在一个异步方法里播放了animation动画,取消任务时,如何停止动画播放
用一个异步方法来播放一个动画,正常情况是:动画播放结束时,异步方法宣告结束。那如果我提前取消这个异步任务,那在这个异步方法里面,我要怎么停止播放呢?! 一、播放animation动画的异步实现 1…...

vue3中ref和reactive联系与区别以及如何选择
vue3中ref和reactive区别与联系 区别 1、ref既可定义基本数据类型,也可以定义引用数据类型,reactive只能定义应用数据类型 2、ref在js中取响应值需要使用 .value,而reactive则直接取用既可 3、ref定义的对象通过.value重新分配新对象时依旧…...

面试宝典之spring框架常见面试题
F1、类的反射机制有啥用? (1)增加程序的灵活性,可扩展性,动态创建对象。 (2)框架必备,任何框架的封装都要用反射。(框架的灵魂) F2、获取Class对象的三种方…...
建筑垃圾处理行业分析:正在被越来越广泛的运用
建筑垃圾处理,是将固体废弃物作为再生资源重新利用的一种方式。建筑垃圾是在对建筑物实施新建、改建、扩建或者是拆除过程中产生的固体废弃物。建筑垃圾一般可分为建设废物、拆除垃圾及装修垃圾。因此建筑垃圾处理行业可以分为建设废物处理、拆除垃圾处理、装修垃圾…...
【DIY summaries on Linux】
DIY Linux summaries 1) difference between ways of creation file and edit files1.1) echoecho talk to yourself touch 1) difference between ways of creation file and edit files 1.1) echo ###) > echo talk to yourself touch...

Redis(概述、应用场景、线程模式、数据持久化、数据一致、事务、集群、哨兵、key过期策略、缓存穿透、击穿、雪崩)
目录 Redis概述 应用场景 Redis的线程模式 数据持久化 1.Rdb(Redis DataBase) 2.Aof(Append Only File) mysql与redis保持数据一致 redis事务 主从复制(Redis集群) 哨兵模式 key过期策略 缓存穿透、击穿、…...

ospf-gre隧道小练习
全网可达,R5路由表没有其他路由器的路由条目 注:每个路由器都添加了自己的环回,如R1就是1.1.1.1 R1可以分别ping通与R2,R3,R4之间的隧道 R1路由表上有所有路由器环回的路由条目 R5路由表上没有其他路由器的路由条目 实现代码: 首先将各个接口IP配好 边上3个路由器:[R6][R7][R…...
C++入门【29-C++ 把引用作为返回值】
通过使用引用来替代指针,会使 C 程序更容易阅读和维护。C 函数可以返回一个引用,方式与返回一个指针类似。 当函数返回一个引用时,则返回一个指向返回值的隐式指针。这样,函数就可以放在赋值语句的左边。例如,请看下面…...

基于Java SSM框架实现企业车辆管理系统项目【项目源码】
基于java的SSM框架实现企业车辆管理系统演示 JSP技术 JSP技术本身是一种脚本语言,但它的功能是十分强大的,因为它可以使用所有的JAVA类。当它与JavaBeans 类进行结合时,它可以使显示逻辑和内容分开,这就极大的方便了运动员的需求…...

MyBatis的配置及简单使用
1.配置myBatis 1.myBatis的作用 MyBatis 是一个开源的持久层框架,它的主要作用是简化数据库操作,使得开发者能够更方便地与数据库进行交互。 MyBatis 允许开发者使用简单的 XML 或注解配置 SQL 映射,从而实现数据库操作,而不需要…...

【UE Niagara学习笔记】07 - 火焰的热变形效果
目录 效果 步骤 一、创建热变形材质 二、添加新的发射器 2.1 设置粒子材质 2.2 设置粒子初始大小 2.3 设置粒子持续生成 三、修改材质 四、设置粒子效果 在上一篇博客(【UE Niagara学习笔记】06 - 制作火焰喷射过程中飞舞的火星)的基础上继续…...
深度学习模型中 argparse 模块Python 脚本的部分参数解读
尤其注意下面这句中的action"store_true"的作用解析: --resume_ckpt 是一个标志参数,它不需要附加值。如果将该参数作为命令行参数传递给脚本,则 resume_ckpt 的值将被设置为 True。action"store_true" 意味着如果在命令…...

在win10和Linux上配置SSH 无密码登录
文章目录 一、用途二、在本地机器上使用ssh-keygen产生公钥私钥对1)在Linux (或macOS) 上产生SSH公私钥的方法2)在win10上产生SSH公私钥的方法a)检查windows 本地是否安装有sshb)在本地生成SSH密钥对(公钥和私钥&#…...

【VUE】无法加载文件 \node\vue.ps1,因为在此系统上禁止运行脚本。问题解决
问题描述 在VS Code中输入vue create -p dcloudio/uni-preset-vue uniapp-demo命令时报错 无法加载文件 D:\address\node\vue.ps1,因为在此系统上禁止运行脚本。有关详细信息,请参阅 https:/go.microsoft.com/fwlink/?LinkID135170 中的 about_Executi…...
C //练习 4-12 运用printd函数的设计思想编写一个递归版本的itoa函数,即通过递归调用把整数转换为字符串。
C程序设计语言 (第二版) 练习 4-12 练习 4-12 运用printd函数的设计思想编写一个递归版本的itoa函数,即通过递归调用把整数转换为字符串。 注意:代码在win32控制台运行,在不同的IDE环境下,有部分可能需要…...

【python playwright 安装及验证】
python playwright pip install playwright pip install playwright -i http://mirrors.aliyun.com/pypi/simple/ playwright codegen -o script.py -b chromium --ignore-https-errors --viewport-size “2560,1440” --proxy-server “http://100.8.64.8:60497” https://w…...

极简云源码已经开源
源码介绍 极简云已经开源 解绑卡密 查询卡密 总体来说还是很完善的 对接例子网盘里有 用户注册需要配置邮箱 上网页QQ邮箱标准版开启SMTP 然后生成授权码 后台发信邮箱里填就对了 实在不会配置邮箱的 可以下载网盘里的reg.php 把reg.php上传源码里的user目录 之后注册就不需要…...
Shell中cp和mv命令说明
在Shell(如Bash)中,cp 和 mv 是两个常用的命令,用于处理文件和目录。它们的用法和作用如下: cp 命令 作用:cp(copy的缩写)用于复制文件或目录。基本用法:cp [选项] 源文…...
cssip 第11章 : 安全网络架构和保护网络组件
11.1 OSI模型 协议:定义数据如何通过网络介质传输。 11.1.1 OSI模型的历史 开发 OSI 协议是为给所有计算机系统建立通用的通信结构或标准。 OSI模型表示:应用层、表示层、会话层、传输层、网络层、数据链路层、物理层。 11.1.3 封装/解…...
ITSS申报条件以及评审方式
01、四级申报基本条件: (1)具有独立法人地位; (2)已按照《运维服务能力成熟度》四级特征和关键指标建立了运维服务能力体系,且已有效运行三个月以上; (3)能…...

eNSP-Cloud(实现本地电脑与eNSP内设备之间通信)
说明: 想象一下,你正在用eNSP搭建一个虚拟的网络世界,里面有虚拟的路由器、交换机、电脑(PC)等等。这些设备都在你的电脑里面“运行”,它们之间可以互相通信,就像一个封闭的小王国。 但是&#…...

深入浅出Asp.Net Core MVC应用开发系列-AspNetCore中的日志记录
ASP.NET Core 是一个跨平台的开源框架,用于在 Windows、macOS 或 Linux 上生成基于云的新式 Web 应用。 ASP.NET Core 中的日志记录 .NET 通过 ILogger API 支持高性能结构化日志记录,以帮助监视应用程序行为和诊断问题。 可以通过配置不同的记录提供程…...

【人工智能】神经网络的优化器optimizer(二):Adagrad自适应学习率优化器
一.自适应梯度算法Adagrad概述 Adagrad(Adaptive Gradient Algorithm)是一种自适应学习率的优化算法,由Duchi等人在2011年提出。其核心思想是针对不同参数自动调整学习率,适合处理稀疏数据和不同参数梯度差异较大的场景。Adagrad通…...

大型活动交通拥堵治理的视觉算法应用
大型活动下智慧交通的视觉分析应用 一、背景与挑战 大型活动(如演唱会、马拉松赛事、高考中考等)期间,城市交通面临瞬时人流车流激增、传统摄像头模糊、交通拥堵识别滞后等问题。以演唱会为例,暖城商圈曾因观众集中离场导致周边…...
Golang dig框架与GraphQL的完美结合
将 Go 的 Dig 依赖注入框架与 GraphQL 结合使用,可以显著提升应用程序的可维护性、可测试性以及灵活性。 Dig 是一个强大的依赖注入容器,能够帮助开发者更好地管理复杂的依赖关系,而 GraphQL 则是一种用于 API 的查询语言,能够提…...
渲染学进阶内容——模型
最近在写模组的时候发现渲染器里面离不开模型的定义,在渲染的第二篇文章中简单的讲解了一下关于模型部分的内容,其实不管是方块还是方块实体,都离不开模型的内容 🧱 一、CubeListBuilder 功能解析 CubeListBuilder 是 Minecraft Java 版模型系统的核心构建器,用于动态创…...

【项目实战】通过多模态+LangGraph实现PPT生成助手
PPT自动生成系统 基于LangGraph的PPT自动生成系统,可以将Markdown文档自动转换为PPT演示文稿。 功能特点 Markdown解析:自动解析Markdown文档结构PPT模板分析:分析PPT模板的布局和风格智能布局决策:匹配内容与合适的PPT布局自动…...
数据链路层的主要功能是什么
数据链路层(OSI模型第2层)的核心功能是在相邻网络节点(如交换机、主机)间提供可靠的数据帧传输服务,主要职责包括: 🔑 核心功能详解: 帧封装与解封装 封装: 将网络层下发…...

从零实现STL哈希容器:unordered_map/unordered_set封装详解
本篇文章是对C学习的STL哈希容器自主实现部分的学习分享 希望也能为你带来些帮助~ 那咱们废话不多说,直接开始吧! 一、源码结构分析 1. SGISTL30实现剖析 // hash_set核心结构 template <class Value, class HashFcn, ...> class hash_set {ty…...

令牌桶 滑动窗口->限流 分布式信号量->限并发的原理 lua脚本分析介绍
文章目录 前言限流限制并发的实际理解限流令牌桶代码实现结果分析令牌桶lua的模拟实现原理总结: 滑动窗口代码实现结果分析lua脚本原理解析 限并发分布式信号量代码实现结果分析lua脚本实现原理 双注解去实现限流 并发结果分析: 实际业务去理解体会统一注…...