在本系列文章中,我将讨论我们决定如何以及为什么决定创建自己的用于将Flash动画导入Unity的解决方案,以及优化和所得插件的内部工具。 并且在程序中:有关swf格式内部,Unity编辑器扩展的功能以及一般而言有关动画的故事。 我要求减价!

引言
在任何项目的开始,都是其许多部分的技术选择难题。 这样的部分就是动画系统。 此选择取决于几个变量。 首先,您的动画师通常使用(可以)使用哪些工具,或者更容易找到这些动画师的哪些工具。 显然,选择一种非常特殊的产品是没有意义的,因为如果现有员工出于某种原因或另一种原因(突然的公车?)离职,将很难更换它们,项目的一部分将无限期地停止,就商业产品而言可能非常昂贵。 第二个变量更具技术性:集成到您的引擎中。 是否有第三方解决方案以及这些解决方案的质量,是否有创建我自己的实力和能力以及上述所有方面的生产力和便利性。 好吧,第三个变量是该工具的功能,因为如果您鼻子的血液需要逆运动学 ,那么选择非骨骼动画完全是奇怪的。
2D动画的类型
考虑在游戏中实现动画的最受欢迎的选项。 我只会谈论2D动画,3D动画-这是完全不同的对话,方法和工具的主题。 如果您精通这些方面(不会有任何启示,也不会揭开面纱),或者如果您打算仅查看Flash动画的功能,则可以安全地跳过一部分。
框架动画
我们的迷你评论以最简单和最早的动画类型开始:逐帧动画。 此类动画的每一帧都由单独的图片表示,其快速变化会产生运动的错觉。
优点:
基本实现,通常在任何引擎中。 动画的任何复杂性和样式,至少都要进行布局并将影片插入帧中(当然,理论上是这样)。 任何工具:几乎所有工具都可以为您提供一系列框架,如有必要,可以将它们粘贴到一个纹理图集中。
缺点:
当然,主要的缺点是内存中这些东西的大小。 自然地,有各种各样的技巧,例如在块中剪切帧,随后重复使用重复的块,从磁盘后台加载帧并释放已经显示的内容,等等。 但是这些技巧有其缺点。 块切片-仅适用于像素艺术 ,背景加载-会在磁盘上产生额外的负载,在磁盘本身上占用大量空间,并且游戏的大小会增加,并且要开始或更新的下载数据量也会增加。
总计:
仅适用于小尺寸和数量的帧动画,非常适合像素艺术和NES复古游戏的风格。
影片动画
作为逐帧动画疯狂的顶峰,视频动画出现了。 是的,它被使用,甚至偶尔使用。 我们在“ 隐藏对象 ”类型的游戏中实现了此功能,适用于大型和复杂的动画,以及具有真实感的剪切场景 。 借助theora和vp8等编解码器,您可以自己实现可忍受的视频动画(甚至使用alpha通道 )。
当然,这里的优点是,您可以用这种动画来显示您内心所希望的一切,从具有真实演员参与的定向场景到渲染的3D战斗。 缺点是解码时会给CPU造成巨大负担,当然还有图像质量。 您可以在非常有限和特定的情况下找到质量和性能之间的折衷,例如,在上面针对“隐藏对象”指出的游戏中。
一般而言,如果我们不考虑渲染的剪裁场景,那么它就远非适合所有人,甚至永远也不是,甚至几乎永远不会适合-这种“动画”的地方在哪里。 此类动画的良好实现也不能自称为简单,在输出图像的质量和中央处理器上收到的负载方面,都有很多细微差别和技巧。 由于特殊性,现成的实现很少,它们的质量也没有差别,或者它们花费了很多钱(嗨, Bink_Video ),所以有很多是自己编写的。
骨骼动画
因此,我们了解了流行和现代的2D动画类型。 骨骼动画越来越吸引2d领域开发人员的心,成为游戏的标准动画。 有趣的是,它们相对较近地在2d中出现得如此广泛,而与3d不同,它们出现在豌豆王时代,当时父亲们写了《 半条命》 。 奇怪的是,骨骼动画的本质是在骨骼中,该骨骼由通过树形结构相互连接的骨骼创建了一个动画制作者。 碎片以单独图片的形式附着在骨骼上。 整个结构通过骨骼相对于彼此的位移和旋转而运动。 骨骼中骨骼的位移和旋转遵循一般动画时间线。
优点是显而易见的:我们不需要以单独图片的形式存储动画的每一帧,只需存储片段(例如角色的手臂和腿)即可移动骨骼的骨骼。 这种类型的动画具有出色的实现,可以为必要的平台和引擎提供运行时,并且使用价格合理甚至免费: Spine , Spriter , Anima2D , DragonBones等。 由于骨骼及其功能,可以通过插补骨骼的位置,混合动画到动画之间的过渡以及什至混合两种不同的动画来实现动画的超大平滑度:在奔跑中拍摄,在潜行中拍摄等等。 我不会列出骨骼动画的所有可能性,确实有很多可能性,而且它们非常酷。 我最好提供一个链接,使它们在Spine网站上清晰地显示有图片和说明。
它看起来像银弹,但是没有。 有一些缺点。 立即对许多项目和动画做出保留-这些缺点可能不存在。 如果骨骼动画适合您-很好,请使用它们,它们既美观又现代。
让我们回到缺点。 制作动画的另一个阶段是装备(或像我们行业中的许多术语一样,装备也被俄罗斯化了)。 骨骼的创造以及骨骼与碎片的结合。 如果对象具有一个动画而不是多个动画(例如,“ Platformer ”类型的游戏角色),则该阶段是完全多余的。 并非所有动画都可以方便地使用骨骼进行动画处理。 在动画制作方面和游戏性能方面,有很多动画会阻碍骨骼的前进,没有动画,它们会更加方便快捷。 一个单独的项目,我将聘请市场专家。 随着骨骼动画的普及,它们的数量正在增长,但是要在团队中找到它们仍然不是一件容易的事。 工具和工具的传播也会影响到某个人习惯并使用一个人,另一个人,因此必须在旅途中进行再培训,这给寻找和培训员工带来了更多困难。
时间轴动画
我将这种动画放到了最后,不是因为它比其他所有人都好,而是因为它只是内部的非常生动的动画,将在本文的技术部分进行讨论。 Flash不是此类动画的唯一代表,但肯定是其领导者,因此将针对它进行描述。 时间轴动画由片段组成,这些片段在时间轴上的不同图层中进行了动画处理。 几乎与骨骼动画中看到的一样,但是没有骨骼。 根据时间轴上的关键帧,我们可以在这些帧中移动,旋转,替换,绘制新片段,当然还可以在它们之间进行连字符,比例缩放和旋转。 也就是说,我们不移动附着有碎片的骨头,而是碎片本身。 当然,我们失去了骨骼动画给我们的许多机会,但并不是每个人都需要它们。 作为交换,我们收购了其他人,例如:
- 在动画中间绘制和插入新片段的能力;
- 没有钻机,没有骨头有时只会干扰;
- Adobe Flash(现为Adobe Animate )是多年以来最古老,最成熟的动画工具,拥有大量的动画师(一种或多种)。
- 在将Flash项目转换为Unity时可以使用旧动画的功能,包括以矢量图形而不是光栅 图形制作的动画。
闪光灯死了吗? 作为浏览器中的播放器-当然,作为动画工具-仍然存在,没有其他选择,而且这是不期望的。 缺点? 当然可以 主要的缺点是,这不是骨骼动画,是吧。 我们被剥夺了将动画彼此混合的机会,我们没有反向运动学和仅骨骼固有的类似功能。 但是,我们有彼此嵌套的时间线,以及栅格和矢量蒙版! 所有动画师都喜欢面具! 这里值得一提的是,在Spine中,最近也出现了剪裁功能,但是到目前为止,与闪光灯蒙版相比,它只有几何形状并且非常有限。
动画类型摘要
自然,我没有列出所有类型的动画,仅列出了主要的动画。 例如,所谓的程序动画被遗忘了,当对象或它们的片段仅通过代码设置为运动时,却无法将它们与其他对象进行对比,而我并不是真的想对文章进行充气,最好观看一些更专门的动画完成图片。
总结一下。 像往常一样,几乎在所有方面,没有万灵药。 您需要选择项目,任务和人员。 毕竟,没有人限制任何人使用一种动画。 到处都有可以同时使用所有类型的动画的项目,每个人都很高兴。 剪切场景-视频,角色-骨骼,风景-时间线, 粒子系统发射器的飞行-程序,粒子本身在系统中-逐帧显示。 我们从每种类型中获取最佳。
选择的痛苦和写自己的决定的痛苦
好吧,出于某些客观原因,我们决定为我们的项目需要动画。 出现了将它们集成到引擎中的问题。 在尝试了多种插件选项之后,我们选择了一个与其功能相匹配的组件,该组件还存在并且仍受支持,而且许可间许可的价格也完全不高,但是您可以容忍一个好的产品。 我故意不给任何名字,以免给任何人做任何广告或反广告。
有了这个插件,我们住了将近一年的开发时间,在此期间澄清了细节,我们无法进一步使用。 即将其质量整合到Unity中。 坦率地说,所有这些都导致了严重的错误,而这些错误不能说轻松,迅速且在开发人员身边无法解决,目标设备的性能也很差 ,我们仍然拥有iPad 2级别。 之所以发生这种情况,是因为未将其集成到特定的引擎中,对它的细节和缺陷的无知,我们对这种可能性非常满意,但是源代码(即使是个人命令)也拒绝公开,因此决定编写我们的决定。
我有一些相当成功的为Unity编写扩展的经验,并且几乎没有将Flash动画转换为我的格式的经验。 后者是很久以前的,但有一些回忆。 在同一地方,决定将所有内容都编写为一个家庭项目,以免依赖于我所从事的项目或公司,老实说,我也想拥有自己的产品。 因此,有了250页的swf格式规范 ,我开始战斗了。
导出选项
首先,值得讨论从Flash编辑器导出动画的选项。 其中有几种,当然有其优点和缺点。 让我们浏览主要内容。
.xfl格式
在Flash动画编辑器中,可以将动画源保存为未压缩的.xfl格式,而不是默认提供的封闭的.fla格式。 格式很简单,但是没有记录。 它由几个嵌套目录和一堆.xml文件组成,其中描述了存储在其中的剪辑的所有状态。 描述格式也很简单明了,下面是一个示例:
static_clip.xml
<DOMSymbolItem name="static_clip" itemID="5c719f28-00000051" lastModified="1550950184"> <timeline> <DOMTimeline name="static_clip"> <layers> <DOMLayer name="Layer_1" color="#00FFFF" current="true" isSelected="true"> <frames> <DOMFrame index="0" keyMode="9728"> <elements> <DOMBitmapInstance selected="true" libraryItemName="bitmap.png"/> </elements> </DOMFrame> </frames> </DOMLayer> </layers> </DOMTimeline> </timeline> </DOMSymbolItem>
链接到源在这里,我们有一个静态剪辑static_clip
,它具有一层Layer_1
和一帧,其中有一个名为bitmap.png
的位图。
movie_clip.xml
<DOMSymbolItem name="movie_clip" itemID="5c719f30-00000053" lastModified="1550950713"> <timeline> <DOMTimeline name="movie_clip"> <layers> <DOMLayer name="Layer_1" color="#00FFFF" current="true" isSelected="true"> <frames> <DOMFrame index="0" duration="4" tweenType="motion" motionTweenSnap="true" keyMode="22017"> <elements> <DOMSymbolInstance libraryItemName="static_clip"> <matrix> <Matrix tx="-50" ty="-50"/> </matrix> <transformationPoint> <Point x="28.5" y="27.5"/> </transformationPoint> </DOMSymbolInstance> </elements> </DOMFrame> <DOMFrame index="4" tweenType="motion" motionTweenSnap="true" keyMode="22017"> <elements> <DOMSymbolInstance libraryItemName="static_clip" centerPoint3DX="128.5" centerPoint3DY="127.5"> <matrix> <Matrix tx="100" ty="100"/> </matrix> <transformationPoint> <Point x="28.5" y="27.5"/> </transformationPoint> </DOMSymbolInstance> </elements> </DOMFrame> </frames> </DOMLayer> </layers> </DOMTimeline> </timeline> </DOMSymbolItem>
链接到源在这里,我们描述movie_clip
动画剪辑,其中包含两个索引分别为0和4的关键帧。 框架在坐标(-50;-50)
和(100;100)
处包含我们的静态剪辑static_clip
。 在帧之间有补间动画(过程动画,在我们的情况下仅为过渡,没有缩放和旋转),可以通过线性插值这些帧中的坐标来获得关键帧之间的静态剪辑的位置。
自然,所有内容都将以真实的动画呈现,等等……更加复杂和庞大,但是您无需借助文档即可了解所有内容。 看来这里就是幸福。 我知道有几个项目和公司很成功地采用了这种方法,但是有一些局限性和困难。 这些困难是.xfl美中不足的地方,即:
- 补间(补间,可能会被Russified)非常不同,既简单:使用线性插值,又更复杂:使用用户定义的函数和该插值图,对双胞胎上的矢量图形进行变形,它们仅具有针对Macromedia和Adobe的规则;
- 例如,矢量图形与除光栅图形之外的所有动画一样,都以文本形式描述。 因此,有必要编写自己的光栅化程序,由于矢量的复杂性和Flash特性,实际上无法实现。
- 播放动画(包括嵌套动画)的规则需要您从头开始思考,没有相关文档以及何时显示哪个帧,在什么情况下需要调整哪些内容以及在没有插值的情况下应该保留哪些内容-仍然需要通过长时间的实验来确定,试图涵盖所有可能的情况。测试运行,对于一般而非私有解决方案而言,这是非常重要的练习。
这些并不是这种方法的全部困难,但是足以理解,对于一般解决方案,它仅适用于强烈保留的情况。 曾几何时,我沿着这条路参加了我参与的项目之一。 有些Flash剪辑场景,包括经典的Twins(没有自定义插值规则和该插值的特殊功能),只有位图图形,而没有Flash编辑器提供的其他高级功能。 专用解决方案的编写速度非常快而有效,但是动画制作人员必须受到严格的限制,以免使用任何复杂的东西。 这种方法具有生命权,并且基于它有几个不同程度的疏忽的图书馆,但是,正如我所说,它有很多保留和局限性。
.jsfl脚本
另一个几乎可行的选择是获取有关动画的信息。 jsfl-scripts允许您扩展Flash编辑器,与其环境交互,更改动画,当然还可以获取有关其中的时间线,图层,剪辑和帧的所有必要信息。 动画师还使用它来自动执行各种动作,但这是相邻书籍的故事。 总的来说,该方法具有前一种方法的所有缺点,因此我不再赘述,只能说,您可以以逐帧动画的形式卸载整个动画,而这并不是真正的绝地武士的方法(但自然地,发生在某些项目中)。 我们将以我选择的方法返回这些脚本:光栅化矢量图形和优化分页动画。
AIR应用
但是这种方法已经非常有效,可以按需使用。 我没有选择它,但是作为参考,我将给出此选项。 这种方法的本质是,我们在Flash本身上创建一个AIR应用程序,或者例如对于Haxe在审美上创建AIR应用程序,这将从已编译为swf格式的动画中获取所有信息。 我们在应用程序中逐帧播放动画,获取所有帧信息并将其保存为运行时所需的格式。 这里解决了以上所有问题:
- 无需对矢量图形进行栅格化,Flash运行时将为我们完成此任务,仅保留此信息并保存矢量片段的栅格化结果即可;
- 无需提出播放规则并处理自定义双插值,AIR应用程序中的Flash Player确切知道如何执行此操作,几乎可以为我们做所有事情;
另外,我们有机会在动画中使用帧脚本(各种play()
, stop()
和其他gotoAndPlay()
,某些动画师都非常喜欢它们)。 缺点是无法将循环的动画导出到Flash本身,但这没关系,因为您已经可以在运行时循环它们,并要求动画制作者为此做好准备。
值得一提的是,我肯定所有内容都会比我的描述中所讲的要复杂一些,因为我个人没有去过那里,所以我无法告诉您详细信息。 让那些走过这条路的人分享他们的经验,我们很高兴地阅读他们的喜悦和苦难!
自己的Flash Player
这可能是所有方式中最诚实,最明显和最直接的选择,但也是最困难的。 最后,这是一个真正的Flash播放器的功能,我们大多数人在我们最喜欢的浏览器中都有它作为其补充。 这种类型的流行代表: gameswf,并且是从上一个版本扩展到scaleform的 。 现在两个都死了。 有趣的是,它们主要用于在各种项目(包括AAA)中实现GUI 。 但是我们对内部结构感兴趣,而不是对失效库的特定应用感兴趣。
任何自重的Flash播放器至少应具备以下功能:
所有这些要点都大声疾呼,如果有可能实现,那么您将不得不更换一个以上的团队来完成此任务,并将数年的开发工作交换到最低版本。 即使只是执行一些足以发挥作用的小部分,也将花费大量时间,而其他困难(以所有未完成部分的形式存在)将使这个本来就很庞大的时期加倍。
是的,网络上充斥着各种不同程度的疏忽和加重的尝试来实现这些部分。 但是,甚至不可能以这些开发为基础,只是要花很多年才能发现错误,并编写未编写的单元测试,然后才意识到它无法完全发挥作用。 那些试图用必要功能补充gamewf的人了解我在说什么。 一个诚实的Flash播放器还必须提前而不是在运行时光栅化矢量图形,这将影响性能,以至于并非每个项目都可以生存。 我借此机会向在移动设备上使用scaleform的幸存者表达我的问候。 总的来说,最令人沮丧和绝望的方式,不是我们的选择。
我们结合方法
所以终于有了我的选择。 研究了可能的方法后,为我绘制了一个有趣的组合。 实施起来相对简单,对一个人而言可行,同时功能足以满足大多数2d时间轴动画的需求。
首先,我们将使用swf中编译的动画,以便不提供各种播放选项,并且几乎永远不会猜测Flash播放器是如何从内部工作的,因为我们无法涵盖所有可能的情况,但是我们尝试编写一个通用的解决方案。 而且,Flash编辑器摆脱了动画中使用的所有孪生子,在已编译的swf中巧妙地表示了片段的裸露位置。
其次,我们禁止在动画中使用脚本。 是的,这是一个非常艰巨而可悲的要求,但是我没有一个程序员团队来实现一个诚实的虚拟机和一个我几乎必须盲目编写的标准库。 当然,在这里,与使用AIR应用程序的方法相比,我的方法的缺点很明显,因为在后者中,可以使用一些用于内部动画播放的脚本。 另一方面,它不会干扰制作好的动画。 要将自定义信息从动画传递到游戏,可以使用所谓的frame labels
(从代码中可以看到它们),也可以将自定义事件以已经使用运行时的回调函数的形式挂在特定框架上。
第三,我们放弃编写自己的矢量图形光栅化程序,因为它无法为一般情况编写一个矢量化光栅化程序,以涵盖Flash中矢量的所有选项和功能。 相反,我们将使用jsfl脚本将图形栅格化为(!)编译。 同时,在此脚本的帮助下,我们将通过将静态剪辑合并到一张图片中来优化矢量图形,根据我们的需求(质量和/或性能)减少或增加生成的位图图像。 在此还值得回顾一下屏幕上像素密度不同的设备(例如,高清和标清图片)的质量/分辨率的各种选项。
歌词部分的结论
到此结束本文的第一部分。 在第二部分中,将提供技术实施细节,代码段,图片(!),优化,技巧和窍门。 即将在您的屏幕上出现,请不要错过! 最后,我将提供一些有趣的链接,其中包括同事(幸运的是ZeptoLab和Playrix)提供的有关使用动画的信息。