黑龙江省建设教育信息网站,广西桂林天气预报15天查询,聊城宏远网站建设优化,唐山网站建设制作【动画片段】
前文我们介绍了骨骼动画#xff0c;在Unity中骨骼动画的部分静态数据存储在SkinedMeshRender中#xff0c;而另一部分动态的关键帧数据就是存储在AnimationClip中的。
关键帧数据来自与FBX、OBJ等动画模型文件#xff0c;可以在动画导入后的Animation选项卡中…【动画片段】
前文我们介绍了骨骼动画在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?vaVwxzDHniEw
应用
基本上三阶贝塞尔曲线就够用了unity中各类曲线的编辑、游戏中道路、水管等的建设、移动轨迹、配置数据等都可以用到贝塞尔曲线
拓展
可以看到几个点即可描述一条贝塞尔曲线。描述二阶贝塞尔曲线需要三个点描述三阶贝塞尔曲线需要四个点。
如果只有两个已知点怎么构造二阶贝塞尔曲线。
必须借助这两个点和其他默认数据算出默认的第二个点已知P1 P3,需要得到P2例如
第二个点和这两个点构成等边三角形第二个点在这两个点连线的中间和连线的距离是连线的长度即构成等腰三角形第二个点在这两个点连线的中间和连线的距离是连线的长度* t 。对于不同组的两个点t可以始终是一个默认的值也可以是根据其他因素算出来的一个值。这里额外引入了一个参数第二个点和两个点的水平距离通过参数t1控制和连线的距离通过参数t2控制。对于不同组的两个点t1、t2可以始终是一个默认的值也可以是根据其他因素算出来的一个值。这里又额外引入了一个参数上述方式不能控制曲线的倾斜需要通过斜率来控制。引入两个参数t1、t2分别表示两个点的斜率斜率的连线交点为第三个点。对于不同组的两个点t1、t2可以始终是一个默认的值也可以是根据其他因素算出来的一个值获取从某个地方读取/获取的值。
按照上述方式只有三个已知点也可以构造出三阶贝塞尔曲线。
如果只有两个已知点怎么构造三阶贝塞尔曲线。可想而知需要有更多的参数来计算出其他两个点
P2、P3和已知的两个点构成正方形P2、P3和已知的两个点构成长放形其宽为长度*tP2、P3和已知的两个点构成等腰梯形其高为长度*t1另一个底为长度*t2P2、P3和已知的两个点构成梯形其高为长度*t1,P2和P1的距离为长度*w1,P3和P4的距离为长度*w2。这里又引入了额外的w1和w2两个权重参数在4的基础上引入斜率参数t1和t2以替代高P2、P3和已知的两个点构成四边形在5的基础上对x和y采用相同的权重在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——仅一次超出时间都取0Loop——循环超出时间从开始再计PingPong——来回Clamp最大值最小值极值高度最大值与最小值之差等等
曲线操作
我们可以对某个已知的动画曲线本身做一些特殊操作例如
增加一些新的点数据删掉一些新的点数据改变曲线的高度或长度修改某些点的数据拉伸拉高或压缩压低曲线镜像反转曲线分割成多个子动画曲线
还可以对动画曲线之间做操作例如
将两个动画曲线拼接起来合成一个新的动画曲线从一个或多个动画曲线中截取一部分拼接成新的动画曲线
这些操作都可以在代码中实现可能要引入一些新的参数并给这些参数一些默认的值。
从代码架构上来看我们会将这些操作作为静态方法放在AnimationCurve类中
如果某些操作不会在运行时用到我们会将这些静态方法放在一个Utility类中以便做代码裁剪。
这些操作更多的其实更多的不是从代码上自动做的而是要提供交互界面给人编辑的。人工编辑时相当于给参数赋值了。
编辑曲线
在Unity中可以手动编辑动画曲线。一般来说从动画文件中获取得到的动画曲线是不允许编辑的。
那么我们编辑动画曲线用于什么样的场景呢这就涉及到动画曲线的本质了。
其本质就是一个值随时间变化的曲线这个值不一定是动画中的可以是其他任意的。
在游戏场景中其可以是Cube的位置随时间变化的曲线。
因此在编辑时我们需要指定对象并指定对象中的哪个属性与曲线关联对象可以是我们自定义的某个类属性可以是这个类中的某个字段。
所以AnimationClip的Get/SetCurve方法可以变成这样
AnimationClip.SetCurve(string relativePath,Type type,string propertyName,AnimationCurve Data)
在Unity的动画中常用的Type是TransformpropertyName就是Transform的字段了。
拓展
AnimationCurve用于编辑时只能对简单的对象做些简单运动如果对象繁多且变化复杂那么其会存在性能、内存、编辑效率等问题在实际的工程中应用不多。
但是其原理时可以扩展的如果有一个固定的时间轴的我们可以选择任意多个物体及物体上任意的MonoBehaviour的属性那么就可以编辑很多动画而不是在代码中取实现了。这就是Timeline的作用。
【参考】
Unity动画关键帧插值_unity inweight outweight-CSDN博客