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

Unity AnimationClip详解(1)

【动画片段】

前文我们介绍了骨骼动画,在Unity中骨骼动画的部分静态数据存储在SkinedMeshRender中,而另一部分动态的关键帧数据就是存储在AnimationClip中的。

关键帧数据来自与FBX、OBJ等动画模型文件,可以在动画导入后的Animation选项卡中查看动画,Unity将其分为了四个区域,可以在动画预览区域,播放动画和查看特定帧的动画。

(A区和B区是在运行时并不会用到,C区严格来说,属于动作系统的一部分和动画片段没关系,但因为和动画系统联系紧密,会和AnimationClip关联起来,在后文中再详细说说)

预览动画所需的数据就在AniamtionClip中,图中所示的即为AniamtionClip文件:

可以双击来查看具体的动画数据,如下所示:

由于这里的AniamtionClip数据来自动画模型文件,所以是不可以修改的。在win的资源管理器中,也看不到这个文件,因为在Unity工程中看到的是Object而不是Asset。(Object与Asset的区别)

可以将AnimationClip的数据Copy一遍,生成单独的文件后,即可编辑。Unity提供了交互的方式,但可以通过代码自动生成,例如:

    public void CopyAnimationClip(GameObject go){AnimationClip[] clips = AnimationUtility.GetAnimationClips(go);foreach (AnimationClip clip in clips){AnimationClip newClip = new AnimationClip();newClip.name = clip.name + "_auto";newClip.frameRate = clip.frameRate;newClip.legacy = clip.legacy;var setting = AnimationUtility.GetAnimationClipSettings(clip);AnimationUtility.SetAnimationClipSettings(newClip, setting);EditorCurveBinding[] bindings = AnimationUtility.GetCurveBindings(clip);//https://github.com/Unity-Technologies/UnityCsReference/blob/master/Editor/Mono/Animation/EditorCurveBinding.bindings.csforeach (var binding in bindings){AnimationCurve curve = AnimationUtility.GetEditorCurve(clip, binding);AnimationUtility.SetEditorCurve(newClip, binding, curve);}AssetDatabase.CreateAsset(newClip, "Assets/" + newClip.name + ".anim");AssetDatabase.SaveAssets();}AssetDatabase.Refresh();}

随后,我们就可以找到该文件,可以发现其实质是个YAML文本文件,其中存放了关键帧的数据

【关键帧数据】

一般UI动画为30帧,局内动画为60帧,前文列举的骨骼层级结构中有30个骨骼,每个骨骼至多有10个通道的数据,分为是S缩放、Q旋转(一般用四元数表示)、T位移。

因此,在数据结构设置上,AnimationClip需要是这样的:

   /// Class AnimationClip
   /// {
   ///     Bone* boneData //需要持有每个骨骼,这里不一定是要引用或者指针,每个骨骼可以有一个ID标识符,看游戏引擎如何计算了,通过标识符索引骨骼数据也可以。长度骨骼数,这里是30
   /// }

    /// Class Bone
    /// {
    ///     string bonename
    ///     int boneid
    ///     Channel* channelData //需要持有每个通道的数据,这里不一定是要引用或者指针,每个通道可以有一个ID标识符,看游戏引擎如何计算了,通过标识符索引通道数据也可以。长度至多是10
    /// }

    /// Class Channel
    /// {
    ///     string channelname
    ///     int channelid
    ///     float* data //30帧有31个数据,60帧有61个数据,这里就是真正的动画数据
    /// }

(为了提高性能,降低内存,这些在引擎底层一般会用struct而不是class)

以时间为X轴,以动画数据中某个骨骼的某个通道的值为Y轴,得到的是一系列孤立的点,点与点之间的数据通常通过插值得到。

通常都会用线性插值,也即我们可以在XY图中,直接将两点依次用直线连接起来,得到折线图。

折线图的变化陡峭(看点的切线变化,基本的数学知识),如果角色前后两帧的变化不大那么直接用折线图没问题,否则我们会看到角色动作不连贯,不流畅。

(注意,数学知识针对的是两个点,不是前后两帧的动作,可能前后两帧动作变化很小,但对动作中某些骨骼的某些通道的值而言变化很大;还有可能前后两帧动作变化很大,但对动作中某些骨骼的某些通道而言变化很小)

为解决动作不流畅的问题,我们需要用曲线去拟合这些点。

通常情况下,我们了解到的曲线拟合,大多都是用一条能够用一段函数描述的曲线去拟合不同点的分布。

这里的拟合要求曲线必须经过这些点,因此,我们需要用一个多段函数来描述曲线。

每段用什么函数可以是任意的,但考虑到对性能等的要求,在游戏中基本都用贝塞尔曲线或三次多项式。

【贝塞尔曲线】

原理

基本原理见链接:https://juejin.cn/post/7082701281969569829

更详细的见视频:https://www.youtube.com/watch?v=aVwxzDHniEw

应用

基本上三阶贝塞尔曲线就够用了,unity中各类曲线的编辑、游戏中道路、水管等的建设、移动轨迹、配置数据等都可以用到贝塞尔曲线

拓展

可以看到,几个点即可描述一条贝塞尔曲线。描述二阶贝塞尔曲线,需要三个点;描述三阶贝塞尔曲线,需要四个点。

如果只有两个已知点,怎么构造二阶贝塞尔曲线。

必须借助这两个点和其他默认数据算出默认的第二个点:(已知P1 P3,需要得到P2)例如:

  1. 第二个点和这两个点构成等边三角形
  2. 第二个点在这两个点连线的中间,和连线的距离是连线的长度(即构成等腰三角形)
  3. 第二个点在这两个点连线的中间,和连线的距离是连线的长度* t 。对于不同组的两个点,t可以始终是一个默认的值;也可以是根据其他因素算出来的一个值。这里额外引入了一个参数
  4. 第二个点和两个点的水平距离通过参数t1控制,和连线的距离通过参数t2控制。对于不同组的两个点,t1、t2可以始终是一个默认的值;也可以是根据其他因素算出来的一个值。这里又额外引入了一个参数
  5. 上述方式不能控制曲线的倾斜,需要通过斜率来控制。引入两个参数t1、t2,分别表示两个点的斜率,斜率的连线交点为第三个点。对于不同组的两个点,t1、t2可以始终是一个默认的值;也可以是根据其他因素算出来的一个值,获取从某个地方读取/获取的值。

按照上述方式,只有三个已知点,也可以构造出三阶贝塞尔曲线。

如果只有两个已知点,怎么构造三阶贝塞尔曲线。可想而知,需要有更多的参数来计算出其他两个点:

  1. P2、P3和已知的两个点构成正方形
  2. P2、P3和已知的两个点构成长放形,其宽为长度*t
  3. P2、P3和已知的两个点构成等腰梯形,其高为长度*t1,另一个底为长度*t2
  4. P2、P3和已知的两个点构成梯形,其高为长度*t1,P2和P1的距离为长度*w1,P3和P4的距离为长度*w2。这里又引入了额外的w1和w2两个权重参数
  5. 在4的基础上引入斜率参数t1和t2以替代高
  6. P2、P3和已知的两个点构成四边形,在5的基础上,对x和y采用相同的权重
  7. 在6的基础上,对y采用不同的权重,再额外引入两个参数w3、w4

【动画曲线及API】

AnimationCurve

AnimationClip中最为重要和核心的数据是AnimationCurve,对应上文说的Bone。我们可以通过GetCurve和SetCurve来从AnimationClip中获取和设置曲线,这个过程就像是从Dictionary中Get和Set一样。

(Add和Delete有时候可以合并到Set中,Set时Key是新的,表示Add;Set是Data是空的,表示Delete)

Get和Set操作都需要Key和Data,此时的Key就是每个骨骼,也即每个的名字,考虑到名字会有重复的,会用相对于根节点的路径来表示,通过相对路径可以找到每个节点;Data就是AnimationCurve了。

一个节点至少有10个Channel的数据,因此,还需要第二个Key来表示哪个Channel,因此GetCurve和SetCurve方法至少要是这样的:

AnimationClip.SetCurve(string relativePath,string channel,AnimationCurve data)

AnimationCurve中最核心和重要的数据是KeyFrame,对应上文说的Channel。同样的,AnimationCurve有对KeyFrame增删改查的接口。

两个KeyFrame做曲线拟合时用的是二阶或三阶贝塞尔曲线,有上文的拓展就可以轻松看懂KeyFrame的给个字段的含义和作用了。

注意,上文说的只是两个点之间做曲线拟合时的情况,实际上两个点之间的路径多种多样,可能保持不变的(Constant)、也可能是一条直线(Constant)、也可能是曲线,这就是KeyFrame的TangentMode。

在Unity中,可以通过AnimationUtility.Get/SetKeyLeftTangentMode系列接口给KeyFrame设置TangentMode。

更多曲线

当然,曲线不仅仅是Unity中可用的这几种,DoTween中的Ease曲线给我们展示了更多的曲线

而数学上的各类曲线会更多,只不过在游戏中有些用的很少罢了。

如果引入更多的曲线,我们可以给KeyFrame再添加一个CurveMode字段。因为不同的曲线所需的参数有差别,我们势必给KeyFrame添加新更多的新的参数,也即KeyFrame中必须包含所有CurveMode的所需参数的所有字段。

这是常见的解决方式,我们在面向对象编程中也经常如此,例如,在某个类中引入一个字段只为了解决某个特殊情况,大多数情况下却不需要此字段;如果多数情况下需要,在面向对象中可用继承,只在特定子类中有该字段。而这里并不是对象,也不能做成对象,因此只能包含所有的方式。

随着CurveMode越多,这必然导致很多字段是空着的,在数量极为庞大时占用很多内存却没有作用。解决该问题,就需要针对每种Curve的特点,想办法共享或合并参数,从而导致复杂度上升,也即后来者的理解成本变大了。

更长动画曲线

两个点之间的曲线是路径曲线,一系列点组成的曲线叫动画曲线AnimationCurve。他们是不同层级的对象,AnimationCurve除了KeyFrame的核心数据外,还有自己的其他数据。例如:

  • 长度——多长时间的动画
  •  WrapMode:——时间是有限的,动画长度是有限的,当前时间超过动画长度时表现是怎么样的,一般为:
    • Once——仅一次,超出时间都取0
    • Loop——循环,超出时间从开始再计
    • PingPong——来回
    • Clamp
  • 最大值
  • 最小值
  • 极值
  • 高度:最大值与最小值之差
  • 等等

曲线操作

我们可以对某个已知的动画曲线本身做一些特殊操作,例如:

  • 增加一些新的点数据
  • 删掉一些新的点数据
  • 改变曲线的高度或长度
  • 修改某些点的数据
  • 拉伸拉高或压缩压低曲线
  • 镜像反转曲线
  • 分割成多个子动画曲线

还可以对动画曲线之间做操作,例如:

  • 将两个动画曲线拼接起来合成一个新的动画曲线
  • 从一个或多个动画曲线中截取一部分拼接成新的动画曲线

这些操作都可以在代码中实现,可能要引入一些新的参数并给这些参数一些默认的值。

从代码架构上来看,我们会将这些操作作为静态方法放在AnimationCurve类中

如果某些操作不会在运行时用到,我们会将这些静态方法放在一个Utility类中以便做代码裁剪。

这些操作更多的其实更多的不是从代码上自动做的,而是要提供交互界面给人编辑的。人工编辑时相当于给参数赋值了。

编辑曲线

在Unity中,可以手动编辑动画曲线。一般来说,从动画文件中获取得到的动画曲线是不允许编辑的。

那么我们编辑动画曲线用于什么样的场景呢?这就涉及到动画曲线的本质了。

其本质就是一个值随时间变化的曲线,这个值不一定是动画中的,可以是其他任意的。

在游戏场景中,其可以是Cube的位置随时间变化的曲线。

因此,在编辑时,我们需要指定对象并指定对象中的哪个属性与曲线关联,对象可以是我们自定义的某个类,属性可以是这个类中的某个字段。

所以,AnimationClip的Get/SetCurve方法,可以变成这样:

AnimationClip.SetCurve(string relativePath,Type type,string propertyName,AnimationCurve Data)

在Unity的动画中,常用的Type是Transform,propertyName就是Transform的字段了。

拓展

AnimationCurve用于编辑时,只能对简单的对象做些简单运动,如果对象繁多且变化复杂,那么其会存在性能、内存、编辑效率等问题,在实际的工程中应用不多

但是其原理时可以扩展的,如果有一个固定的时间轴的,我们可以选择任意多个物体,及物体上任意的MonoBehaviour的属性,那么就可以编辑很多动画而不是在代码中取实现了。这就是Timeline的作用。

【参考】

Unity动画关键帧插值_unity inweight outweight-CSDN博客

相关文章:

Unity AnimationClip详解(1)

【动画片段】 前文我们介绍了骨骼动画,在Unity中骨骼动画的部分静态数据存储在SkinedMeshRender中,而另一部分动态的关键帧数据就是存储在AnimationClip中的。 关键帧数据来自与FBX、OBJ等动画模型文件,可以在动画导入后的Animation选项卡中…...

在这12种场景下会使Spring事务失效--注意防范

在某些业务场景下,如果一个请求中,需要同事写入多张表的数据,但为了保证操作的原子性(要么同事插入数据成功,要么同事插入失败),例如,当我们创建用户的时候,往往会给用户…...

SOPC:Nios II Processor -> Vectors

Reset Vector——复位向量 Exception Vector——执行向量 两个向量地址都存储着程序 1.Reset Vector 当FPGA进行复位时,FPGA就重新开始执行程序,这时就需要从EPCS中读取程序。由于FPGA的程序存放在EPC…...

golang学习笔记11——Go 语言的并发与同步实现详解

推荐学习文档 基于golang开发的一款超有个性的旅游计划app经历golang实战大纲golang优秀开发常用开源库汇总golang学习笔记01——基本数据类型golang学习笔记02——gin框架及基本原理golang学习笔记03——gin框架的核心数据结构golang学习笔记04——如何真正写好Golang代码&…...

关于汽车加油是加200还是加满的思考

车子快开了1万公里了,每个月保险会送一个200-15的加油优惠券,所以之前一直是每次加200的习惯, 最近突然觉得经常去加油很麻烦,而且很浪费时间, 需要重新评估一下, 一次加200 or 一次加300 or 一次加满&am…...

C# 去掉字符串最后一个字符的5种方法

C# 去掉字符串最后一个字符的 5 种方法 (1)Substring string original "Hello!"; string result original.Substring(0, original.Length - 1); Console.WriteLine(result); // 输出: Hello (2)Remove string o…...

[Redis] Redis中的String类型

🌸个人主页:https://blog.csdn.net/2301_80050796?spm1000.2115.3001.5343 🏵️热门专栏: 🧊 Java基本语法(97平均质量分)https://blog.csdn.net/2301_80050796/category_12615970.html?spm1001.2014.3001.5482 🍕 Collection与…...

Transiting from CUDA to HIP(三)

一、Workarounds 1. memcpyToSymbol 在 HIP (Heterogeneous-compute Interface for Portability) 中,hipMemcpyToSymbol 函数用于将数据从主机内存复制到设备上的全局内存或常量内存中,这样可以在设备端的内核中访问这些数据。这个功能特别有用&#x…...

基于SpringBoot+Vue+MySQL的垃圾分类回收管理系统

系统展示 用户界面 管理员界面 系统背景 二十一世纪互联网的出现,改变了几千年以来人们的生活,不仅仅是生活物资的丰富,还有精神层次的丰富。在互联网诞生之前,地域位置往往是人们思想上不可跨域的鸿沟,信息的传播速度…...

游戏的3C,Al

在游戏开发中,“3C”通常指的是三个重要的组成部分:Character(角色)、Camera(摄像机)和Control(控制)。这三者对于创建沉浸式和流畅的游戏体验至关重要。而AI(人工智能&a…...

深度学习基础案例4--运用动态学习率构建CNN卷积神经网络实现的运动鞋识别(测试集的准确率84%)

🍨 本文为🔗365天深度学习训练营 中的学习记录博客🍖 原作者:K同学啊 前言 前几天一直很忙,一直在数学建模中,没有来得及更新,接下来将恢复正常这一次的案例很有意思:在学习动态调整…...

tekton pipeline workspaces

tekton pipeline workspace是一种为执行中的管道及其任务提供可用的共享卷的方法。 在pipeline中定义worksapce作为共享卷传递给相关的task。在tekton中定义workspace的用途有以下几点: 存储输入和/或输出在task之间共享数据secret认证的挂载点ConfigMap中保存的配置的挂载点…...

[git操作] git创建仓库上传github报错

操作流程如下 使用 git init使用 git remote add origin 项目ssh链接git add . 报错如下 Bus error (core dumped)然后执行任何别的操作都会报错: fatal: Unable to create path .. /.git/index.lock: File exists.Another git process seems to be running in …...

飞牛fnOS安装KDE桌面

飞牛fnOS安装KDE桌面 这段时间新出的nas系统飞牛os真不错,基于debian的可折腾性又高了不少,今天就来给这个系统装个桌面,插上显示器也能当个电脑自己进自己的管理界面,播放下视频,上上网啥的。 文章目录 飞牛fnOS安装…...

运维Tips | 如何安全的移除系统中旧的Linux内核?

[ 知识是人生的灯塔,只有不断学习,才能照亮前行的道路 ] 如何安全的删除系统中旧的 Linux 内核? 描述:如果更新了 Linux 操作系统,那么你会注意到,每次升级 Linux 内核后,GRUB 菜单都会添加一个新的引导条…...

基于JAVA+SpringBoot+Vue的工程教育认证的计算机课程管理平台

基于JAVASpringBootVue的工程教育认证的计算机课程管理平台 前言 ✌全网粉丝20W,csdn特邀作者、博客专家、CSDN[新星计划]导师、java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ 🍅文末附源码下载链接…...

软件质量保障:故障演练介绍

目录 背景:架构变化带来的问题 什么是故障演练 为什么需要故障演练 故障演练场景有哪些 不同演练类型和目标 如何对工具进行评估 功能评测项 告警评测项 观测指标评测项 总结 背景:架构变化带来的问题 随着架构越来越复杂、应用越来越多样&…...

Vue3可编辑表格插件

插件亮点: 通过可自定义单元格内容。可控的状态控制,例如只读、禁止编辑行等配置。可控的状态交互,例如框选单元格、框选行等配置。可控的推拽顺序,例如拖拽行、推拽列。方便的单元格数据验证配置。方便的快捷右键菜单&#xff0c…...

RedisTemplate操作Redis

文章目录 1. String 命令1.1 添加缓存1.2 设置过期时间(单独设置)1.3 获取缓存值1.4 删除key1.5 顺序递增1.6 顺序递减1.7 常用的 2. Hash命令2.1 添加缓存2.2 设置过期时间(单独设置)2.3 添加一个Map集合2.4 提取所有的小key2.5 提取所有的value值2.6 根据key提取value值2.7 获…...

Web安全:SQL注入实战测试.(扫描 + 测试)

Web安全:SQL注入实战测试. SQL注入就是 有些恶意用户在提交查询请求的过程中 将SQL语句插入到请求内容中,同时程序的本身对用户输入的内容过于相信,没有对用户插入的SQL语句进行任何的过滤,从而直接被SQL语句直接被服务端执行&am…...

猜测、实现 B 站在看人数

猜测、实现 B 站在看人数 猜测找到接口参数总结 实现 猜测 找到接口 浏览器打开一个 B 站视频,比如 《黑神话:悟空》最终预告 | 8月20日,重走西游_黑神话悟空 (bilibili.com) ,打开 F12 开发者工具,经过观察&#xf…...

网络编程(UDP)

UDP编程 UDP:全双工通信、面向无连接、不可靠 UDP(User Datagram Protocol)用户数据报协议,是不可靠的无连接的协议。在数据发送前,因为不需要进行连接,所以可以进行高效率的数据传输。 适用场景 发送小尺寸…...

CENet及多模态情感计算实战(论文复现)

CENet及多模态情感计算实战(论文复现) 本文所涉及所有资源均在传知代码平台可获取 文章目录 CENet及多模态情感计算实战(论文复现)概述研究背景主要贡献论文思路主要内容和网络架构数据集介绍性能对比复现过程(重要&am…...

备战秋招60天算法挑战,Day34

题目链接: https://leetcode.cn/problems/coin-change/ 视频题解: https://www.bilibili.com/video/BV1qsvDeHEkg/ LeetCode 322.零钱兑换 题目描述 给你一个整数数组coins,表示不同面额的硬币;以及一个整数amount,表…...

vue实现评论滚动效果

vue插件实现滚动效果 一、安装组件 官网地址&#xff1a;https://chenxuan0000.github.io/vue-seamless-scroll/ 1、vue2安装 npm install vue-seamless-scroll --savevue3安装 npm install vue3-seamless-scroll --save二、组件引入 <template><div v-if"…...

iphone13 不升级IOS使用广电卡

iPhone13的信号&#x1f4f6;&#xff0c;15系统刷高版本iPCC&#xff0c;本帖以后不再更新&#xff01;&#xff01;&#xff01; 自从知道可以通过刷iPCC的方式改善信号(不更新iOS大版本的情况下)&#xff0c;尝试了各种版本。 我自己用下来总结 - 移动联通48、49、50 &…...

网络地址转换

文章目录 1. NAT使用环境2. NAT的优缺点3. NAT的三种类型4. NAT工作原理5. 配置示例6. 常用排错命令 1. NAT使用环境 需要连接到互联网&#xff0c;但主机没有全局唯一的IP地址&#xff1b;更换的ISP的要求对网络进行重新编址&#xff1b;需要合并两个使用相同编址方案的内联网…...

【python】 @property属性详解 and mysql的sqlalchemy的原生sql

【python】 property属性详解 一文搞懂python中常用的装饰器&#xff08;classmethod、property、staticmethod、abstractmethod…&#xff09; sqlalchemy的原生sql from sqlalchemy import create_engine from sqlalchemy.orm import sessionmaker# 数据库连接字符串 DATAB…...

西门子WinCC开发笔记(一):winCC西门子组态软件介绍、安装

文为原创文章&#xff0c;转载请注明原文出处 本文章博客地址&#xff1a;https://hpzwl.blog.csdn.net/article/details/142060535 长沙红胖子Qt&#xff08;长沙创微智科&#xff09;博文大全&#xff1a;开发技术集合&#xff08;包含Qt实用技术、树莓派、三维、OpenCV、Op…...

如何在5个步骤中编写更好的ChatGPT提示

ChatGPT是一个风靡全球的生成式人工智能 (AI) 工具。虽然它有可能编造一些东西&#xff0c;但是通过精心设计提示&#xff0c;可以确保获得最佳结果。在这篇文章中&#xff0c;我们将探讨如何做到这一点。 在本文中&#xff0c;我将向你展示如何编写提示&#xff0c;激励驱动C…...