开头先讲个我身边真实的经历。去年我们项目组要重新做一个卡通换装系统,美术那边导出的角色动画最早是用 Spine 4.0 做的,结果我们客户端拿 3.8 的运行时一跑,直接崩,报错还说得不明不白。后来一查,不是代码写错了,是 Spine 编辑器版本和运行时版本对不上。这事让我意识到,Spine 的骨骼动画看着是个"导出图片+读配置"的简单流程,但真正落地到代码里,从编辑器导出的资源文件到运行时加载 skeleton 数据、再到渲染出正确的一帧,中间隔着好几个容易翻车的环节。尤其是 Spine 3.8 这个版本,到现在还有大量项目在用,而且网上能搜到的教程、老项目源码、美术插件,很多都是基于 3.8 的。
这篇东西我就围绕"Spine 3.8 版本下 skeleton 的加载"展开,不堆概念,直接讲清楚资源文件是什么样的、不同环境下代码怎么写、加载过程中会踩哪些坑,适合刚接触 Spine 的客户端开发、独立游戏开发者,还有想维护老项目的朋友参考。我会把加载 skeleton 这条链路拆成几个环节:先搞清楚 3.8 版本为什么还在广泛使用,再分析资源三件套的结构,然后分别给出 libgdx、Unity、Web 三种环境下的实际加载代码,最后集中写加载阶段的真实问题和排查方法。
1. 3.8 版本的特殊地位:为什么新项目也绕不开它
1.1 3.8 运行时与编辑器共存的现实
Spine 的版本号分成编辑器和运行时两套东西。编辑器主要负责制作骨骼、K 动画、导出数据,而运行时是集成在你游戏引擎或者渲染框架里的那一套解析和渲染代码,比如 spine-libgdx、spine-unity、spine-ts 这些。
3.8 这个版本很特殊。Spine 3.8 编辑器大约在 2019-2020 年期间是主流,之后 3.9、4.0、4.1、4.2 陆续出来。但很多游戏项目在早期就锁定了 3.8,因为升级运行时成本很高:不只是替换几个 jar 包或 dll 的事情,还涉及动画状态机 API 的变化、皮肤系统数据结构的调整、甚至渲染管线的适配。所以你会看到很多上线了两三年的游戏,客户端里跑的还是 3.8 的运行时,美术手里却早就装了 4.x 的编辑器——他们导出资源的时候必须特意选 "3.8 兼容" 的导出格式。
这个兼容导出其实就是把 4.x 的工程转存成 3.8 能读的 JSON 或二进制数据。但如果你不了解 3.8 的数据结构,直接在代码里用最高版本的运行时去加载,或者拿 3.8 的运行时去读 4.x 的原始文件,都会出问题。更麻烦的是,3.8 的运行时 API 本身跟 4.x 差别不小,很多网上教程里写 new SkeletonJson(atlas) 还是 new SkeletonData(),实际都要看具体版本。所以做 3.8 相关项目,第一件事就是确认编辑器导出选项和运行时版本严格对齐。
1.2 3.8 版的骨架数据结构与 4.x 的核心差异
从数据结构上说,3.8 和 4.x 的骨架模型有两个明显变化。
第一,3.8 的 skeleton.json 文件顶部会有 "skeleton": {"spine": "3.8.xx", "images": "...", "audio": "..."} 这样的字段。4.x 开始这个文件结构变了,骨骼节点的名字和层级组织方式也有调整,比如部分插槽属性从 attachment 改成了 skin 里一套更复杂的组织方式。如果代码里用 3.8 的解析器去读 4.x 导出的 JSON,经常会在读取 bone 的 rotation、shear 等属性时报空指针或者直接忽略未知字段,最终画面里角色瘫成一团。
第二,3.8 的插槽-附件系统比较直白:一个 slot 对应一个 attachment,skin 负责把不同 attachment 组织到渲染列表里。4.x 对 skin 系统做了一次比较大的抽象,加入了 skin structure 的概念。这导致换装逻辑在 3.8 和 4.x 下的 API 完全不一样。如果你们项目的换装系统是当年用 3.8 写好的,贸然升级运行时,所有换装代码都得重写。
这也就是为什么很多团队宁愿继续用 3.8:系统稳定、代码量可控、美术资源也能用旧版导出工具批量处理。可能对新手来说 4.x 才是新版本,但对成熟项目来说,稳定性远比新特性重要。
1.3 什么项目适合继续用 3.8
我自己的判断标准是这样的:如果项目主体代码已经跑在 3.8 运行时上、美术资源也都是 3.8 格式,那除非有明确收益(比如需要 4.x 里新增的约束、网格变形、物理模拟等功能),否则不要轻易升级。反过来,如果是一个全新项目,团队经验也都是基于 3.8 的,用 3.8 起步也完全没问题——毕竟 3.8 的功能做 95% 的 2D 游戏动画都够了,而且经历过多年生产环境验证,坑都已经填平了。但要是你们要做的游戏对物理效果、复杂的网格动画要求很高,那就建议从 4.x 开始学,免得以后还要迁移。
不管是哪条路,只要最终代码是基于 3.8 的,下面的加载方法就适用。
2. 加载 skeleton 之前,先读懂这套"三件套"资源
2.1 .json 与 .skel:同一数据的两种载体
Spine 导出的资源通常会包含一个数据文件,这个文件有两种格式:JSON(文本)和二进制(.skel)。两者内容承载的骨架数据完全一致,包括骨骼层级、插槽、附件、动画关键帧、皮肤等信息,只是编码方式不同。
JSON 格式的好处是肉眼可读、方便排查问题,适合开发阶段。你打开一个 3.8 版本的 JSON 文件,能看到 "bones"、"slots"、"skins"、"animations" 这几个主要数组,结构非常清晰。坏处是文件体积偏大,解析速度比二进制慢一些。游戏正式包里如果用 JSON,加载时间会长那么几十毫秒,但一般 2D 游戏也不会因此卡顿。
二进制 .skel 的体积大概只有 JSON 的三分之一到四分之一,解析更快,而且不容易被美术人员无意间改坏。缺点是出了问题不方便直接打开看。
我个人的建议是:开发阶段用 JSON,进版本管理;出包的时候如果项目对启动时间敏感,就切换成 .skel。3.8 的运行时里加载二进制的入口一般叫 SkeletonBinary,加载 JSON 的入口叫 SkeletonJson,两者的输出结果都是 SkeletonData 对象,后面处理逻辑完全一致。
提示:注意多语言运行时里,
SkeletonJson和SkeletonBinary的构造函数第一个参数都是一个AttachmentLoader。在 3.8 版本里,通常传AtlasAttachmentLoader。这个细节很多人第一次写时会漏掉,后面加载纹理时就会出现"附件缺失"。
2.2 .atlas 图集文件的结构逐行拆解
.atlas 文件是 Spine 图集(Texture Atlas)的描述文件,它描述了多个纹理页(page)和多个区域(region)的布局。加载 skeleton 时,渲染器需要根据 .atlas 里的 region 信息,从一张大图里抠出对应的纹理片段,贴到骨骼的附件上。
一个典型的 3.8 版 .atlas 文件长这样:
