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)能…...
19c补丁后oracle属主变化,导致不能识别磁盘组
补丁后服务器重启,数据库再次无法启动 ORA01017: invalid username/password; logon denied Oracle 19c 在打上 19.23 或以上补丁版本后,存在与用户组权限相关的问题。具体表现为,Oracle 实例的运行用户(oracle)和集…...
业务系统对接大模型的基础方案:架构设计与关键步骤
业务系统对接大模型:架构设计与关键步骤 在当今数字化转型的浪潮中,大语言模型(LLM)已成为企业提升业务效率和创新能力的关键技术之一。将大模型集成到业务系统中,不仅可以优化用户体验,还能为业务决策提供…...
日语学习-日语知识点小记-构建基础-JLPT-N4阶段(33):にする
日语学习-日语知识点小记-构建基础-JLPT-N4阶段(33):にする 1、前言(1)情况说明(2)工程师的信仰2、知识点(1) にする1,接续:名词+にする2,接续:疑问词+にする3,(A)は(B)にする。(2)復習:(1)复习句子(2)ために & ように(3)そう(4)にする3、…...
AI书签管理工具开发全记录(十九):嵌入资源处理
1.前言 📝 在上一篇文章中,我们完成了书签的导入导出功能。本篇文章我们研究如何处理嵌入资源,方便后续将资源打包到一个可执行文件中。 2.embed介绍 🎯 Go 1.16 引入了革命性的 embed 包,彻底改变了静态资源管理的…...
GC1808高性能24位立体声音频ADC芯片解析
1. 芯片概述 GC1808是一款24位立体声音频模数转换器(ADC),支持8kHz~96kHz采样率,集成Δ-Σ调制器、数字抗混叠滤波器和高通滤波器,适用于高保真音频采集场景。 2. 核心特性 高精度:24位分辨率,…...
Docker 本地安装 mysql 数据库
Docker: Accelerated Container Application Development 下载对应操作系统版本的 docker ;并安装。 基础操作不再赘述。 打开 macOS 终端,开始 docker 安装mysql之旅 第一步 docker search mysql 》〉docker search mysql NAME DE…...
PHP 8.5 即将发布:管道操作符、强力调试
前不久,PHP宣布了即将在 2025 年 11 月 20 日 正式发布的 PHP 8.5!作为 PHP 语言的又一次重要迭代,PHP 8.5 承诺带来一系列旨在提升代码可读性、健壮性以及开发者效率的改进。而更令人兴奋的是,借助强大的本地开发环境 ServBay&am…...
快速排序算法改进:随机快排-荷兰国旗划分详解
随机快速排序-荷兰国旗划分算法详解 一、基础知识回顾1.1 快速排序简介1.2 荷兰国旗问题 二、随机快排 - 荷兰国旗划分原理2.1 随机化枢轴选择2.2 荷兰国旗划分过程2.3 结合随机快排与荷兰国旗划分 三、代码实现3.1 Python实现3.2 Java实现3.3 C实现 四、性能分析4.1 时间复杂度…...
路由基础-路由表
本篇将会向读者介绍路由的基本概念。 前言 在一个典型的数据通信网络中,往往存在多个不同的IP网段,数据在不同的IP网段之间交互是需要借助三层设备的,这些设备具备路由能力,能够实现数据的跨网段转发。 路由是数据通信网络中最基…...
qt+vs Generated File下的moc_和ui_文件丢失导致 error LNK2001
qt 5.9.7 vs2013 qt add-in 2.3.2 起因是添加一个新的控件类,直接把源文件拖进VS的项目里,然后VS卡住十秒,然后编译就报一堆 error LNK2001 一看项目的Generated Files下的moc_和ui_文件丢失了一部分,导致编译的时候找不到了。因…...
