当前位置: 首页 > news >正文

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动画,取消任务时,如何停止动画播放

用一个异步方法来播放一个动画&#xff0c;正常情况是&#xff1a;动画播放结束时&#xff0c;异步方法宣告结束。那如果我提前取消这个异步任务&#xff0c;那在这个异步方法里面&#xff0c;我要怎么停止播放呢&#xff1f;&#xff01; 一、播放animation动画的异步实现 1…...

vue3中ref和reactive联系与区别以及如何选择

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

面试宝典之spring框架常见面试题

F1、类的反射机制有啥用&#xff1f; &#xff08;1&#xff09;增加程序的灵活性&#xff0c;可扩展性&#xff0c;动态创建对象。 &#xff08;2&#xff09;框架必备&#xff0c;任何框架的封装都要用反射。&#xff08;框架的灵魂&#xff09; F2、获取Class对象的三种方…...

建筑垃圾处理行业分析:正在被越来越广泛的运用

建筑垃圾处理&#xff0c;是将固体废弃物作为再生资源重新利用的一种方式。建筑垃圾是在对建筑物实施新建、改建、扩建或者是拆除过程中产生的固体废弃物。建筑垃圾一般可分为建设废物、拆除垃圾及装修垃圾。因此建筑垃圾处理行业可以分为建设废物处理、拆除垃圾处理、装修垃圾…...

【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&#xff08;Redis DataBase&#xff09; 2.Aof&#xff08;Append Only File&#xff09; mysql与redis保持数据一致 redis事务 主从复制&#xff08;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++ 把引用作为返回值】

通过使用引用来替代指针&#xff0c;会使 C 程序更容易阅读和维护。C 函数可以返回一个引用&#xff0c;方式与返回一个指针类似。 当函数返回一个引用时&#xff0c;则返回一个指向返回值的隐式指针。这样&#xff0c;函数就可以放在赋值语句的左边。例如&#xff0c;请看下面…...

基于Java SSM框架实现企业车辆管理系统项目【项目源码】

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

MyBatis的配置及简单使用

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

【UE Niagara学习笔记】07 - 火焰的热变形效果

目录 效果 步骤 一、创建热变形材质 二、添加新的发射器 2.1 设置粒子材质 2.2 设置粒子初始大小 2.3 设置粒子持续生成 三、修改材质 四、设置粒子效果 在上一篇博客&#xff08;【UE Niagara学习笔记】06 - 制作火焰喷射过程中飞舞的火星&#xff09;的基础上继续…...

深度学习模型中 argparse 模块Python 脚本的部分参数解读

尤其注意下面这句中的action"store_true"的作用解析&#xff1a; --resume_ckpt 是一个标志参数&#xff0c;它不需要附加值。如果将该参数作为命令行参数传递给脚本&#xff0c;则 resume_ckpt 的值将被设置为 True。action"store_true" 意味着如果在命令…...

在win10和Linux上配置SSH 无密码登录

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

【VUE】无法加载文件 \node\vue.ps1,因为在此系统上禁止运行脚本。问题解决

问题描述 在VS Code中输入vue create -p dcloudio/uni-preset-vue uniapp-demo命令时报错 无法加载文件 D:\address\node\vue.ps1&#xff0c;因为在此系统上禁止运行脚本。有关详细信息&#xff0c;请参阅 https:/go.microsoft.com/fwlink/?LinkID135170 中的 about_Executi…...

C //练习 4-12 运用printd函数的设计思想编写一个递归版本的itoa函数,即通过递归调用把整数转换为字符串。

C程序设计语言 &#xff08;第二版&#xff09; 练习 4-12 练习 4-12 运用printd函数的设计思想编写一个递归版本的itoa函数&#xff0c;即通过递归调用把整数转换为字符串。 注意&#xff1a;代码在win32控制台运行&#xff0c;在不同的IDE环境下&#xff0c;有部分可能需要…...

【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&#xff08;如Bash&#xff09;中&#xff0c;cp 和 mv 是两个常用的命令&#xff0c;用于处理文件和目录。它们的用法和作用如下&#xff1a; cp 命令 作用&#xff1a;cp&#xff08;copy的缩写&#xff09;用于复制文件或目录。基本用法&#xff1a;cp [选项] 源文…...

cssip 第11章 : 安全网络架构和保护网络组件

11.1 OSI模型 协议&#xff1a;定义数据如何通过网络介质传输。 11.1.1 OSI模型的历史 开发 OSI 协议是为给所有计算机系统建立通用的通信结构或标准。 OSI模型表示&#xff1a;应用层、表示层、会话层、传输层、网络层、数据链路层、物理层。 11.1.3 封装/解…...

