从头开始在Unity3D中进行动画制作。 第一部分,抒情

在本系列文章中,我将讨论我们如何以及为什么决定为Flash动画导入Unity创建自己的解决方案,以及优化技术和插件的内部工作方式。 我还有很多其他有趣的东西要讲:SWF格式的内部,Unity编辑器扩展的特殊功能以及动画的一般性问题。 您会在里面找到所有内容!




引言


当您开始进行任何项目时,始终必须面对为大多数组件选择技术的问题。 这些组件之一当然是动画系统。 现在,有一些变量将取决于您的选择。 您必须问自己的第一个问题是您的动画师曾经使用过哪些工具(可以使用),以及哪些工具更容易找到。 当然,选择非常具体的产品没有多大意义。 在这种情况下,如果您的员工由于某种原因离职(或被公共汽车撞到),将几乎不可能更换他们。 因此,基本上,您的项目(无论是全部还是部分)将无限期停止,这可能会非常昂贵,尤其是对于商业产品而言。 第二个变量是技术性更强的变量,它是将工具集成到引擎中的变量。 是否有任何第三方解决方案,这些解决方案的质量如何? 您是否有机会和能力来创建自己的解决方案? 当然,以上所有方面的效率和便利性也至关重要。 第三个变量是工具的功能。 因此,如果您确实确实需要逆运动学 ,那么您更偏爱非骨骼动画会很奇怪。


2D动画的类型


现在,让我们看一下游戏中最流行的动画技术。 我将只考虑2D动画,因为3D动画是一个完全不同的对话主题,它具有自己的方法和工具。 如果您精通此事,请跳过本节(相信我,不会有任何启示或大开眼界)。 同样,如果您只是在这里看到Flash动画的功能,则可以在下面的文章中找到它们。


逐帧动画


开始我们的迷你审查,这是最简单,最古老的动画类型:逐帧动画。 在此动画中,每一帧都由一幅单独的图片表示,并且通过快速地更改这些图片,我们得到了运动的错觉。


优点:


基本实现,通常可用于任何引擎。 任何动画的复杂性和样式,您甚至都可以插入整个电影帧(当然,从理论上讲)。 任何工具:几乎所有工具都可以为您提供一系列框架,如果需要,您可以将它们组合成一个纹理图集。


缺点:


当然,主要缺点是所有这些东西需要的内存量。 当然,您可以使用各种技巧,例如在块中剪切帧,然后重用重复的块,从磁盘中后台加载帧并删除已显示的帧,等等。 但是请记住,所有这些技巧也都有其自身的缺点。 例如,切块只能用于像素艺术 ,背景加载会在磁盘上施加额外的压力,在此磁盘上占用大量空间,从而增加了游戏本身的大小以及所需的数据量供用户启动或更新它。


底线:


仅适用于很小的动画帧,非常适合像素艺术和NES等复古风格的游戏。


影片动画


基本上,视频动画是逐帧疯狂的典范。 是的,它仍然适用,有时甚至是正确的。 我们已经在“ 隐藏对象”游戏中实现了类似的功能,以实现庞大而复杂的动画,以及具有真实感的过场动画 。 添加到诸如theoravp8这样的编解码器中,您将能够创建自己的非常漂亮的视频动画(即使使用alpha合成 )。


当然,这种动画的好处是,您几乎可以创建任何东西,从带有真实演员的舞台管理场景到渲染的3D战斗。 坏的是解码时CPU上的负担很大,当然还有图像质量。 仅在有限数量的特定情况下才能实现质量与效率之间的折衷,例如在上述“隐藏对象”游戏中。

因此,无论如何,视频动画并不适合所有人,而且并非总是适用,确切地说,几乎从来没有。 基本上,这种动画仅适用于渲染的过场动画。 如果您能够很好地实现这种动画,那肯定不会小菜一碟。 关于输出图像的质量和CPU负载,有许多细微差别和技巧需要牢记。 由于这种特殊性,很少有现成的实现,这些实现要么不够好,要么花费很多钱(您好, Bink_Video ),所以大多数人都喜欢编写自己的实现。


骨骼动画


因此,让我们转向更流行和现代的2D动画类型。 骨骼动画越来越赢得2D开发人员的关注,越来越接近游戏动画标准。 有趣的是,它们实际上相对较早地在2D中变得如此流行,不像3D那样,当我们的祖先创造《 半条命》时,它们回溯了很久。 令人惊讶的是,骨骼动画的本质是由动画师创建的骨骼。 基本上,骨骼是一组相互连接并形成树结构的骨骼。 骨骼被束缚在一起,由单独的图片表示。 当骨骼相对于彼此移动或旋转时,此构造开始移动。 骨骼骨骼的移动和旋转会遵循常规动画时间轴。


优点很明显:我们不必将每个动画帧都存储为单独的图像,而只需将骨骼移动的各个部分(例如角色的腿和手臂)存储起来即可。 这种动画的一些非常酷的实现,带有针对不同平台和引擎的运行时,价格实惠甚至免费: SpineSpriterAnima2DDragonBones等。 使用骨骼及其特征,可以通过对骨骼的位置进行插值来获得令人难以置信的动画平滑度。 您也可以在场景之间切换,甚至混合两种不同类型的场景:跑步时拍摄,爬行时拍摄等等。 我不会列出骨骼动画的所有可能性,因此您要记住,确实有很多可能性,而且它们真的很酷。 而且,我希望为您提供Spine网站的链接, 其中您可以通过图片和说明来很好地了解所有这些内容


看起来我们已经有了我们的全能士兵,但还不是刚刚。 它也有自己的缺点。 我必须马上说,对于许多项目和动画,这些缺点甚至可能不存在。 因此,如果骨骼动画对您有用-那就太好了,请使用它,因为它看起来确实很现代。


现在,让我们回到弊端。 骨骼动画需要额外的步骤-装配。 这是关于骨架的创建以及绑定骨骼和碎片的信息。 如果您的对象只有一个动画,而没有几个动画(例如, 平台游戏的角色),则此步骤对您没有用。 您还应该记住,并非所有场景都可以轻松地用骨骼动画。 实际上,有很多例子可以说明,如果没有这些例子,那么在创建和最终游戏性能方面都会更好。 我还要指出市场上专家的存在是一个重要因素。 当然,随着骨骼动画的日益普及,它们的数量也在增加,但要使它们加入团队可能仍然不容易。 各种工具也在发挥作用。 每个人都有自己的喜好,仍然需要保持灵活性和随时学习的能力,这在招募和培训员工方面造成了更多困难。


时间轴动画


我之所以最后保存这种动画,并不是因为它比其他所有动画都要好,而是因为它主要由Flash动画表示,我们将在本文的技术部分中讨论它的内幕。 现在,Flash并不是这种动画的唯一代表,但无疑是领先的,因此从现在开始,我将描述与之有关的所有内容。 时间轴动画场景包括在时间轴上不同层中进行动画处理的片段。 基本上,它与我们在骨骼动画中看到的几乎相同,但没有骨骼。 使用时间轴上的关键帧,我们可以在这些帧中移动,旋转,替换和添加新片段,当然还可以在它们之间插入过渡,放大和旋转。 这意味着在这种情况下,我们不移动碎片附着的骨骼,而是移动碎片本身。 当然,它使我们失去了骨骼动画的许多可能性,但是正如我之前所说,并不是每个人都需要它们。 此外,我们还具有其他优势,例如:


  • 在场景中间添加和插入新片段的能力;
  • 没有索具,没有骨头,有时候只是不必要的;
  • 最古老且经过多年动画工具测试的Adobe Flash(当前为Adobe Animate ); 大量动画师可以以一种或多种方式使用它;
  • 将Flash项目转移到Unity时可以使用旧场景的功能,包括您在矢量图形中创建的场景,而不仅仅是在光栅中

您可能会问,Flash是否死了? 是的,作为浏览器播放器,但是作为动画工具,它仍在游戏中。 而且,我将告诉您更多信息,现在或没有其他替代Flash可以预见。 有什么缺点吗? 当然可以 而且主要缺点是……这不是骨骼动画,哈哈。 因此,我们正在失去将场景彼此混合,逆运动学以及仅在使用骨骼时才能使用的其他一些功能的功能。 但是我们彼此之间有了时间表,还有栅格和矢量蒙版! 动画师只是喜欢面具! 在这里必须注意,Spine最近还添加了剪裁选项 ,但是到目前为止,它们只是几何形状,与Flash蒙版相比受到严格限制。


动画类型:摘要


当然,我列出的不是所有类型的动画,而是主要的。 例如,当对象或片段仅由代码驱动时,我就留下了所谓的过程动画,但几乎无法将其与其他对象进行比较,并且我不想使文章太长而无法阅读。 我想,现在最好是仔细研究动画方面更专业的东西来完成图片。

