明日方舟动画模型拆解:一张图集怎样变成会动的角色

阿米娅抬手攻击时,屏幕上是一整个角色;打开资源图集,看到的却是分散的眼睛、头发、衣摆和手。它们没有按时间排成一格一格的完整画面。角色的动作,发生在这些部件与另一份骨骼数据之间。

这次把本地客户端导出的几个样本放进明日方舟动画实验室。可以直接播放、暂停和拖动时间,再把骨骼、网格与原始图集叠起来观察。本文沿着这些可核对的数据,解释角色如何被画出来、如何动起来,以及动画数据能告诉我们什么。

先把角色停在半个动作上

打开完整动画实验室 →

先选阿米娅的战斗模型,切换一个攻击动作,降低播放速度。等手臂抬起时暂停,打开骨骼或网格,再切到图集。可以依次观察:

  1. 角色移动了,图片有没有换? 留意手、头发和衣摆:很多变化来自同一块图片的位置、旋转或网格形状变化。
  2. 一根骨骼能否带动一串部件? 对照身体、头部与发梢,区分整体运动和局部摆动。
  3. 一块布料为何能弯? 网格内的三角形在变形,纹理仍贴在这些三角形上。
  4. 动作数多,模型就一定复杂吗? 换到基建与动态立绘样本,再对照骨骼、插槽和动作列表。

页面展示的是样本的骨架与贴图动画。客户端额外的粒子、材质效果、镜头、背景和战斗系统不在这次浏览器复播的范围内。看到的结果适合分析部件运动,不能据此认定已完整还原游戏里的整场演出。

先分清哪些是实测,哪些是解释

文中的样本统计来自本次实际读取的骨骼、图集与动画数据;工作流程说明制作这类动画时通常怎样组织素材;游戏逻辑部分只讨论接口关系,不把动画文件当作客户端代码。

同一个角色可能有战斗正面、战斗背面、基建、皮肤和动态立绘等多份资源。以下比较针对页面所选的具体样本,不代表该角色所有皮肤,也不是对整款游戏资源规模的估算。

本页样本 骨骼 插槽 动画条目 图集页数与尺寸
阿米娅 · 战斗正面 67 53 14 1 页,512 × 512
阿米娅 · 基建 48 35 6 1 页,516 × 516
源石虫 enemy_1007_slime 13 5 7 1 页,128 × 128
迷迭香 · 动态立绘 319 129 3 1 页,2348 × 2348

四份样本都只有一个 skin。动画条目包含前三份文件中各自的零秒 Default:它们可以提供默认姿态,但不能当作一段有持续时间的运动。源石虫的 ID 与中文名,另外用公开客户端数据镜像中的敌人图鉴记录核对;这不是从英文 slime 猜译出的名称。

进一步按附件与时间线检查,可以得到更有用的差别:

样本 区域附件 / 网格附件 其中加权网格 IK 约束 检测到的动画特性
阿米娅 · 战斗正面 38 / 22 21 4 含形变、绘制顺序时间线和事件关键帧
阿米娅 · 基建 32 / 15 15 4 含形变与绘制顺序时间线;无事件关键帧
源石虫 0 / 5 1 0 有事件关键帧;无形变、无绘制顺序时间线
迷迭香 · 动态立绘 0 / 129 77 2 含形变、颜色和附件时间线;无事件、无绘制顺序时间线

这里统计的是 skin 中的附件记录,并非某一帧同时显示的部件数。四份样本都没有 Clipping 附件;后文仍会解释裁剪的作用,但不会用这四个样本证明它被使用过。完整的样本版本、动画清单与来源记录见实验室样本元数据。

骨骼数也不是人体关节数。头发、衣摆、饰物、效果控制点和约束目标都可能占用骨骼。插槽数则表示挂接与排序的位置,不等于同一帧必然绘制多少张图片;插槽可以隐藏、换附件,也可能挂着不直接着色的裁剪附件。

从立绘到可动素材,第一步是把遮挡关系画完整

以一张完成度很高的静态立绘为起点,直接把手臂圈出来旋转,通常会立刻遇到两个问题:肩膀露出空洞,手臂后面的身体没有画过。

因此,适合骨骼动画的原画需要按运动关系拆分。前后头发、脸、眼睛、手臂、衣袖、衣摆、配饰,往往要能分别移动;原来被遮住、动作时可能露出的区域,还需要补画。关节附近也需要留出足够的重叠区,避免转动时出现裂缝。Spine 的官方图像指南要求独立运动的部分使用独立图片,并支持从分层绘图文件导入位置关系。Spine Images

