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

【Unity】2D 对话模块的实现

对话模块主要参考 【Unity教程】剧情对话系统 实现。

在这次模块的构建将基于 unity ui 组件 和 C#代码实现一个从excel 文件中按照相应规则读取数据并展示的逻辑。这套代码不仅能实现正常的对话,也实现了对话中可以通过选择不同选项达到不同效果的分支对话功能。

整一套代码分为分为两部分,一部分和库存模块一样通过 Collider 2D 和 Unity Event 构建了一个范围内可互动的功能,这一部分可以参考之前的库存模块。

剩下一部分就是对话框模块整体逻辑,先看一下效果:

在这里插入图片描述

在这里插入图片描述

从上图中,可以看出整个对话框可以分为五个部分:头像、说话人名字、普通对话内容、跳转到下一句的按钮、和 选择对话框。可以简单将 普通对话内容和跳转按钮 划分成一个逻辑组件,而选择对话框划分成另一个逻辑组件,这样划分的原因在于两者实现方式不一样。

接下来内容将分为三个段落,我将自下而上一一实现:从Excel中读取对话内容的方式、普通对话实现 和 选择对话 这三个步骤:

从Excel中读取对话内容的方式

整一个Excel的内容分为了五个部分,如下图所述,分别是、

对话的id,用来快速定位到一个对话类,可以方便我们进行查找和使用

说话人的名字,用来展示在 对话框中的UI组件

说话人的头像,也是用来展示在对话框中的 UI 和 名字不同的是他是代表一个文件的路径

对话内容,用来展示在 对话框中的UI组件

下一句对话id,用来做跳转使用

在这里插入图片描述

定义好具体数据的Excel之后,需要将Excel导出成Unity可以识别的编码格式,否则在Unity中会被识别成乱码(这一步可以通过 txt 文本另存为的方式进行变更)。将另存为的文本保存在unity 项目中的 Assets/Resources/Dialogue/路径下,以便项目能够读取到。

在这里插入图片描述

接下来,在C#中定义一个对应的class,用来接住Excel读取出的数据,并在C#中通过Resources.Load的方法来读取。Resoruces.Load的方法会将整个文本以 string的方式进行读取,所以还需要对每一行文本进行拆分,才能处理成我们需要的对话类。最终,将 对话 id 和 对话实体保存到一个 dictionary里,方便后面的步骤进行调用。

// 对话类class Dialogue{public int dialogueId;public string characterName;public string characterIcon;public string dialogue;public List<int> toDialogueIdList;public Dialogue(int dialogueId, string characterName, string characterIcon, string dialogue, List<int> toDialogueIdList){this.dialogueId = dialogueId;this.characterName = characterName;this.characterIcon = characterIcon;this.dialogue = dialogue;this.toDialogueIdList = toDialogueIdList;}}//public void eventDialogueFileProcess(int eventId){   // 文件处理成 对话 id 和 对话对象 的映射dialogueDictionary.Clear();string filePath = dialogueFilePrefix + eventId;TextAsset textFile = Resources.Load<TextAsset>(filePath);processDialogueFile(textFile);    }private void processDialogueFile(TextAsset dialogueFile){string[] dialogueArrays = dialogueFile.text.Split("\r\n");foreach(string strDialogue in dialogueArrays){if(string.IsNullOrEmpty(strDialogue)){continue;}string[] cols = strDialogue.Split(',');string[] strToDialogueIds = cols[4].Split('|');List<int> toDialogueIdList = new List<int>();foreach(string strToDialogueId in strToDialogueIds){   if(string.IsNullOrEmpty(strToDialogueId)){break;}Debug.Log(strToDialogueId);toDialogueIdList.Add(int.Parse(strToDialogueId));}Dialogue dialogue = new Dialogue(int.Parse(cols[0]), cols[1], cols[2], cols[3], toDialogueIdList);dialogueDictionary.Add(dialogue.dialogueId, dialogue);}}

普通对话实现

普通对话实现的主要方式就是将当前对话的 id 保存为一个成员对象,这样在触发按钮的点击事件之后,便能通过事件监听的方式调用这个对话id来获取下一段对话。