因此,总结一下。 像往常和其他地方一样,这里没有通用士兵。 您需要根据项目,任务和人员来选择解决方案。 毕竟,谁说您一次只需要使用一种类型的动画? 在很多项目中,我们可以看到所有使用的动画类型,每个人都很高兴。 用于过场动画的视频动画,用于角色的骨架,用于场景的时间线,用于发射器粒子系统飞行的程序以及用于粒子本身的单帧。 我们只是从每种类型中获取最佳。


困境和决定编写我们自己的解决方案


无论如何,出于某些客观原因,我们认为对于我们的项目,我们需要Flash动画。 然后引起了关于它们是否集成到引擎中的问题。 在尝试了多种插件选项之后,我们选择了我们认为最能实现其功能的插件。 尽管它可以生存并且得到了良好的支持,但它也拥有完全无法承受的企业许可证,但是谁能真正为好的产品定价呢? 现在,我故意不给您任何名称,以免使其看起来像广告或反广告。

我们对该插件花费了将近一年的开发时间,在此期间,我们为什么不能继续使用它已经很清楚了。 特别是与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的关键帧组成。 在这些帧中,我们的剪辑static_clip由坐标(-50;-50)(100;100) 。 这里在帧之间有一个补间动画(一个过程动画,在我们的例子中,它只是一个移位,没有缩放和旋转)。 因此,可以使用我们从这些帧中获取的坐标的线性插值来计算关键帧之间的静态剪辑的位置。

当然,如果是真实的动画,一切都会变得…有点复杂和笨重,但是即使没有文档,也仍然可以理解。 在您看来,就是这样。 实际上,我知道有几个项目和公司都采用这种方式并取得了很大的成功,但并非没有任何限制和困难。 这些困难是.XFL的真正障碍,它们是:


  1. 补间可以非常不同,既简单(使用线性插值),又更复杂(通过定制的函数和该插值图); 补间上的矢量图形变形仅根据它们自己的规则执行,仅Macromedia和Adobe家伙可以理解规则。
  2. 与图形一样,矢量图形与除光栅图形之外的所有动画一样,都在文本文件中描述。 例如 。 这意味着您需要编写自己的光栅化程序,由于Flash中矢量格式的性质和复杂性,可能无法完成此操作。
  3. 动画播放的规则(包括嵌套的规则)需要从头开始制定。 没有相关文档,并且回答了以下问题:应显示何时,应显示哪个帧,何时补间以及何时不进行插值,您必须自己找出,同时尝试涵盖测试运行中的所有可能案例,我相信,对于一般而非私有解决方案而言,这是一项非常重要的练习。

这些并不都是这种方法的全部缺点,但是我认为这些足以让您了解,它仅在具有强大局限性的情况下才适用于一般解决方案。 很久以前,我沿着这条路从事我的一个项目。 Flash的过场动画具有经典的补间(没有自定义的插值规则和特殊的插值功能),突出的光栅图形,并且Flash编辑器没有提供其他高级功能。 我想出了一种快速有效的专有解决方案,但是它仍然对动画师有一些严格的限制,因此他们不会使用任何您可以称之为复杂的东西。 这种方法有权存在,并且有一些基于它的库,它们具有不同程度的不稳定性,但是,正如我说的那样,它有很多局限性。


.JSFL脚本


获取JS动画信息的另一个几乎可行的选择,.JSFL脚本使您可以扩展Flash编辑器,与其环境进行交互,编辑动画,当然,还可以获取有关时间线,图层,剪辑和帧的所有必要信息。 。 动画师也将其用于各种过程的自动化,但这实际上是一个完全不同的故事。 总体而言,该方法具有上一个方法的所有缺点,因此我将不深入探讨它。 我只说它允许您以逐帧格式导出动画场景,但是我们知道这不是真正的绝地武士所做的(但是,当然,它可以在某些情况下完成项目)。 稍后,当我们讨论我选择的方法时,我们将回到这些脚本:矢量图形的栅格化和导出场景的优化。


AIR应用


这种方法确实有效,可以按需使用。 仍然,我没有选择它,但是为了记录,我将告诉您有关此选项的信息。 这种方法的本质是,我们在Flash本身上创建一个AIR应用程序,或者例如对于纯粹主义者,在Haxe上创建一个AIR应用程序。 该应用程序将从您已编译为SWF格式的动画场景中提取所有信息。 在应用程序中,我们逐帧播放场景,获取有关帧的所有信息,并将其保存为最适合我们的运行时的格式。 使用这种方法,我们可以解决我上面提到的所有问题:


  • 我们不需要栅格化矢量图形,因为它将由Flash运行时完成。 我们剩下要做的唯一一件事就是获取这些信息并保存矢量片段的栅格输出。
  • 我们不必发明播放规则并处理自定义的补间插值,集成在AIR应用程序中的Flash Player确切知道应该如何做,并且基本上为我们完成了工作。