ITSS申报条件以及评审方式

01、四级申报基本条件&#xff1a; &#xff08;1&#xff09;具有独立法人地位&#xff1b; &#xff08;2&#xff09;已按照《运维服务能力成熟度》四级特征和关键指标建立了运维服务能力体系&#xff0c;且已有效运行三个月以上&#xff1b; &#xff08;3&#xff09;能…...

linux之kylin系统nginx的安装

一、nginx的作用 1.可做高性能的web服务器 直接处理静态资源&#xff08;HTML/CSS/图片等&#xff09;&#xff0c;响应速度远超传统服务器类似apache支持高并发连接 2.反向代理服务器 隐藏后端服务器IP地址&#xff0c;提高安全性 3.负载均衡服务器 支持多种策略分发流量…...

QMC5883L的驱动

简介 本篇文章的代码已经上传到了github上面&#xff0c;开源代码 作为一个电子罗盘模块&#xff0c;我们可以通过I2C从中获取偏航角yaw&#xff0c;相对于六轴陀螺仪的yaw&#xff0c;qmc5883l几乎不会零飘并且成本较低。 参考资料 QMC5883L磁场传感器驱动 QMC5883L磁力计…...

让AI看见世界:MCP协议与服务器的工作原理

让AI看见世界&#xff1a;MCP协议与服务器的工作原理 MCP&#xff08;Model Context Protocol&#xff09;是一种创新的通信协议&#xff0c;旨在让大型语言模型能够安全、高效地与外部资源进行交互。在AI技术快速发展的今天&#xff0c;MCP正成为连接AI与现实世界的重要桥梁。…...

企业如何增强终端安全?

在数字化转型加速的今天&#xff0c;企业的业务运行越来越依赖于终端设备。从员工的笔记本电脑、智能手机&#xff0c;到工厂里的物联网设备、智能传感器&#xff0c;这些终端构成了企业与外部世界连接的 “神经末梢”。然而&#xff0c;随着远程办公的常态化和设备接入的爆炸式…...

【电力电子】基于STM32F103C8T6单片机双极性SPWM逆变(硬件篇)

本项目是基于 STM32F103C8T6 微控制器的 SPWM(正弦脉宽调制)电源模块,能够生成可调频率和幅值的正弦波交流电源输出。该项目适用于逆变器、UPS电源、变频器等应用场景。 供电电源 输入电压采集 上图为本设计的电源电路,图中 D1 为二极管, 其目的是防止正负极电源反接, …...

提升移动端网页调试效率:WebDebugX 与常见工具组合实践

在日常移动端开发中&#xff0c;网页调试始终是一个高频但又极具挑战的环节。尤其在面对 iOS 与 Android 的混合技术栈、各种设备差异化行为时&#xff0c;开发者迫切需要一套高效、可靠且跨平台的调试方案。过去&#xff0c;我们或多或少使用过 Chrome DevTools、Remote Debug…...

Leetcode33( 搜索旋转排序数组)

题目表述 整数数组 nums 按升序排列&#xff0c;数组中的值 互不相同 。 在传递给函数之前&#xff0c;nums 在预先未知的某个下标 k&#xff08;0 < k < nums.length&#xff09;上进行了 旋转&#xff0c;使数组变为 [nums[k], nums[k1], …, nums[n-1], nums[0], nu…...

在鸿蒙HarmonyOS 5中使用DevEco Studio实现指南针功能

指南针功能是许多位置服务应用的基础功能之一。下面我将详细介绍如何在HarmonyOS 5中使用DevEco Studio实现指南针功能。 1. 开发环境准备 确保已安装DevEco Studio 3.1或更高版本确保项目使用的是HarmonyOS 5.0 SDK在项目的module.json5中配置必要的权限 2. 权限配置 在mo…...

ui框架-文件列表展示

ui框架-文件列表展示 介绍 UI框架的文件列表展示组件&#xff0c;可以展示文件夹&#xff0c;支持列表展示和图标展示模式。组件提供了丰富的功能和可配置选项&#xff0c;适用于文件管理、文件上传等场景。 功能特性 支持列表模式和网格模式的切换展示支持文件和文件夹的层…...

Java详解LeetCode 热题 100(26):LeetCode 142. 环形链表 II(Linked List Cycle II)详解

文章目录 1. 题目描述1.1 链表节点定义 2. 理解题目2.1 问题可视化2.2 核心挑战 3. 解法一&#xff1a;HashSet 标记访问法3.1 算法思路3.2 Java代码实现3.3 详细执行过程演示3.4 执行结果示例3.5 复杂度分析3.6 优缺点分析 4. 解法二&#xff1a;Floyd 快慢指针法&#xff08;…...