Unity中的异步编程【7】——在一个异步方法里播放了animation动画,取消任务时,如何停止动画播放

news2025/4/21 20:46:36

用一个异步方法来播放一个动画,正常情况是:动画播放结束时,异步方法宣告结束。那如果我提前取消这个异步任务,那在这个异步方法里面,我要怎么停止播放呢?!

一、播放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("播放整个动画")]
#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
}





本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1382999.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

医疗器械网络安全风险评定CVSS打分

为了完成医疗器械软件的网络安全风险评定相关文档&#xff0c;需要进行CVSS评分&#xff0c;这个评分对于第一次做的人来说感觉还是有些迷惑的&#xff0c;查了一些资料&#xff0c;留作参考。 CVSS 指的是 Common Vulnerability Scoring System&#xff0c;即通用漏洞评分系统…

七、HorizontalPodAutoscaler(HPA)

目录 一、HPA概述&#xff1a; 二、HPA工作机制&#xff1a; 三、HPA流程: 四、HPA API对象: 五、示例&#xff1a; 1、基于CPU的HPA 2、常见问题&#xff1a; 3、基于内存的HPA 一、HPA概述&#xff1a; Horizontal Pod Autoscaler&#xff0c;中文就是水平自动伸缩可…

JUC02同步和锁

同步&锁 相关笔记&#xff1a;www.zgtsky.top 临界区 临界资源&#xff1a;一次仅允许一个进程使用的资源成为临界资源 临界区&#xff1a;访问临界资源的代码块 竞态条件&#xff1a;多个线程在临界区内执行&#xff0c;由于代码的执行序列不同而导致结果无法预测&am…

mysql清空并重置自动递增初始值

需求&#xff1a;当上新项目时&#xff0c;测试环境数据库导出来的表id字段一般都有很大的初始递增值了&#xff0c;需要重置一下 先上代码&#xff1a; -- 查看当前自动递增值 SHOW CREATE TABLE table_name; -- 重建自动递增索引&#xff08;可选&#xff09; ALTER TABLE t…

初学者的基本 Python 面试问题和答案

文章目录 专栏导读1、什么是Python&#xff1f;列出 Python 在技术领域的一些流行应用。2、在目前场景下使用Python语言作为工具有什么好处&#xff1f;3、Python是编译型语言还是解释型语言&#xff1f;4、Python 中的“#”符号有什么作用&#xff1f;5、可变数据类型和不可变…

【深度学习:Micro-Models】用于标记图像和视频的微模型简介

【深度学习&#xff1a;Micro-Models】用于标记图像和视频的微模型简介 微模型&#xff1a;起源故事微模型到底是什么&#xff1f;更详细地解释微观模型&#xff1a;一维标签蝙蝠侠效率 在计算机视觉项目中使用微模型的额外好处面向数据的编程 在本文中&#xff0c;我们将介绍 …

qt5.14.2配置opencv4.5.5

使用环境&#xff1a;windows&#xff0c;opencv4.5.5&#xff0c;qt5.14.2&#xff0c;msvc编译器 这里的opencv文件是已经编译好了&#xff0c;在qt工程中配置就可使用&#xff0c;编译器得是msvc才行&#xff0c;MinGW不管用。 资源地址&#xff1a;https://download.csdn.…

【年终总结】回首2023的精彩,迈向2024的未来

文章目录 一、历历在目&#xff0c;回首成长之路&#x1f3c3;‍1、坚持输出&#xff0c;分享所学2、积土成山&#xff0c;突破万粉3、不断精进&#xff0c;向外涉足 二、雅俗共赏&#xff0c;阅历百般美好&#x1f3bb;1、音乐之声&#xff0c;声声入耳2、书海遨游&#xff0c…

10.9.2 std::function 代替函数指针

std::function是一个模板类&#xff0c;基本可作为函数指针的代替品&#xff0c;具备更多功能&#xff0c;特别是与函数对象及bind配合使用。使用std::function时&#xff0c;需要添加头文件 #include <functional> 1.定义函数指针 18行&#xff0c;定义了一个函数指针类…

ssm基于java的智能训练管理平台论文

摘 要 互联网发展至今&#xff0c;无论是其理论还是技术都已经成熟&#xff0c;而且它广泛参与在社会中的方方面面。它让信息都可以通过网络传播&#xff0c;搭配信息管理工具可以很好地为人们提供服务。针对信息管理混乱&#xff0c;出错率高&#xff0c;信息安全性差&#x…