作为奖励,我们有机会使用框架脚本(所有play()stop()和其他gotoAndPlay() ,一些动画师真的很喜欢它们)。 作为缺点,我们失去了导出Flash本身循环的场景的机会。 但这确实不是那么糟糕,因为我们可以在运行时中循环它们,并且动画师可以为此做好一切准备。


现在应该注意,由于我个人还没有去过那里,所以我确定一切都会或多或少比我描述的困难,因此我无法真正告诉您所有细节。 我欢迎那些通过这种方式分享经验的人,我将很高兴阅读您的成功和失败!


您自己的Flash播放器


在所有情况下,这可能是最公平,最明显和最直接的选择,但也是最困难的选择。 毕竟,这是一个真正的Flash播放器所做的事情,我们大多数人将该播放器安装在收藏的浏览器中作为附件。 现在,这种类型的流行代表是: gamewfscaleform 。 他们俩今天都死了。 令他们着迷的是,它们主要用于包括AAA在内的各种项目中的GUI实现。 但是我们的兴趣在于内部,而不是死库的应用。


任何体面的Flash播放器都至少隐藏了以下两个重要内容:


  • SWF格式解析器;
  • 矢量图形光栅化器;
  • 用于启动代码的ActionScript的虚拟机;
  • 自定义代码的标准库;

我知道你在想 以上每个观点都在大声疾呼,即使可以实现类似的目标,您也将不得不切换多个团队来完成此任务,并花费数年的时间开发基本版本。 即使只是为了发挥作用而实现的一小部分,也将花费您很长时间,并且您可以肯定,以该管道的许多未记录部分的形式进行的进一步添加将使这一长期工作加倍。


是的,Internet充满了尝试实现这些部分中的每个部分的尝试,其程度各不相同。 但是您甚至不能将这些开发作为自己的东西的基础,因为您将仅花费数年的时间来寻找错误。 同样,您必须添加所有未编写的单元测试,然后才能意识到您将永远无法完全实现每件事。 那些试图为gamewf添加必要功能的家伙会明白我在说什么。 一个合格的Flash播放器也不应提前对矢量图形进行栅格化,而应在运行时进行栅格化,这将对性能产生巨大影响,因此并非每个项目都可以通过。 顺便说一句,我想向试图在移动设备上使用scaleform的幸存者问好。 无论如何,这是最严肃和绝望的方式,绝对不是我们的选择。


结合方法


因此,我们终于选择了我的选择。 研究了所有可能的方法后,我想到了这个有趣的组合。 它相对容易实现,可由一个人管理,同时具有足够的功能来满足几乎所有2D时间轴动画制作的所有需求。


首先,我们将使用在SWF中编译的动画场景,以免发明各种播放选项,并且几乎从不猜测Flash播放器在内部的工作方式,因为我们不可能涵盖所有情况,但我们仍然寻找一般的解决方案。 而且,Flash编辑器摆脱了我们在动画中使用的所有补间,从而仅向我们提供了已编译SWF中片段的裸露位置。


第二,我们禁止在动画中使用脚本。 是的,这一要求确实很艰辛和可悲,但是我没有一个完整的程序员团队来实现他们几乎必须蒙住眼睛才能实现的体面的虚拟机和标准库。 当然,在这里,与AIR应用程序方法相比,您可以看到我的方法的缺点。 在第二个中,我们可以使用一些脚本来播放内部动画。 同时,这并不会阻止我们创建良好的动画场景。 要将自定义信息从场景转移到游戏中,可以使用所谓的frame labels ,您可以在代码中找到它们。 您还可以使用运行时的回调函数将用户事件固定在特定帧上。


然后,我们不再编写自己的矢量图形光栅化程序,因为我们不能在一般情况下编写它,并且不能涵盖Flash中矢量的每个选项和可能性。 相反,我们将使用JSFL脚本在(!)编译之前对图形进行栅格化。 同时,使用此脚本,我们将通过在一张图像中合并静态片段来优化矢量图形,并出于自身的优先考虑(质量和/或性能)放大或缩小生成的光栅图像。 同样,在这里值得考虑具有各种屏幕像素密度的设备的画质/分辨率选项(例如,高清和标清画质)。


抒情部分的总结


到此结束本文的第一部分。 在第二篇文章中,我们将讨论技术实现细节,代码片段,图片(!),优化,技巧和调整。 即将推出,请不要错过!

Source: https://habr.com/ru/post/zh-CN445102/


All Articles