Holonic Asset:AI 游戏资产生产架构设计

#Holonic Asset
#系统架构
#游戏开发
#AI工程化
#模块化单体

为什么绝大多数 AI 生图工具做不了“游戏资产生产”?

做过游戏开发的工程师通常会有一个很直观的体会:单次 AI 出图的惊艳,和真正用于游戏项目的资产生产之间,隔着一条巨大的工程鸿沟。

市面上绝大多数 AI 图像生成工具的核心交互都是“输入一段 Prompt,等待几十秒,拿到一张图”。这种模式适合做单张插画或灵感探索,但一旦进入实际游戏资产管线,立刻就会撞上几堵硬墙:

  1. 风格与透视的漂移:游戏需要一组资产共同构建世界观。如果你先做主角、再做敌人、最后做场景和道具,只靠在每次生成时硬背 Prompt,很快就会发现角色是侧视视角、场景变成了俯视 2.5D、后期的道具颜色和线条粗细完全对不上。
  2. 资产结构的异构性:游戏资产绝不仅是“一张图片”。角色资产包含原型(Prototype)、动作方向和帧序列动画;场景资产包含分层图层(Layers);瓦片地图(Tileset)包含切片规则和碰撞体;UI 资产包含独立组件。把它们全部粗暴地当作扁平的 PNG 来处理,后续的切片、调整和集成工作将寸步难行。
  3. 长耗时异步任务与创作流的冲突:调用模型生成一组帧动画可能需要5分钟甚至更久。创作者不可能一直看着 Loading 菊花干等;但如果允许用户在后台生成时继续编辑当前画布,当 AI 任务完成时,绝不能未经确认就暴力覆盖用户刚才在这 30 秒内手动修改的内容。
  4. 版本追溯与试错成本:AI 生成具有天然的随机性。创作者必须能够安全地试验、撤销、对比分支,并在满意时将资产固化为一个不可变的历史版本。

Holonic Asset 的立项初衷,就是解决上述矛盾:不把 AI 当作单次出图的玩具,而是构建一套让游戏资产能够被持续定义、生产、组织和迭代的工程系统。


总体架构:一个部署单元,两条协作链路

在深入 Project、Asset、Task 与 Record 之前,先把这些概念放回真实代码中的位置。Holonic Asset 不是“前端调用一个生成接口”这么简单,而是由 Frontend、Backend、持久化设施与外部生成能力共同组成的生产系统。

Holonic Asset 总体架构

从部署视角看,系统只有三个主要边界:

  • Frontend:承载项目资产库、画布编辑器与生成任务队列。Working Content、Undo/Redo 和待应用结果的预览都保留在浏览器会话中;普通业务请求通过统一 Contract 访问后端,大文件则获取临时上传凭证后直传对象存储。
  • Backend:承载请求入口、Outbox Dispatcher 与 Task Worker,内部按职责拆为 Workspace、Generator、Task、Processor、Upload 与 Repository 等模块。模块之间依赖接口,而不是通过网络相互调用;至于为什么采用单一部署单元,后文再单独解释。
  • 基础设施与外部能力:关系数据库同时保存 Project、Asset、Record 等业务数据,以及 Task、Outbox 和 Job;对象存储保存图片、视频与帧序列;文本理解、图像生成和视频生成 Provider 只负责概率性生成,本地 Processor 负责切帧、去背景、调色与规格归一化等确定性处理。

其中, 我们团队一开始在拆Backend系统模块的时候, 拆出 Workspace、Task、Generator、Processor、Upload 以及 Export 等多个概念时,很容易立刻联想到微服务架构:把每个模块独立拆成一个微服务,通过 gRPC 或消息队列在中间做通信。

按模块拆成服务后的一次生成调用链

如果此时拆成多个服务:

  • 原本可以在一个本地事务中完成的“业务状态更新 + Task/Outbox 创建”会变成分布式一致性问题;
  • 每个模块都要承担服务发现、重试、链路追踪、熔断和独立发布等运维成本;
  • 模块之间原本清晰的接口会被迫升级成需要长期兼容的网络协议,而业务仍处于快速演进阶段。

因此这里采用的是“代码边界清晰、部署边界统一”:业务模块在代码层面保持隔离,在运行时则共享同一事务边界和进程内调用链。

模块化单体如何落地?

  • 按领域拆包:workspace(project+asset)、task、generator、processor、storage 各自拥有清晰边界;
  • 通过接口同进程协作:Generator 通过注册 Handler 接入 Task 体系,Task 不引用任何具体 Generator 的内部结构;
  • 共享事务边界:请求入口、Task Worker 与 Outbox Dispatcher 运行在同一进程内,共享数据库连接和事务上下文,简化一致性保障。

什么时候再拆?

只有遇到明确的物理或组织约束时,才针对瓶颈拆分:

  1. 媒体处理需要独立扩缩容:当抽帧、超分辨率或复杂图像算法成为整体瓶颈时,将 Processor 剥离为计算 Worker;
  2. 自托管模型需要异构运行环境:当推理依赖专用运行时、加速设备与独立调度时,将特定 Provider 封装为专用推理服务;
  3. 业务域由独立团队负责:当团队边界已经稳定,且各模块需要独立发布和演进时,再让部署边界跟随组织边界拆分。

在这些条件出现之前,模块化单体就是当前复杂度下更合适的部署形态。


Project:把“飘在用户脑子里的约束”沉淀为系统内核

在没有统一项目概念时,创作者生产一组像素游戏资产的典型流程往往是充满痛苦的:

每次打开生成窗口,都要在 Prompt 里机械地复制粘贴一长串固定修饰词:“16-bit pixel art, side-view 2D platformer, NES palette, 32x32 grid…”。只要某一次任务少贴了一个关键词,或者模型对某个词产生了理解偏差,新生成的素材就会与旧资产产生肉眼可见的割裂。

这种做法的本质缺陷在于:我们把本该由系统层维护的“跨资产全局约束”,转嫁给了创作者的记忆力和反复编辑的 Prompt。

Project 创作内核

在 Holonic Asset 中,Project 不仅仅是一个收纳文件的文件夹,而是所有关联资产共享的“创作内核(Creative Kernel)”。

在 Project 层面,我们中心化沉淀了跨资产长期稳定的全局元数据:

  • GameType(游戏类型:如是卡牌还是RPG都很大程度上会决定整个游戏的画风)
  • Perspective(透视规范:如 2D Side-scroll、Top-down 45°、Isometric)
  • Target Platform(目标平台,是PC还是Mobile)
  • Reference Images(参考图, 会包含项目中的各个元素,成为整个项目的基底)

架构设计上的关注点分离

这里的核心原则是:Project 仅管理项目级上下文,完全不关心具体某个 Asset 内部长什么样,更不关心具体某次生成怎么调用模型。

当创作者在具体某个资产(如主角骑士)下发起生成请求时,他只需要聚焦于当前资产的差异化诉求(如“手持长剑向右挥击”)。系统在组装生成任务时,会自动将 Project 上下文与 Asset 局部参数按需合并,注入到对应的生成器中。

这样既消除了重复填写的负担,又从系统架构层面杜绝了“跨资产风格渐进式漂移”的顽疾。


Asset:统一外部生命周期,保留内部业务异构

系统设计中最容易犯的错误之一是“为了统一抽象而抹杀真实的业务差异”。

在游戏管线中,不同资产的数据结构差异极大:

  • Character / Object Asset:包含基础立绘、多方向姿态,以及 Run/Attack/Die 等多套 Animation Frames;
  • Scenery Asset:包含背景、中景、前景等多个独立可调层级(Layers);
  • Tileset Asset:包含整图、切分后的 Tiles、元数据以及碰撞网格;
  • UI Asset:包含多个状态切图与组件布局。

如果我们把它们都抽象成通用的“图片文件”,系统就无法提供细粒度的帧预览、图层开关和瓦片切分功能;但如果我们为每一种资产都从头建一套独立的 CRUD、标签、版本和发布体系,又会导致大量通用基础设施代码被重复拷贝。

为了解耦这种矛盾,我们将 Asset 拆分为两个变化原因完全不同的维度:

Asset 的 Attributes 与 Contents

1. Attributes(元数据与规格约束)

Attributes 定义了资产的“外部身份与规则”:包括 Asset ID、名称、所属项目、资产类型、标签、透视(Perspective)以及强类型的规格约束(Dimensions,如角色与物件的 {"width": 64, "height": 64}、瓦片集的 {"tileSize": {...}, "tileAmount": {...}} 等)。 它回答的核心问题是:“这个资产是什么,在系统中以什么规则与规格约束存在?” 所有资产无论类型为何,都共享这一套生命周期接口:创建、检索、重命名、打标签、复制、删除与版本提交。

2. Contents(业务结构与资源实体)

Contents 则是具体资产实际承载的“结构化内容”,它以强类型或专有 Schema 形式存在:

  • Character / Object 保存帧序列与动作映射;
  • Scenery 保存图层树与深度配置;
  • Tileset 保存切片规则与瓦片映射。

它回答的核心问题是:“这个资产当前具体由哪些元素构成?”

为什么这样拆?

这种拆分让通用模块与特定业务各司其职:

  • Backend的资产管理模块只需要理解 Attributes,就能轻松完成整个工作区内所有资产的检索、过滤与批量管理;
  • 具体画布编辑器与 Generator 只需要关注自己对应的 Contents 结构,无需介入资产的外部管理;
  • 后续如果需要新增一种全新的资产类型(如 UI Set),只需定义其特定的 Contents Schema,现有的项目管理、标签、版本快照与任务调度链路全部可以原生复用。

Task 与 Generator:长耗时异步编排与“不打扰”的前端状态投影

AI 资产生成通常伴随着长耗时外部调用(LLM 规划、图像扩散模型推理、视频模型生成、音频合成),单次任务可能持续数秒至数十秒。

如果采用同步接口,前端会被直接锁死;但即使转为异步任务,如果不仔细划分责任边界,系统仍然会陷入混乱。这里最典型的三个工程陷阱是:

  1. 任务基础设施与具体业务耦合:如果让通用的 Task 队列直接去理解“角色动画如何抽帧”或“场景图层如何抠图”,任务模块很快就会沦为堆满 switch-case 的超级上帝类。
  2. 生成结果暴力覆盖编辑态:用户发起生成后,趁着等待的 30 秒顺手在画板上微调了已有动画。如果任务完成后后端直接自动更新资产,用户的最新修改就会被彻底冲掉。
  3. 后端持久化状态直接暴露给前端:后端的六态生命周期(包括各种取消、中间排队、终态清理)如果全部一股脑塞给前端任务栏,用户的操作界面会充斥着无意义的噪声。

为此,我们确立了 Task(任务调度骨架) 与 Generator(局部业务编排) 相互独立的架构。

后端 Task 完整状态机与前端状态投影

后端维护完整六态生命周期

在后端,Task 作为一个通用的异步信封存在,负责任务入队、Worker 调度、重试控制与状态持久化:

  • pending:任务已入库并等待 Worker 拉取;
  • processing:Worker 已认领并正在调用具体 Generator Handler 执行;
  • awaiting_application:生成已成功,但结果被隔离在 Result 字段中,等待用户审查;
  • completed:用户已明确完成接受/拒绝,或者无需确认的任务正常结束;
  • failed:执行发生异常,记录 Error 并等待重试或取消;
  • cancelled:创作者明确放弃失败任务,进入终态归档。

Generator:面向单一能力的局部编排

Generator 才是真正懂业务逻辑的模块。以生成角色跑步动画为例,AnimationGenerator:

  1. 仅接收生成所需的最小输入(Prototype 引用、动作方向、帧数、参数);
  2. 调度外部 AI Provider(如 ComfyUI/Flux/视频模型);
  3. 调用本地 Processor 进行确定性处理(如背景剔除、调色、FFmpeg 抽帧、网格对齐);
  4. 上传静态资源至对象存储;
  5. 仅输出一份局部 Content Patch 及其资源引用。

Generator 绝不需要读写整个 Asset,也不负责资产最终的落库。

前端状态投影与 awaiting_application 防护机制

为了给创作者提供干净、非侵入式的体验,前端并不镜像后端的全部状态,而是做了一层按需状态投影(State Projection):

  • 前端只关心 4 种需要用户注意力或操作的状态:pending(排队)、processing(生成中)、awaiting_application(待确认)、failed(失败可重试)。
  • 当任务完成进入 awaiting_application 时,前端把生成的 Content Patch 与用户当前正在编辑的最新工作副本进行无损叠加预览。
  • 创作者点击 Accept,局部结果才真正合入前端工作副本;点击 Reject,结果被直接丢弃并触发后端临时资源清理。无论哪种选择,Task 均平稳过渡为 completed 并从前端任务通知栏中淡出。

核心准则:AI 的输出永远只是提议,创作者才拥有最终决定权。


Record:为什么坚决采用“全量快照”而非“Git 式 Diff”?

在设计版本与历史记录系统时,工程师的第一反应往往是:“我们是不是应该实现一个类似 Git 的 Diff/Patch 引擎,用 DAG 图记录每次修改的增量变更?”

然而,深入分析业务特性后,我们坚决放弃了 Git 式 Diff 的方案,选择了 Copy-on-Write 的不可变 Content 全量快照(Record)。

Content 工作副本与 Record 快照

为什么放弃 Diff 方案?

  1. 业务结构演进的维护成本过高:游戏资产的 Contents 结构(动画状态机、图层元数据、切片配置)随着功能迭代会频繁升级。如果使用 Diff 存储,后端就必须长久维护所有历史版本的 Patch 重放逻辑和 Schema 迁移规则。一旦某个中间 Patch 兼容出错,整条历史链条都会损坏。
  2. 存储成本收益极低:在游戏资产中,真正占据大容量的是图片、序列帧切片和音频等媒体文件,这些文件全部存储在对象存储(S3/OSS)并通过 CDN URL 引用。Contents 记录的仅仅是这些 URL 和元数据 JSON。保存一份完整的 JSON 快照通常只有几 KB,为了省这几 KB 去引入一整套极其复杂的 Diff/Patch 引擎,是典型的过度设计。