【位运算】【二分查找】【C++算法】100160价值和小于等于 K 的最大数字

作者推荐 【动态规划】【字符串】扰乱字符串 本文涉及的基础知识点 二分查找算法合集 位运算 LeetCode100160. 价值和小于等于 K 的最大数字 给你一个整数 k 和一个整数 x 。 令 s 为整数 num 的下标从1 开始的二进制表示。我们说一个整数 num 的 价值 是满足 i % x 0 且…

[书生·浦语大模型实战营]——XTuner 大模型单卡低成本微调

1.Finetune简介 在未经过微调的pretrained LLM中&#xff0c;模型只会尽量去拟合你的输入&#xff0c;也就是说模型并没有意识到你在提问&#xff0c;因此需要微调来修正。 1.1常用的微调模式 LLM的下游应用中,增量预训练和指令跟随是经常会用到的两种的微调模式。 增量预训练…

【前后端的那些事】解放后端!10min快速上手人人代码生成器(后端篇)

人人代码生成器【后端篇】 文章目录 人人代码生成器【后端篇】1. 克隆renren-generator2. 配置项目信息3. 配置数据库信息4. 启动项目5. 创建springboot项目5.1 pom.xml5.2 创建包结构5.3 编写application.yml5.4 将生成代码集成到项目中5.5 集成common模块5.6 启动项目 前言&a…

影响邮件打开率的因素有哪些?

影响邮件打开率得因素有很多&#xff0c;比如说邮件地址的有效性、邮件标题、定位人群、发送频率或者时间等因素。目前来讲&#xff0c;我们可以通过技术的手段改善邮件的到达率&#xff0c;但是邮件的打开率取决于收件人本身&#xff0c;所以发件人的发送动作如何在很大程度上…

一、Mindspore 公开课 - Transformer

课程链接&#xff1a;Mindspore 技术公开课 Transformer 论文地址&#xff0c;建议看完课程以后简单看看论文 前言 Transformer是一种神经网络结构&#xff0c;由Vaswani等人在2017年的论文“Attention Is All You Need” 中提出&#xff0c;用于处理机器翻译、语言建模和文…

PLAN B KRYPTO ASSETS GMBH CO. KG 普兰资产管理公司

引领加密技术不断演进 PLAN B KRYPTO ASSETS普兰资产管理以其独创的「Trident Strategy三叉戟模型」技术为基础&#xff0c;持续推动加密技术的发展&#xff0c;打造 Schutz&#xff08;舒茨盾&#xff09; AI 金融隐私匿名公链。致力于提供高效的技术服务&#xff0c;基于机构…

SqlAlchemy使用教程(三) CoreAPI访问与操作数据库详解

SqlAlchemy使用教程(一) 原理与环境搭建SqlAlchemy使用教程(二) 入门示例及编程步骤 三、使用Core API访问与操作数据库 Sqlalchemy 的Core部分集成了DB API, 事务管理&#xff0c;schema描述等功能&#xff0c;ORM构筑于其上。本章介绍创建 Engine对象&#xff0c;使用基本的…

「企业架构框架」什么是TOGAF?

什么是TOGAF&#xff1f;TOGAF开发概述什么是TOGAF上下文中的架构&#xff1f;什么是企业架构&#xff1f;TOGAF的结构介绍架构开发方法ADM指南和技术架构内容框架企业连续体和工具参考模型架构能力框架相关链接 什么是TOGAF&#xff1f; TOGAF由开放集团推出&#xff0c;是一种…

LangChain 71 字符串评估器String Evaluation衡量在多样化数据上的性能和完整性

LangChain系列文章 LangChain 60 深入理解LangChain 表达式语言23 multiple chains链透传参数 LangChain Expression Language (LCEL)LangChain 61 深入理解LangChain 表达式语言24 multiple chains链透传参数 LangChain Expression Language (LCEL)LangChain 62 深入理解Lang…

【STK】手把手教你利用STK进行关联分析仿真01-STK/CAT模块介绍

关联分析工具(Conjunction Analysis Tool )主要用于分析航天发射或卫星在轨运行过程中与其他目标之间的接近情况,关联分析包括: 接近分析工具 Close Approach Tool CAT高级接近分析工具 AdvCAT激光接近分析工具 LaserCAT发射窗口分析工具 Launch Window Analysis今天主要介绍…