// 正常对话int nextDialogueId = dialogueIdList[0];if(dialogueDictionary.TryGetValue(nextDialogueId, out Dialogue dialogue)){// 隐藏多选dialogueMutliBG.enabled = false;// 显示正常对话dialogueText.enabled = true;nextButton.gameObject.SetActive(true);characterIcon.sprite = Resources.Load<Sprite>(characterIconPrefix + dialogue.characterIcon);characterName.text = dialogue.characterName;dialogueText.text = dialogue.dialogue;currentDialogueIndex = dialogue.dialogueId; }

选择对话

选择对话相比普通对话来说,实现有一些复杂,主要在于需要用C# 的 delegate 代理一个函数,来达成下一步对话的操作。这样做的原因在于选择对话每一个对话都会有一个下一个对话的选项,导致没有办法直接使用普通对话定义的当前对话id变量。

// 可选对话选择List<Dialogue> dialogues = new List<Dialogue>();foreach(int index in dialogueIdList){if(dialogueDictionary.TryGetValue(index, out Dialogue dialogue)){dialogues.Add(dialogue);}}// 隐藏正常对话dialogueText.enabled = false;nextButton.gameObject.SetActive(false);// 显示多选dialogueMutliBG.enabled = true;foreach(Dialogue dialogue in dialogues){// 依次生成对话 ButtonoptionButton = Instantiate(optionButton, dialogueMutliBG.transform);TextMeshProUGUI textMeshPro = optionButton.GetComponentInChildren<TextMeshProUGUI>();textMeshPro.text = dialogue.dialogue;// 添加事件监听optionButton.GetComponent<Button>().onClick.AddListener(delegate {// 点完以后 销毁选择对话框Button[] optionalButtons = dialogueMutliBG.GetComponentsInChildren<Button>();foreach(Button button in optionalButtons){Destroy(button.gameObject);}// 显示下一句对话showDialogue(dialogue.toDialogueIdList);});}

将这两部分合入一个函数中,通过下一段对话id 的数量进行判断到底是 普通对话还是选择对话,合并后的代码如下:


using System.Collections;
using System.Collections.Generic;
using TMPro;
using UnityEngine;
using UnityEngine.UI;public class DialogueControl : MonoBehaviour
{class Dialogue{public int dialogueId;public string characterName;public string characterIcon;public string dialogue;public List<int> toDialogueIdList;public Dialogue(int dialogueId, string characterName, string characterIcon, string dialogue, List<int> toDialogueIdList){this.dialogueId = dialogueId;this.characterName = characterName;this.characterIcon = characterIcon;this.dialogue = dialogue;this.toDialogueIdList = toDialogueIdList;}}public Image characterIcon;public TextMeshProUGUI characterName;public TextMeshProUGUI dialogueText;private static string dialogueFilePrefix = "Dialogue/event_dialogue_";private static string characterIconPrefix = "Character/";private Dictionary<int, Dialogue> dialogueDictionary = new Dictionary<int, Dialogue>();private int currentDialogueIndex = 1;public Image dialogueMutliBG;public Button optionButton;public Button nextButton;public int lastEvent;// Start is called before the first frame updatevoid Start(){transform.gameObject.SetActive(false);}// Update is called once per framevoid Update(){}public void eventDialogueFileProcess(int eventId){   transform.gameObject.SetActive(true);// 文件处理成 对话 id 和 对话对象 的映射if(!lastEvent.Equals(eventId)){dialogueDictionary.Clear();string filePath = dialogueFilePrefix + eventId;TextAsset textFile = Resources.Load<TextAsset>(filePath);processDialogueFile(textFile);}// 显示第一段对话List<int> dialogueIdList = new List<int>(1);dialogueIdList.Add(1);showDialogue(dialogueIdList);lastEvent = eventId;}private void processDialogueFile(TextAsset dialogueFile){string[] dialogueArrays = dialogueFile.text.Split("\r\n");foreach(string strDialogue in dialogueArrays){if(string.IsNullOrEmpty(strDialogue)){continue;}string[] cols = strDialogue.Split(',');string[] strToDialogueIds = cols[4].Split('|');List<int> toDialogueIdList = new List<int>();foreach(string strToDialogueId in strToDialogueIds){   if(string.IsNullOrEmpty(strToDialogueId)){break;}Debug.Log(strToDialogueId);toDialogueIdList.Add(int.Parse(strToDialogueId));}Dialogue dialogue = new Dialogue(int.Parse(cols[0]), cols[1], cols[2], cols[3], toDialogueIdList);dialogueDictionary.Add(dialogue.dialogueId, dialogue);}}private void showDialogue(List<int> dialogueIdList){if(dialogueIdList.Count == 0){return;}if(dialogueIdList.Count > 1){// 可选对话选择List<Dialogue> dialogues = new List<Dialogue>();foreach(int index in dialogueIdList){if(dialogueDictionary.TryGetValue(index, out Dialogue dialogue)){dialogues.Add(dialogue);}}// 隐藏正常对话dialogueText.enabled = false;nextButton.gameObject.SetActive(false);// 显示多选dialogueMutliBG.enabled = true;foreach(Dialogue dialogue in dialogues){// 依次生成对话 ButtonoptionButton = Instantiate(optionButton, dialogueMutliBG.transform);TextMeshProUGUI textMeshPro = optionButton.GetComponentInChildren<TextMeshProUGUI>();textMeshPro.text = dialogue.dialogue;// 添加事件监听optionButton.GetComponent<Button>().onClick.AddListener(delegate {// 点完以后 销毁选择对话框Button[] optionalButtons = dialogueMutliBG.GetComponentsInChildren<Button>();foreach(Button button in optionalButtons){Destroy(button.gameObject);}// 显示下一句对话showDialogue(dialogue.toDialogueIdList);});}}else {// 正常对话int nextDialogueId = dialogueIdList[0];if(dialogueDictionary.TryGetValue(nextDialogueId, out Dialogue dialogue)){// 隐藏多选dialogueMutliBG.enabled = false;// 显示正常对话dialogueText.enabled = true;nextButton.gameObject.SetActive(true);characterIcon.sprite = Resources.Load<Sprite>(characterIconPrefix + dialogue.characterIcon);characterName.text = dialogue.characterName;dialogueText.text = dialogue.dialogue;currentDialogueIndex = dialogue.dialogueId;}}}public void nextDialogue(){if(dialogueDictionary.TryGetValue(currentDialogueIndex, out Dialogue dialogue)){if(dialogue.toDialogueIdList.Count == 0){transform.gameObject.SetActive(false);nextButton.gameObject.SetActive(false);}showDialogue(dialogue.toDialogueIdList);}}}

来看一下最终效果

请添加图片描述

相关文章:

【Unity】2D 对话模块的实现

对话模块主要参考 【Unity教程】剧情对话系统 实现。 在这次模块的构建将基于 unity ui 组件 和 C#代码实现一个从excel 文件中按照相应规则读取数据并展示的逻辑。这套代码不仅能实现正常的对话&#xff0c;也实现了对话中可以通过选择不同选项达到不同效果的分支对话功能。 …...

laravel安装初步使用学习 composer安装

一、什么是laravel框架 Laravel框架可以开发各种不同类型的项目&#xff0c;内容管理系统&#xff08;Content Management System&#xff0c;CMS&#xff09;是一种比较典型的项目&#xff0c;常见的网站类型&#xff08;如门户、新闻、博客、文章等&#xff09;都可以利用CM…...

【VS插件】VS code上的Remote - SSH

【VS插件】VS code上的Remote - SSH 目录 【VS插件】VS code上的Remote - SSH获得Linux服务器或者Linux系统的IP地址下载插件远程登录注意如果Linux虚拟机系统无法连接成功可能是没有开启ssh服务优势 作者&#xff1a;爱写代码的刚子 时间&#xff1a;2023.9.12 前言&#xff1…...

TensorFlow 02(张量)

一、张量 张量Tensor 张量是一个多维数组。与NumPy ndarray对象类似&#xff0c;tf.Tensor对象也具有数据类型和形状。如下图所示: 此外&#xff0c;tf.Tensors可以保留在GPU中。TensorFlow提供了丰富的操作库 (tf.add&#xff0c;tf.matmul,tf.linalg.inv等)&#xff0c;它们…...

513. 找树左下角的值

代码链接&#xff1a; 力扣&#xff08;LeetCode&#xff09;官网 - 全球极客挚爱的技术成长平台 思路&#xff1a; 万金油层次遍历&#xff0c;保存每一层的第一个元素返回就行了 我的代码&#xff1a; /*** Definition for a binary tree node.* struct TreeNode {* …...

量化:基于支持向量机的择时策略

文章目录 参考机器学习简介策略简介SVM简介整体流程收集数据准备数据建立模型训练模型测试模型调节参数 参考 Python机器学习算法与量化交易 利用机器学习模型&#xff0c;构建量化择时策略 机器学习简介 机器学习理论主要是设计和分析一些让计算机可以自动“学习”的算法。…...

成功解决Selenium 中116版本的chromedriver找不到问题

Selenium 中的Google&#xff08;谷歌浏览器&#xff09;最新版本chromedriver 文章目录 Selenium 中的Google&#xff08;谷歌浏览器&#xff09;最新版本chromedriver1.当前作者的谷歌浏览器版本2.当前驱动官网的最新版本3.当不想降低浏览器版本继续使用谷歌浏览器的办法 1.当…...

PYQT常用组件--方法汇总

QTimeEdit timeEdit是Qt框架中的一个时间编辑器控件&#xff0c;它提供了以下常用方法&#xff1a; setTime(QTime time): 设置时间编辑器的时间为指定的QTime对象。time(): 返回时间编辑器的当前时间&#xff0c;返回一个QTime对象。setDateTime(QDateTime dateTime): 设置时…...

Linux系统编程(一):文件 I/O

参考引用 UNIX 环境高级编程 (第3版)黑马程序员-Linux 系统编程 1. UNIX 基础知识 1.1 UNIX 体系结构&#xff08;下图所示&#xff09; 从严格意义上说&#xff0c;可将操作系统定义为一种软件&#xff0c;它控制计算机硬件资源&#xff0c;提供程序运行环境&#xff0c;通常…...

OSM+three.js打造3D城市

对于我在 Howest 的研究项目,我决定构建一个 3D 版本的 Lucas Bebber 的“交互式讲故事的动画地图路径”项目。我将使用 OSM 中的矢量轮廓来挤出建筑物的形状并将它们添加到 3js 场景中,随后我将对其进行动画处理。 一、开发环境 为了使用 Node 和 npm 包,我选择使用 Vite…...

02JVM_垃圾回收GC

二、垃圾回收GC 在堆里面存放着java的所有对象实例&#xff0c;当对象为“死去”&#xff0c;也就是不再使用的对象&#xff0c;就会进行垃圾回收GC 1.如何判断对象可以回收 1.1引用计数器 介绍 在对象中添加一个引用计数器&#xff0c;当一个对象被其他变量引用时这个对象…...

ARM Linux DIY(八)USB 调试

前言 V3s 带有一个 USB 接口&#xff0c;将其设置为 HOST 或 OTG 模式&#xff0c;这样可以用来接入键盘、鼠标等 USB 外设。 USB 简介 USB 有两种设备&#xff1a;HOST 和 USB 功能设备。 在 USB2.0 中又引入了一个新的概念 OTG&#xff0c;即设备角色可以动态切换。 切换方…...

编程小白的自学笔记十四(python办公自动化创建、复制、移动文件和文件夹)

系列文章目录 编程小白的自学笔记十三&#xff08;python办公自动化读写文件&#xff09; 编程小白的自学笔记十二&#xff08;python爬虫入门四Selenium的使用实例二&#xff09; 编程小白的自学笔记十一&#xff08;python爬虫入门三Selenium的使用实例详解&#xff09; …...

MySQL使用Xtrabackup备份到AWS存储桶

1.安装Xtrabackup cd /tmp wget https://downloads.percona.com/downloads/Percona-XtraBackup-8.0/Percona-XtraBackup-8.0.33-28/binary/redhat/7/x86_64/percona-xtrabackup-80-8.0.33-28.1.el7.x86_64.rpm yum -y localinstall percona-xtrabackup-80-8.0.33-28.1.el7.x86…...

(高阶)Redis 7 第11讲 BIGKEY 优化篇

面试题 问题答案如何在海量数据中查询某一固定前缀的Keyscan生产环境如何限制 keys */FLUSHDB/FLUSHALL 等危险命令,防止误删误用# 修改配置文件 rename-command keys "" rename-command flushdb "" rename-command flushall ""如何使用MEMORY U…...

一阶差分和二阶差分概念及其举例

一阶差分和二阶差分概念及其举例 目录 一阶差分和二阶差分概念及其举例1、一阶差分1.1 概念1.2 举例 2、二阶差分2.1 概念2.2 举例 1、一阶差分 1.1 概念 一阶差分是指对一个数列中的每个元素&#xff0c;计算其与其前一个元素之差的操作。 1.2 举例 举例来说&#xff0c;对…...

使用自定义注解和SpringAOP捕获Service层异常,并处理自定义异常

目录 一 自定义异常二 自定义注解三 注解切面处理类四 使用 一 自定义异常 /*** 自定义参数为null异常*/ public class NoParamsException extends Exception {//用详细信息指定一个异常public NoParamsException(String message){super(message);}//用指定的详细信息和原因构…...

Kotlin(六) 类

目录 创建类 调用类 类的继承------open 构造函数 创建类 创建类和创建java文件一样&#xff0c;选择需要创建的目录New→Kotlin File/Class Kotlin中也是使用class关键字来声明一个类的&#xff0c;这一点和Java一致。现在我们可以在这个类中加入字段和函数来丰富它的功…...

蓝桥杯官网练习题(灌溉)

题目描述 小蓝负责花园的灌溉工作。 花园可以看成一个 n 行 m 列的方格图形。中间有一部分位置上安装有出水管。 小蓝可以控制一个按钮同时打开所有的出水管&#xff0c;打开时&#xff0c;有出水管的位置可以被认为已经灌溉好。 每经过一分钟&#xff0c;水就会向四面扩展…...

数据结构:树的概念和结构

文章目录 1. 树的概念2. 树的结构3. 树的相关概念4. 树的表示孩子表示法双亲表示法孩子兄弟表示法 5. 树在实际中的应用5. 树在实际中的应用 1. 树的概念 树是一种非线性的数据结构,它是由 n (n > 0)个有限结点组成一个具有层次关系的. 把它叫做树是因为它看起来像一棵倒挂的…...

【GIS】栅格转面报错:ERROR 000864输入栅格: 输入不在定义的属性域内。 ERROR 000863: 无效的 GP 数据类型

问题: 栅格转面(矢量)时,ArcGIS窗口显示:ERROR 000864输入栅格: 输入不在定义的属性域内。 ERROR 000863: 无效的 GP 数据类型. 原因: 栅格转面时输入的栅格数据集的字段必须是整型. 解决办法: 使用Spatial Analyst中的转为整型工具,将栅格数据转为整型后再进行栅格转面的操作…...

32 WEB漏洞-文件操作之文件下载读取全解

目录 介绍利用获取数据库配置文件文件名&#xff0c;参数值&#xff0c;目录符号 涉及案例:Pikachu-文件下载测试-参数Zdns-文件下载真实测试-功能点小米路由器-文件读取真实测试-漏洞RoarCTF2019-文件读取真题复现-比赛百度杯2017二月-Zone真题复现-比赛拓展 下载和读取都差不…...

Linux之history、tab、alias、命令执行顺序、管道符以及exit

目录 Linux之history、tab、alias、命令执行顺序、管道符以及exit history历史命令 格式 参数 修改默认记录历史命令条数 案例 案例1 --- 显示history历史记录中出现次数最高的top10 案例2 --- 增加history显示的时间信息 命令与文件名补全 --- tab 命令别名 格式 案…...

vcomp100.dll丢失怎样修复?5个靠谱的修复方法分享

VCOMP100.DLL 是由微软打造的动态链接库&#xff0c;它对于一些图形密集型应用&#xff0c;例如Photoshop&#xff0c;以及多款知名游戏如巫师3的运行至关重要。 如果操作系统在启动应用程序时无法找到此vcomp100.dll&#xff0c;则会出现vcomp100.dll丢失或未找到错误。 如果D…...

Vue3自定义指令(directive)

文章目录 前言一、Vue3指令钩子函数二、自定义指令的两种方式1.局部使用例子1&#xff1a;鉴权例子2&#xff1a;拖拽 2.全局使用例子1&#xff1a;监听宽高指令例子2&#xff1a;监听是否出现在视口 总结 前言 此文章主要讲了vue3中自定义指令的使用&#xff0c;以及一些WebA…...

大数据课程L9——网站流量项目的实时业务处理代码

文章作者邮箱:yugongshiye@sina.cn 地址:广东惠州 ▲ 本章节目的 ⚪ 掌握网站流量项目的SparkStreaming代码; ⚪ 掌握网站流量项目的HBaseUtil代码; ⚪ 掌握网站流量项目的MysqlUtil代码; ⚪ 掌握网站流量项目的LogBean代码; ⚪ 掌握网站流量项目的To…...

【2023最新B站评论爬虫】用python爬取上千条哔哩哔哩评论

文章目录 一、爬取目标二、展示爬取结果三、爬虫代码四、同步视频五、附完整源码 您好&#xff0c;我是 马哥python说&#xff0c;一枚10年程序猿。 一、爬取目标 之前&#xff0c;我分享过一些B站的爬虫&#xff1a; 【Python爬虫案例】用Python爬取李子柒B站视频数据 【Pyt…...

mysql设置max_sp_recursion_depth,sql_mode

mysql 中设置 @@max_sp_recursion_depth select @@max_sp_recursion_depth; 今天在mysql 写存储过程递归调用时,发现老是报错(recovery limit 0(as set by the max_sp_recursion_depth));后来百度下发现 max_sp_recursion_depth设置不对; 这个修改涉及到全局和session级修…...

论文阅读:SERE: Exploring Feature Self-relation for Self-supervised Transformer

Related Work Self-supervised 学习目的是在无人工标注的情况下通过自定制的任务&#xff08;hand-crafted pretext tasks&#xff09;学习丰富的表示。 Abstract 使用自监督学习为卷积网络&#xff08;CNN&#xff09;学习表示已经被验证对视觉任务有效。作为CNN的一种替代…...

遥感数据与作物模型同化应用:PROSAIL模型、DSSAT模型、参数敏感性分析、数据同化算法、模型耦合、精度验证等主要环节

查看原文>>>遥感数据与作物模型同化实践技术应用 基于过程的作物生长模拟模型DSSAT是现代农业系统研究的有力工具&#xff0c;可以定量描述作物生长发育和产量形成过程及其与气候因子、土壤环境、品种类型和技术措施之间的关系&#xff0c;为不同条件下作物生长发育及…...

网站设计合同/百度双十一活动

我是linux 的服务器&#xff0c;navicat12的客户端&#xff0c; 开始链接的时候需要开服务器上得对外爆漏端口 3306&#xff0c;方法&#xff1a; 添加指定需要开放的端口&#xff1a; firewall-cmd --add-port123/tcp --permanent 重载入添加的端口&#xff1a; firewall-cmd …...

做网站大概需要多少费用/关键词优化seo优化排名

IT培训费用一般都是1W以上&#xff0c;因而想学IT的大学生们在选择培训机构时会犹豫很久&#xff0c;一是自身经济不自由&#xff0c;二是培训效果没人保证&#xff0c;而CSDN推出的超级实习生公开保障培训效果&#xff0c;费用等同于IT培训&#xff0c;应该怎么选呢? IT培训…...

网站找百度做可以嘛/西安百度快照优化

博主闭关两个多月&#xff0c;查阅了数百万字的大数据资料&#xff0c;结合自身的学习和工作经历&#xff0c;总结了大厂高频面试题&#xff0c;里面涵盖几乎所有我见到的大数据面试题目。 《大厂高频面试题系列》目前已总结4篇文章&#xff0c;且在持续更新中✍。文中用最直白…...

用java做网站教程/烟台seo外包

Android:自定义View&#xff08;一&#xff09; 自定义View&#xff1a; 就是继承系统的View&#xff0c;通过添加绘制元素&#xff0c;逻辑以实现自己想要实习的效果的控件。在Android开发过程中&#xff0c;我们想要的效果大多数情况下可以直接使用系统提供的控件来实现&…...

wordpress调用 php文件上传/百度下载安装到手机

事件&#xff1a; 由于前一天的晚上加班了、第二天又接着上班、所以精神上有点不在状态&#xff1b;收到客户的反馈说在slave上找不到master刚刚插入的数据&#xff1b; 阶段1&#xff1a; 遇到这事的第一感觉就是这可能是主从延时、或是slave的复制出错了使得数据没有同步、于…...

天津 论坛网站制作公司/线上推广100种方式

注&#xff1a;本文图片来源&#xff08;http://hosted.zeh.com.br/tweener/docs/en-us/misc/transitions.html。 侵权请告知&#xff0c;即刻删除&#xff09; 什么是缓动&#xff0c; 缓动(easing) 是指动画效果在运行时被指定速度&#xff0c;使视感更加真实。 比較经典的&a…...