最终的读写分离设计

我们把编辑状态与正式历史严格划分为两个世界:

画布高频操作 ≠ 已保存
点击 Accept 接受 AI 结果 ≠ 已保存
只有创作者主动点击 Save,才生成新的 Record 快照
  • 前端:维护唯一可变的 Working Content 负责响应创作者的高频交互、本地撤销/重做(Undo/Redo)、以及 AI Patch 的局部合并。
  • 后端:持久化不可变的 Content Record 当用户主动点击 Save 时,前端将完整的 Content 提交给后端。后端进行版本并发校验后,通过 Copy-on-Write 机制生成不可变的 Record 快照(Record vN+1),旧快照绝不会被后续编辑篡改。

这保证了用户可以自由地在前端画板上做任何破坏性试验,而历史记录始终坚如磐石。


端到端协同:一个角色动画生成闭环

将上述设计串联起来,以“为已有角色原型生成一套走动动画”为例,整条链路在各边界间的协作极为清晰:

已有 Character Asset 新增 Animation 的完整链路

  1. 发起任务:创作者选定 Character Asset,配置生成参数并提交。Core API 读取持久化的 Prototype 与 Project 创作上下文,在单个本地数据库事务中创建 Task 和 Transactional Outbox 记录,毫秒级返回 Run ID。前端无需锁定画布,创作者可继续进行其他操作。
  2. 后台执行与确定性处理:Task Worker 消费任务,由 AnimationGenerator 组织业务输入,调用外部模型生成视频/图片,再交由本地 Processor 完成背景扣除、自动切帧并存入对象存储。
  3. 隔离待审:Worker 将局部帧数据写入 Task Result,状态置为 awaiting_application。此时资产本身的正式数据毫发未动。
  4. 前端预览与合并:前端收到通知,将 Task Result 中的帧序列与创作者当前最新的 Working Content 结合渲染预览。
  5. 用户决断与快照持久化:
    • 若用户点击 Accept,帧数据合入当前工作副本,Task 标记为 completed;
    • 用户继续调整满意后点击 Save,前端提交最终完整快照,后端校验基线版本并落库生成新的不可变 Record。

架构决策矩阵

下表总结了 Holonic Asset 架构演进中的关键权衡与取舍:

架构决策点采纳方案放弃方案核心考量接受的代价
项目上下文Project 中心化创作内核每个 Asset 独立维护公共 Prompt风格、透视、色板规范需要跨资产长久复用,避免风格漂移需在系统层维护上下文继承与注入机制
资产数据模型统一 Attributes 生命周期,异构 Contents 隔离每类资产独立建表或粗暴视为纯文件兼顾通用管理能力(标签、检索、复制)与各类游戏资产真实的结构差异通用层需支持动态/异构 Contents 校验
生成任务编排Task 状态机与 Generator 解耦各生成能力独立维护排队与状态任务排队、重试、错误处理是通用能力;生成业务是易变逻辑需定义通用的任务信封与 Handler 注册协议
交互与状态投影后端完整六态,前端按需四态投影前后端镜像共享完全一致的状态后端需保留完整终态用于溯源与清理;前端需最小化用户注意力干扰前端需维护状态投影映射层
AI 输出处置awaiting_application 显式审查生成完成直接写回当前资产避免 AI 异步返回时冲掉用户在生成期间的手动修改增加局部 Patch 叠加预览与接受/拒绝交互
版本历史模型Copy-on-Write 全量 Content 快照Git 式 Diff / Patch 增量链条Contents 主要是媒体 URL,快照极小;避免为微小体积收益引入沉重的 Patch 兼容引擎容忍少量结构化元数据的冗余存储
系统部署边界Go 模块化单体 + 本地 Outbox拆分为多个独立微服务耗时瓶颈在外部 Provider,拆服务徒增网络与分布式一致性开销模块共享发布周期,依赖严格的代码架构纪律

写在最后

在 AI 浪潮下,很多人容易将注意力完全放在大模型的参数规模与单次生成的魔法感上。

但真正将 AI 引入专业生产管线后,你会发现:决定一个工具好不好用的,往往不是模型本身,而是它周围的工程脚手架。

  • 如何让创作者不用反复写 Prompt?
  • 如何让长耗时的 AI 生成不打断创作者手头的微调?
  • 如何让每一次生成结果都可预期、可审查、可撤销?
  • 如何在业务快速演进的同时,保持系统架构的极简与克制?

Holonic Asset 的这套架构设计,正是我们在真实游戏资产生产场景下,给出的工程答案。