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





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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

RIP【新华三与华为区别】

【介绍】 rip分为rip 1 与 rip 2 &#xff0c;rip 2 是对 rip 1 的一种升级&#xff0c;rip 2 可以进行认证等功能 【命令】 新华三&#xff1a; [HC3-R1] rip #启用rip [HC3-R1-rip] version 2 #告知rip 版本号 [HC3-R1-rip] network 192.168.1.0 #宣告其网段 [HC3-R1-rip] …

【AIGC】电影风格的一组绝美高清图提示词解析

实际示例 女人主角&#xff0c;以时尚电影风格为灵感&#xff0c;追求照片般的逼真度&#xff0c;运用伦勃朗式光线&#xff0c;创造奇幻且细节丰富的场景&#xff0c;充满象征意义&#xff0c;使用3D渲染技术达到8K超高清晰度。 分类相关信息主角女人风格时尚电影风格逼真度…

银行储蓄系统的顶层数据流图及细化数据流图

绘制出银行储蓄系统的顶层数据流图及细化数据流图&#xff1b; 银行储蓄系统存、取款流程如下&#xff1a; 1&#xff09;业务员事先录入利率信息&#xff1b; 2&#xff09;如果是存款&#xff0c;储户填写存款单&#xff0c;业务员将存款单键入系统&#xff0c;系统更新储户存…

力扣周赛第二题,下午更新后两道

本题中其实看着条件很多&#xff0c;但是仔细读一下会发现&#xff0c;前四个条件都是固定条件。然后我们再看题。 我们的暴力做法是遍历a数组的字符串a的下标起始下标&#xff0c;然后遍历b数组的字符串b的下标起始下标&#xff0c;进行判断&#xff0c;但是这样会超时&#x…

[软件工具]通用OCR识别文字识别中文识别服务程序可局域网访问

【软件界面】 【算法介绍】 采用业界最先进算法之一paddlocr&#xff0c;PaddleOCR&#xff0c;全称PaddlePaddle OCR&#xff0c;是一种基于深度学习的光学字符识别&#xff08;OCR&#xff09;技术。相较于传统的OCR技术&#xff0c;PaddleOCR具有许多优点。 首先&#xff0…

如何创建一个pytorch的训练数据加载器(train_loader)用于批量加载训练数据

Talk is cheap,show me the code! 哈哈&#xff0c;先上几段常用的代码&#xff0c;以语义分割的DRIVE数据集加载为例&#xff1a; DRIVE数据集的目录结构如下&#xff0c;下载链接DRIVE,如果官网下不了&#xff0c;到Kaggle官网可以下到&#xff1a; 1. 定义DriveDataset类&…

Kibana:使用反向地理编码绘制自定义区域地图

Elastic 地图&#xff08;Maps&#xff09;附带预定义区域&#xff0c;可让你通过指标快速可视化区域。 地图还提供了绘制你自己的区域地图的功能。 你可以使用任何您想要的区域数据&#xff0c;只要你的源数据包含相应区域的标识符即可。 但是&#xff0c;当源数据不包含区域…

Spring集成

目录 概述1 声朋一个简单的集成流1.1 使用XML定义集成流1.2 使用Java配置集成流1.3 使用Spring lntegration 的 DSL 配置 2 Spring integration 功能概览2.1 消息通道2.2 过滤器2.3 转换器2.4 路由器2.5 切分器2.6 服务激活器2.7 网关2.8 通道适配器2.9 端点模块 概述 就像我们…

RibbonGroup 添加QLineEdit

RibbonGroup添加QLineEdit&#xff1a; QLineEdit* controlEdit new QLineEdit(); controlEdit->setToolTip(tr("Edit")); controlEdit->setText(tr("Edit")); controlEdit->setMinimumWidth(150); …

vue知识-04

计算属性computed 注意&#xff1a; 1、计算属性是基于它们的依赖进行缓存的 2、计算属性只有在它的相关依赖发生改变时才会重新求值 3、计算属性就像Python中的property&#xff0c;可以把方法/函数伪装成属性 4、computed: { ... } 5、计算属性必须要有…

Windows python下载

1、下载 进入到地址&#xff1a;https://www.python.org/dowmloads 可以直接下载最新的版本 或者点击Windows&#xff0c;查看下方更多的版本 点击文档就下载下来啦 2、安装 双击下载的文件&#xff0c;勾选Add python.exe to PATH添加到环境变量中&#xff0c;选择Coutomi…

【数据结构】树和二叉树堆(基本概念介绍)

&#x1f308;个人主页&#xff1a;秦jh__https://blog.csdn.net/qinjh_?spm1010.2135.3001.5343&#x1f525; 系列专栏&#xff1a;《数据结构》https://blog.csdn.net/qinjh_/category_12536791.html?spm1001.2014.3001.5482 ​​ 目录 前言 树的概念 树的常见名词 树与…

Linux工具-搭建文件服务器

当我们使用linux系统作为开发环境时&#xff0c;经常需要在Linux系统之间、Linux和Windows之间传输文件。 对少量文件进行传输时&#xff0c;可以使用scp工具在两台主机之间实现文件传输&#xff1a; rootubuntu:~$ ssh --help unknown option -- - usage: ssh [-46AaCfGgKkMN…

【面试突击】Java面试底层逻辑(HashMap、ConcurrentHashMap面试实战)

&#x1f308;&#x1f308;&#x1f308;&#x1f308;&#x1f308;&#x1f308;&#x1f308;&#x1f308; 欢迎关注公众号&#xff08;通过文章导读关注&#xff1a;【11来了】&#xff09;&#xff0c;及时收到 AI 前沿项目工具及新技术 的推送 发送 资料 可领取 深入理…

HashMap集合万字源码详解(面试常考)

文章目录 HashMap集合1.散列2.hashMap结构3.继承关系4.成员变量5.构造方法6.成员方法6.1增加方法6.2将链表转换为红黑树的treeifyBin方法6.3扩容方法_resize6.3.1扩容机制6.3.2源码resize方法的解读 6.4 删除方法(remove)6.5查找元素方法(get)6.6遍历HashMap集合几种方式 7.初始…

12.2内核空间基于SPI总线的OLED驱动

在内核空间编写SPI设备驱动的要点 在SPI总线控制器的设备树节点下增加SPI设备的设备树节点&#xff0c;节点中必须包含 reg 属性、 compatible 属性、 spi-max-frequency 属性&#xff0c; reg 属性用于描述片选索引&#xff0c; compatible属性用于设备和驱动的匹配&#xff…

pyinstaller,一个超酷的 Python 库!

更多Python学习内容&#xff1a;ipengtao.com 大家好&#xff0c;今天为大家分享一个超酷的 Python 库 - pyinstaller。 Github地址&#xff1a;https://github.com/pyinstaller/pyinstaller PyInstaller是一个用于将Python应用程序打包成独立可执行文件的工具。这可以将Python…

Leading Dimension是什么

在LAPACK中频繁出现Leading Dimension&#xff08;中文翻译为“主维度”&#xff09;&#xff0c;那么它是什么呢&#xff1f; 首先了解行主序&#xff08;Row-Major&#xff09;和列主序&#xff08;Column-Major&#xff09;的概念&#xff1a; Given a matrix A of shape …

宝塔nginx部署前端页面刷新报404

问题&#xff1a; 当我们使用脚手架打包前端项目的时候&#xff0c;如果前端项目并没有静态化的配置&#xff0c;如以下 当我们刷新页面&#xff0c;或进行路由配置访问的时候就会报404的错误 原因&#xff1a; 这是因为通常我们做的vue项目属于单页面开发。所以只有index.html…