这是对制作需求的解释,并不表示我们拿到了《明日方舟》的原始 PSD 或知道其美术团队的全部分层规则。从导出的图集中可以确认有哪些最终部件,却不能倒推出每一步原画工作。

阿米娅的图集提供了一个很直观的观察入口:多个眼部、手部与头发部件共存,同一张图里还有衣服和配件。不同表情或手势可以通过更换附件实现,摆动则可以通过骨骼与网格实现。这些方式能够混用,不能把所有可见变化都归因于单一的“拉伸图片”。

分层也决定了动作的可达范围。被遮住的纹理没有补画,运行时无法凭空生成;只有正面素材,也不能靠旋转几根二维骨骼得到可信的完整背面。这正是观察战斗正背面资源时需要记住的边界。

图集存放部件,骨骼文件组织动作

一套可复播的 Spine 资源,通常由三个部分配合:

文件 保存什么 少了以后会发生什么
.skel 或骨骼 JSON 骨骼层级、插槽、附件、约束和动画时间线 能看到零散图片,却不知道怎样组成角色和运动
.atlas 每个命名区域在纹理页中的位置、尺寸、旋转及裁切信息 无法可靠地把附件名称映射到正确图片区域
PNG 纹理页 角色部件的实际像素 骨架可以计算姿态,但没有需要绘制的美术内容

图集可以有多页。打包时还可能旋转部件、裁去透明边,再通过 Atlas 保存还原位置所需的信息。裁切后的小矩形不能直接当成角色坐标,也不能因为文件名相近就随意换用另一份图集。Spine Texture Packing

这里的图集不是逐帧录像。一段几秒钟的动作并不要求在 PNG 上存几百个完整角色。多数时候,多个动作共用同一批部件,时间线只记录“这个时刻手臂转到哪里”“这个插槽换成哪只手”“这块网格怎样变形”。它仍然允许附件切换,也能混合少量帧序列素材;不能反过来说骨骼动画里绝不会出现逐帧元素。

本次文件使用 Spine 3.8 系列数据。二进制骨骼必须按对应版本的字段读取,不能用一个能读出名称的脚本就判断解析成功。本次统计建立在完整读取骨骼与动画的基础上;网页加载的则是由源骨骼转换的本站中间数据格式,它不是官方 Spine JSON 导出。版本字段和数据结构可与官方 3.8 格式实现对照。Spine 3.8 SkeletonBinary

Spine 与 Unity 分别负责什么

Spine 是 Esoteric Software 开发的二维骨骼动画工具及其配套运行时体系。Unity 是游戏引擎,负责组织场景、组件、资源、输入和渲染等系统。两者通过集成层衔接:例如 spine-unity 可以把 Spine 导出的骨骼与图集装入 Unity,并由相应组件与材质显示。Spine 并不是“Unity 自带的一种动画文件格式”。Spine Runtime Architecture、spine-unity 文档

这也解释了本地资源包为什么会同时出现两种东西:一边是 Spine 的骨骼、Atlas 与贴图,另一边是 Unity 的 Mesh、AnimationClip、粒子或材质。它们可以在同一个场景里合作,但读取其中一类数据,并不等于得到了另一类系统。

本页浏览器查看器采用独立实现来重放选定样本,未把官方 Spine Runtime 或客户端 Unity 程序搬进网页。下面的管线说明描述数据和动画原理;具体支持范围以实验室页面的说明为准。

一帧画面是怎样算出来的

可以按这条顺序理解:

1
2
3
4
5
6
7
8
9
10
11
选定动作与时间
↓
采样关键帧,得到局部姿态、附件、颜色与形变
↓
按父子层级和约束关系更新骨骼变换
↓
计算图片或网格的最终顶点
↓
按绘制顺序处理裁剪、贴图采样与透明混合
↓
得到当前画面

实际实现会把约束穿插在依赖它们的骨骼更新过程里,不能把所有骨骼算完之后,再随意改变一个父骨骼而不更新它的子节点。

骨骼:先有局部动作,再有整体姿态

一根骨骼具有相对父骨骼的位置、旋转、缩放和剪切等变换。父节点转动,子节点随之运动;子节点自己的动画则叠加在这个继承关系上。

例如身体上下起伏时,头和手应该跟着整体运动,头发又可以在头部运动之上增加延迟摆动。这样不用给每片头发重复写一遍身体位移。用于绘制的世界变换,最终由层级关系组合得到。Spine Runtime Skeletons

骨骼调试线不是角色轮廓。有些骨骼只负责提供控制点,有些会影响多个部件;看到骨骼移动,也不意味着那个位置一定有一块可见像素。

网格与权重:同一张图片怎样弯曲

