使用Trae Wrok快速迭代Spine骨骼动画库:支持ASTC格式
①痛点来源
- 前提概要:当前客户端项目iOS/Android部分页面以Flutter跨平台形式实现。
- 当前需求:客户端实现3D房间装扮能力以及用户角色人物支持换装打扮(类似QQ秀)还有支持人物动作。
- 方案选择:从需求方向上看需要引入游戏引擎来实现3D空间较为妥当;最终技术团队计划引入Cocos Creator作为实现3D房间的技术框架;角色换装因为CC内置支持Spine骨骼动画,另外Spine官方提供了iOS/Android双端以及Flutter平台的版本;因此最终选择Spine作为实现人物装扮的技术选型(Live2D也是类似骨骼动画但并没有Flutter版本)。
上述介绍许多主要为了让大家了解整个需求背景,同时也是痛点起源。
1.1-技术优化
整体需求前期开发较为顺利,但后期随着Cocos游戏引擎中3D空间模型资源递增以及内置Spine加载素材变多后导致内存暴涨,在低端机型出现卡顿甚至Crash的情况。这就预示着当前项目已经到了需要做技术优化处理的阶段。
因此针对性对Cocos环境下进行技术排查做深度优化工作。优化过程从资源素材压缩、资源异步增量加载、Spine同素材复用等操作,尽可能降低内存增长量。最后呈现效果有明显提升但还没有做到极致。
由于Cocos环境下Spine素材资源和原生环境以及Flutter平台使用同一份资源:图片资源是PNG格式。而Cocos环境是支持纹理压缩。从而显著减少内存占用、降低带宽需求,提高游戏的渲染性能和加载速度。
同理Spine资源也能够在Cocos中将纹理像素数据转化为GPU专用的压缩格式。我们技术团队老大先操作实践将原有Spine资源全部转为ASTC格式并顺利在Cocos环境中加载成功,从内存性能上有突破性提升。
1.2-ASTC压缩格式
在Cocos官方文档中有对压缩纹理的介绍: (压缩纹理 | Cocos Creator)
而ASTC正是纹理压缩格式其中一种,因为这些压缩格式是可以直接在GPU内存中使用,省去了上层图片格式到压缩格式中间处理过程,提高了效率同时减少解码图片资源的时间以及减少游戏的内存。
1.3-格式统一
原先技术方案Spine全局资源共用一份:iOS/Android、Flutter、Cocos,原生下载管理同一份源文件,原因显而易见:方便缓存管理;避免冗余下载,浪费带宽资源。当前Cocos环境下已实现采用ASTC格式加载,其他平台Spine能力是否也能实现兼容该格式加载?
②实操AndroidSpine支持ASTC加载
2.1-Android痛点解决
团队技术老大先前已经对iOS的Spine库做升级拓展支持了ASTC格式加载且iOS底层实现兼容好,改动较少。但AndroidSpine库的实现方式和iOS差异化太大,改造存在一定难度。
2.2-技术底层原理分析
之前对AndroidSpine有一定了解,spine实现依赖以下两个重要仓库。
api "com.badlogicgames.gdx:gdx:1.14.0"
api files('libs/spine-libgdx-4.2.12.jar')
libGDX 是一款基于 Java 的跨平台 2D/3D 游戏开发框架,底层依托 OpenGL (ES) 图形接口,支持 Windows、Linux、macOS、Android、iOS 及 Web 浏览器六大平台,是 Java 生态中最成熟、应用最广的游戏开发框架之一。
spine-libgdx 是基于 libGDX 框架的实现,使用 OpenGL ES 渲染骨架动画。
查看依赖库源码中有OpenGL相关代码,原先猜测AndroidSpine底层渲染应该是采用GLSurfaceView或TextureView。但深入阅读源码发现实际上AndroidSpine是采用Canvas渲染。
因此产生疑惑:依赖库底层渲染采用OpenGL而上层则没有完全使用该方案,其中是否有什么原因存在,继而影响未来是否能够支持ASTC格式的可能性(ASTC不可避免采取OpenGL纹理渲染)。
遇事不决先问问AI:打开Trae Work切换到Code模式,新建任务,选择文件夹,最后输入自己的问题。
第一个问题
github.com/libgdx/libg… 帮我分析该项目Cross-platform Game Development Framework底层技术实现原理 如何做到跨平台以html文档形式输出技术架构。重点介绍如何在安卓项目中接入和使用且告知在安卓项目中底层是如何渲染展示。
Trae输出:
Trae直接输出一份libgdx详细介绍文档,再次确认渲染库底层在Android平台是以OpenGL实现。
第二个问题
github.com/EsotericSof… spine-android底层使用了com.esotericsoftware.spine:spine-libgdx和com.badlogicgames.gdx。但SpineView.java中方法onDraw使用了canvas绘制内容。这和底层使用libgdx是opengl实现又有所不同了。帮我再生成一份html文档解释安卓原生spineview是怎么渲染spine内容的,底层有没有用到opengl
Trae输出:
前面提到过对于AndroidSpine为何不使用OpenGL实现,毕竟libgdx渲染库以全面支持。
从文档说明中可知:
- 复用 spine-libgdx 核心逻辑:骨骼动画更新、插槽变换、附件计算等全部来自 spine-libgdx。
- 复用 gdx 数据结构:Array、FloatArray、IntArray、Pool 等来自 com.badlogicgames.gdx。
- 替换渲染后端:不使用 libGDX 的 OpenGL 管线,而是通过 Android Canvas API 渲染。
关键点:spine-android 只依赖 spine-libgdx 的 逻辑层,不使用其 渲染层(GL20 / Batch / ShaderProgram)。渲染由 Android 原生的 Canvas 完成。
从文档方案对比AndroidSpine是采取Canvas.drawVertices()形式绘制,虽然代码层面不调用OpenGL,但Android渲染系统底层可能涉及GPU加速。
SpineView的渲染总结如下:
SpineView.onDraw(Canvas)
└── SkeletonRenderer.renderToCanvas(Canvas, RenderCommand[])
└── Canvas.drawVertices(TRIANGLES, ...)
├── [硬件加速] → OpenGL ES → GPU → SurfaceFlinger
└── [软件渲染] → Skia CPU 光栅化 → Bitmap → 拷贝到 Surface
但文档最后总结:与libgdx OpenGL 方案相比虽然性能不如GPU直通方案,但集成成本低,更适合UI嵌入、简单动画播放场景,这侧面说明采用OpenGL 方案或许可行。
2.3-技术改造方案选型
第三个问题
spine-android若采用 libgdx OpenGL 渲染方案改动量大吗?
Trae输出:
spine-android 改为 libgdx OpenGL 渲染方案的改动量分析 改动评估:中等偏大,核心逻辑可复用。
从目标改动点来看总体改动量较大:从Renderer绘制层、纹理对象AndroidTexture、Atlas等,核心部分代码都要重新重构。
不过最后总结推荐使用spine-libgdx进行改造。
spine-libgdx 已经提供了完整的 OpenGL ES 渲染实现:
- com.esotericsoftware.spine.SkeletonRenderer(OpenGL 版本)
- com.esotericsoftware.spine.SkeletonDrawable(libgdx 版本)
- 集成 libGDX 的 SpriteBatch、Texture、TextureAtlas
第四个问题
市面上是否有开源项目spine-android采用opengles渲染的
Trae输出:
根据公开搜索,目前没有发现成熟的开源项目专门提供"纯 Android OpenGL ES 渲染的 spine-android 替代方案"。
同时最后总结还是推荐直接使用 spine-libgdx(推荐,改动量最小)。最后总结AndroidSpine采用OpenGL方案是可行的。
第五个问题
spine_android_spineview_rendering.html 基于技术方案描述,若将Android版本的SpineView改造成直接加载astc来实现应该如何修改重写代码,以html文档形式输出。
Trae输出:
原版 spine-android 的 SpineView 通过 Canvas.drawVertices() 进行 2D 渲染,纹理加载链路为:
- 显存占用高:Bitmap 解压后为 RGBA8888,4 bytes/像素,一张 2048×2048 纹理占用 16 MB。
- 不支持压缩纹理:BitmapFactory 仅支持 PNG/JPEG/WebP,无法直接解码 ASTC/ETC2 等 GPU 压缩格式。
- 采样质量受限于软件层:API < 29 强制 LAYER_TYPE_SOFTWARE,无法享受 GPU mipmap 与各向异性过滤。
- 多页纹理切换开销:BitmapShader 在多页 atlas 中需频繁切换 Paint,draw call 难以合并。
改造方案目标效果:
- 直接加载 ASTC 压缩纹理,免去 PNG 解码与 RGBA 中间表示。
- 纹理数据全程以压缩形态驻留 GPU 显存,降低 4~8 倍显存占用。
- 保留 spine-libgdx 逻辑层(Skeleton / AnimationState)与 AndroidSkeletonDrawable 接口不变,仅替换渲染后端。
- 对外 API 与原 SpineView.Builder 保持一致,迁移成本最小。
ASTC 是 GPU 压缩纹理,Bitmap 与 BitmapShader 无法消费。这意味着改造 必须 把渲染后端从 Canvas 切换到 OpenGL ES。这是方案的根本性变更,而非局部优化。
然后输出的改造方案中介绍到核心逻辑是基本不变,不同点在视图承载方式发生改变:View转向GLSurfaceView;Bitmap变成textureId;SkeletonRenderer(Canvas)换成GLSkeletonRenderer;混合模式改变等。
技术方案改造计划中能够想到改动最大的核心痛点在于代码翻译,将原先Canvas绘制流程全部替换成GL环境:ASTC格式文件加载;GL环境搭建;绘制流程渲染;混合模式映射。其中GL环境尤其繁琐复杂,做过音视频方向开发肯定了解OpenGL的着色器编译,帧数据处理,顶点数据处理,创建纹理,离屏渲染等都是又臭又长的公式化代码。
技术改造方案已成熟,可代码实现功能验证结果了!将文档丢给AI分析制订开发计划实施结果。
2.4-技术细节优化修复
万事开头难,AI写代码的效率是挺快,但有一定难度的功能实现上也会栽跟头。如图所示以下对话记录都是和AI沟通如何修改BUG和问题。
优化第一个问题
使用其他AI一直无法解决黑屏没有展示内容的问题。
我已经使用AI实现了加载ASTC的SpineView,android-spine/spine-android/src/main/java/com/esotericsoftware/spine/android/gl/AstcSpineView.java。但目前还无法展示实际内容。是否哪里还有bug未解决或是功能缺失导致无法加载。其实就是参考SpineView实现的AstcSpineView,但底层渲染框架换成opengl。帮我排查下具体原因。
Trae输出:
Bug 1(致命):GLSkeletonRenderer.createProjectionMatrix 矩阵变换错误
原代码直接把像素单位的 translateX/translateY 加到了 ortho/ortho上。这两个元素是 NDC 裁剪空间的平移分量,取值必须在 [-1, 1] 内。传入的平移量通常是几百像素,导致所有顶点被映射到 NDC 范围之外,被 GPU 全部裁剪掉,屏幕全黑。
修复方案:改为直接构造正确的 MVP 矩阵,先通过 scale/translate 把 Spine 世界坐标转换到屏幕像素坐标,再用正交投影映射到 NDC。
Bug 2:AstcSpineView.updateCanvasTransform 中 translateY 符号错误
与原始 SpineView 的 Canvas 变换链对比,Y 轴平移应该是 + boundsCenterY * scale 而不是 - boundsCenterY * scale。这会导致 skeleton 在 Y 轴方向偏移出可视区域。
现在重新编译运行,AstcSpineView 应该能正常渲染 ASTC 纹理的 Spine 动画了。如果仍有问题,建议开启 GLUtils.checkGLError 检查是否有 OpenGL 报错,或查看 logcat 中 GLSkeletonRenderer 和 AstcSpineView 的 debug 日志确认顶点数据是否到达 GPU。
优化第二个问题
AstcSpineView内部是否有优化空间,我看没15s会触发一次GC的情况
Trae输出:
有明显的优化空间。看完 AstcSpineView.java、AstcSkeletonRenderer.java 和 GLSkeletonRenderer.java 后,几乎每一帧都在分配新对象。15s 触发一次 GC 是因为分配速率超过了 young gen 的回收节奏。
- 高频分配(每帧、每个 slot)
- 渲染层每帧分配
- 缓存 commands Array
- MVP 矩阵复用
- 把 getWidth/getHeight 缓存到 onSurfaceChanged
- 等等细节上的优化
优化前内存使用情况:
优化后内存使用情况:
优化第三个问题
android-spine/spine-android/docs/GLSurfaceView_vs_TextureView_方案对比.md 根据文档中介绍新增AstcTextureSpineView(基于 TextureView)实现新spine加载能力,尽量复用原有可直接复用的能力。
Trae输出:
仅 View 容器层(EGL 管理 + 渲染线程 + SurfaceTexture 处理)为新增,渲染核心零改动复用。
PS:已实现AstcSpineView,为何新增AstcTextureSpineView?由于Flutter环境也计划采用ASTC加载模式并采用Flutter Platform View进行兼容处理。但发觉采用GLSurfaceView和Flutter适配上存在严重问题(独立 Surface 与 Flutter(Skia/Impeller)渲染管线冲突)会导致展示内容坐标错位无法对其显示区域。
后续又使用AI重新实现了采用TextureView渲染的AstcTextureSpineView。虽然前期调研没考虑并踩坑,好在有AI加持重写效率极高,差不多一次对话即可复刻功能。
Flutter环境下桥接原生SurfaceView展示效果:
Flutter的Texture纹理组件外层包裹了红色背景的父布局但内容渲染展示还是会超出约束显示。
Flutter环境下桥接原生TextureView展示效果:
③成果展示
原生侧的Spine源码展示:
最终Android原生端ASTC格式效果展示:
④经验总结
啰里八嗦到最后,非常感谢能坚持看到这里!!!
起因SpineView因为显存与内存问题,原逻辑采用Bitmap内存占用较大,若采用ASTC压缩纹理不仅文件大小减,内存损耗也能得到优化;同时从Canvas绘制升级为独立GL线程渲染;后续又因为Flutter环境下GLSurfaceView(无法叠加原生 UI、不支持 View 变换等问题)改用TextureView提高整体兼容性。
总结来讲:从「软件 Canvas 绘制 PNG」到「GPU 直绘 ASTC 压缩纹理」解决性能和显存,再从「GLSurfaceView」到「TextureView + 自管 EGL」解决透明叠加、View 变换和生命周期保活,最终让 Spine 动画在 Android 上既省资源又能像普通 View 一样自由使用。
研发的流程和过去没有本质变化:
- 首先调研了解需求背景,制定方法衡量优缺点和痛点。
- 设计技术方案选型,择优选择最合适最有效的方案。
- 落实实现需求功能,满足基本条件达到预期效果。
- 优化迭代完成最终目标。
使用TraeWork开发也是如此,在让AI帮助开发前需要开发者自身先了解开发要求,有了明确目标后AI才能更准确的实现结果。
TraeWork使用感受
以上都是个人在工作环境下真实使用Trae Work实战成功。如今有AI并运用到工作实际场景中是真真切切能感受到AI的力量,也确实帮助开发者解决了一些棘手和繁琐的工作内容,提升工作效率。
另外全文架构图和技术框架使用了TraeWork生成而来,这大大提升了写作效率(过去都是自己默默画图完成)
其他思考🤔
- 或许未来AI会代替人类,但目前来讲还言之过早。人类创造了AI,必然还有人类可继续创造的未来。
- AI会降智会出错,人类也是一样。但人作为最后一道兜底还是十分重要的。
- 有AI但不能停止思考,心中要明确想法AI是工具而不是完全代替我的克隆人。
- 以上仅代表个人想法和牢骚,在AI时代望共勉🙏🏻。