简单的区域附件可以作为一个四边形绘制。需要弯曲的衣摆、头发或身体部件,则可以用更多顶点和三角形组成网格。纹理坐标把图片固定到网格表面,顶点位置改变时,图片也随之弯曲。Spine Mesh Attachments

权重决定一个顶点受到哪些骨骼影响、各占多大比例。靠近肩部的顶点可以主要跟随上臂,靠近肘部的顶点则由多根骨骼共同影响。一个便于理解的写法是:

1
顶点最终位置 = 各个骨骼变换后的位置 × 对应权重,再相加

这里是概念说明:实际数据会为每个骨骼影响保存局部信息,还可能叠加形变。权重蒙皮的作用,是让多个控制点之间的过渡连续,而不是把整张图片僵硬地拧成一块。Spine Weights

网格越密、每个顶点受到的骨骼影响越多,求值工作量通常越大。因此一块有很多骨骼的饰物,和大量高密度加权网格,不应只用“骨骼数”比较成本。

Deform 与 IK:两种不同的修正手段

Deform 时间线直接记录附件顶点的形变。骨骼摆动可以完成主要运动,局部轮廓的修正再用形变补足。它并不是“换一张图片”,也不等于权重本身。大量顶点形变关键帧还会增加动画数据量;官方建议优先考虑骨骼与权重,再有节制地使用逐顶点形变。Spine Keys

IK(反向运动学)解决另一件事:给出手或脚的目标位置,由约束求出骨骼应该怎样转。它适合让脚保持在地面附近,或者让手追随某个控制点,而不用手工逐帧调整每段肢体角度。普通骨骼运动、IK 和其他约束,可以共同构成最终姿态。Spine IK Constraints

这两项是格式与制作管线的能力。某个样本是否真的使用它们,需要看该样本的数据;不能因为它的头发会动,就推断一定存在 Deform,也不能因为脚看起来稳定,就断言使用了 IK。

插值:关键帧之间也有姿态

动画数据不必保存屏幕刷新时的每一帧。假如在两个时刻分别记录旋转角度,播放时会按曲线计算中间值。

线性插值让值均匀变化;曲线插值可以形成起步缓、末端快等节奏;阶梯变化则把旧值保持到下一个关键帧。附件切换、事件触发与绘制顺序变化具有离散含义,也不能照搬连续旋转的处理方式。Spine Graph

所以,把播放器减速能帮助观察动作,却不会让原素材“增加关键帧”。显示器刷新率、动画关键帧的密度和游戏逻辑更新频率,是三个需要分别讨论的量。

绘制顺序与裁剪:谁遮住谁

插槽把附件放进一条绘制顺序中。角色的手原来在身体后面,动作中移到前面时,除了位置变化,还可能需要改变前后叠放关系。Spine 的 draw order 就是当前应按什么顺序绘制插槽;它可以随动画变化。Spine Runtime Terminology

Clipping 附件则提供一个裁剪多边形,让指定绘制范围内的内容只在这个区域中显现。它本身不是一块彩色图片,也不意味着这里就有战斗碰撞框。裁剪从哪个插槽开始、在哪个插槽结束,同样依赖绘制顺序。Spine Clipping Attachments

裁剪和碰撞都可以用多边形表示,但用途不同。若只看到一个多边形就把它当作命中区域,会把表现层的数据误读成玩法规则。

Alpha 与混合:透明边缘也是数据的一部分

把所有部件画到正确位置,还不代表颜色一定正确。普通透明混合要根据 Alpha 合成前景与背景;加法混合等模式又采用不同的颜色组合方式。

还要区分直通 Alpha 与预乘 Alpha:后者的 RGB 已经乘过透明度,绘制时不能再照另一套规则重复相乘。贴图、顶点颜色和混合方式不匹配,半透明边缘就可能偏黑、偏亮或出现光晕。官方 Unity 渲染说明也把贴图编码与混合方式的配套作为明确要求。spine-unity Rendering

这解释了为什么“PNG 打开正常”和“动画组合正常”是两种检查。前者只说明图片可解码,后者还要验证附件区域、坐标、透明度和合成顺序。

战斗、基建与动态立绘,把预算花在不同地方

战斗模型需要在较小尺寸下交代朝向、攻击、受击、死亡和技能状态。大量单位可能同时出现,动作轮廓是否清楚、状态切换是否易读,会比每一缕头发的细微变化更重要。正面与背面资源提供不同视角的美术内容;浏览器里对单份模型做左右翻转,只能得到镜像,不能代替背面素材。

基建模型的观看方式不同。它更适合待机、走动与交互动作,角色通常在另一种展示尺度和场景节奏下活动。因此,不能把战斗动作列表直接套给基建模型,也不能因为两个文件都叫阿米娅,就假设骨骼和插槽可以一一对应。

本页的迷迭香样本只有 Idle、Interact、Special 三个动作,时长分别约 10.667、5.333、10.667 秒,却用了 319 根骨骼、129 个网格附件。其中 77 个网格带权重,统计到 2231 个网格顶点和 2506 个三角形。它的两个双骨 IK 约束在数据中命名为 Cat_L 与 Cat_R;这些名称可帮助定位控制关系,但不能单凭名称认定官方的全部绑定意图。

动态立绘允许观众长时间看一个较大的角色。呼吸、眼神、发梢、衣料与配件的小幅运动会变得醒目;较多骨骼和网格,可能被用于让一幅画中的多个局部独立活动,而非增加更多动作名称。

再看两个很具体的动作:阿米娅战斗样本的 Attack 约 1.767 秒,基建样本的 Sit 则为 8 秒。它们承担的表现任务不同,不能因为都属于同一角色就直接共享播放策略。源石虫的 Move_Begin、Move_Loop、Move_End 分别约 0.5、2、0.5 秒,名称与时长显示出“进入、循环、退出”的组织方式;客户端是否总按这个顺序调用,仍需另外验证。

图集尺寸也给出一项能直接估算的资源差异:若只计算一份未压缩 RGBA8 像素,迷迭香的 2348 × 2348 约为 21 MiB,阿米娅战斗的 512 × 512 为 1 MiB。这不是 PNG 下载大小,也不是实际整段动画的显存占用;纹理格式、缓存和额外缓冲都还需要另算。

这是根据样本结构和典型观看场景作出的解释,不是对官方性能预算的测量。真正的运行成本还取决于同屏数量、加权顶点数量、裁剪、纹理切换、透明叠画面积和材质。一个只有三个动作的大立绘,完全可能比十几个动作的小人更复杂;动作数量也不能直接换算成显存或帧率。

动画事件能通知游戏,但不能证明伤害怎样结算

动画时间线可以在某个时刻触发事件。游戏代码收到事件后,可以播放声音、产生特效,或者执行别的行为;事件的含义由接收它的应用决定。Spine Events

样本里可以看到一个具体区别:阿米娅战斗骨骼声明了 OnAttack、OnStart,并有事件关键帧;基建骨骼也声明这两个名字,但六条动画没有事件时间线。定义表里存在一个名字,甚至还不能证明当前动作会触发它。

这让美术时间与程序响应有了连接点,却不足以证明《明日方舟》的伤害计算就放在某个动画事件上。要确认战斗判定,还需要观察逻辑层、状态机或运行过程。仅凭 Attack 这样的动作名,也不能知道攻击力、目标选择、命中时刻和弹道规则。

更稳妥的阅读方式,是分别提出两个问题:

  • 表现层已经提供什么? 姿态、动作长度、部件切换、事件时间、可供挂接特效的骨骼。
  • 游戏代码还要决定什么? 当前状态该播哪个动作,动作能否被打断,单位何时选择目标,以及伤害如何计算。

循环待机可以持续播放,攻击动画可以从待机切入,再回到待机。至于具体游戏用队列、状态机、脚本还是其他方式驱动,这份动画资源本身并没有给出全部答案。动作时长也不能直接当作角色的攻击间隔。

可复播,比“解出了文件”多了几步

本次资源检查遇到过一个很有代表性的问题:几份骨骼对象使用相同的资源名称,却不一定对应相同图集或纹理。工具为了处理重名加上后缀后,如果仍机械地拼出原来的 Atlas 名称,文件都在,关联却会错。

因此,核验需要分层进行:先确认骨骼能按正确版本完整读取,再确认附件区域存在、Atlas 页能找到对应 PNG、尺寸匹配;遇到重名变体,还要回到原资源中的对象引用关系,而不是凭文件名猜测。最后再实际播放,检查网格、约束、裁剪和颜色是否组成合理画面。

完整客户端演出还有另一层边界。动态立绘资源包里可能同时包含 Spine 骨架与 Unity 的 Mesh、AnimationClip、粒子或材质。只把骨架和图集放进网页,并不会自动带来这些外围效果。本文的图集、骨骼统计和动作分析,只针对所选 Spine 样本。

回到上面的播放器,把一个动作停在最不容易看清的中间姿态,再打开图集:可以把屏幕上的一只手、一段头发、一片衣摆,逐一对应到素材、骨骼和网格。理解这条对应关系之后,“角色在动”就变成了可以拆开检查的一组具体过程。