Asset Studio 是如何实现的
开始之前
在上一篇 游戏制作计划 里,我说明了自己正在按什么顺序制作哪些类型的游戏。那篇文章里,Asset Studio 只是被简单提到了一下。大致只是说,它是一个把 Story Quiz 和 Mystery 的角色与背景资源放在一起管理的工具。
这篇文章想按真实实现把那句话展开。Asset Studio 不是一个单纯出图的页面。现在在 Omnilude 里,它是收集资源、整理提示词、执行生成任务、审核结果、再把结果登记进资源库的工作台。前端 backoffice 负责承载这个工作台,game-service 负责资源领域,ai-service 负责生成执行本身。
为什么要单独做 Asset Studio
真正开始做游戏之后,资源会比想象中更快地分散开。Story Quiz 有角色、背景和 block 用图。Mystery 有剧情角色、地点和笔记图片。即使是同一个人在做同一个世界观,如果提示词、风格预设、生成结果、审核状态和实际使用情况分散在不同页面里管理,很快就会失控。
所以 Asset Studio 从一开始就被设计成更像工作室,而不是画廊。关键点在于,它不是某一个类型专用的工具。它可以用同样的方式处理 Story Quiz 里已经做好的资源,以及从 Mystery 草稿里拉过来的资源;可以把生成结果保存成可审核的资源库;也可以继续追踪这些资源后来是否被别的领域实际使用。没有这个视角,AI 生成看起来会很方便,但它不会真正沉淀成可运营的资产。
后台界面是怎么组织的
前端 backoffice 里有 Asset Studio 的专用路由。背景和角色分别从不同入口进入,但内部共享的是同一套工作台组件。列表页同时支持树形视图和扁平视图,也会显示最近编辑过的项目。也就是说,既可以按目录整理,也可以从最近的工作记录重新进入。
进入详情页之后,结构就更清楚了。这个页面实际上就是一个完整的资源制作控制台。顶部管理名称和审核状态,正文部分则按标签页处理导入源、提示词、生成、媒体和历史。实现上可以看到,导入源的搜索状态以及生成媒体的操作状态都保存在页面级别。目的就是在切换标签时不要丢掉上下文。
这里有一个很重要的分界。资源列表查询、目录修改、生成结果登记这一类资源管理 API,都会通过 /api/asset-studio/* 进入 game-service。而真正的生成请求并不经过 Asset Studio API,而是通过 /api/media-generation/generate 进入 ai-service。这个拆分是有意为之的。资源管理和生成执行在同一个界面里,但它们不是同一种责任。
实际的生成流程是怎么走的
从用户体验来看,这个流程很简单。打开一个资源,导入已有资源或者 Story Quiz、Mystery 的源,调整提示词,然后按下生成按钮。但内部流程其实被拆成了下面这样。
- 后台详情页先读取当前的
assetItem.generationConfig。 - 生成请求被发送到
/api/media-generation/generate,前端立刻拿到jobId。 - 前端用这个
jobId订阅 backbone 的 SSE 流,并接收进度事件。 ai-service通过实际的 provider 生成结果,并把结果上传到 storage。- 前端从 SSE 收到
outputUrl和sourceUrl,然后展示预览。 - 如果结果可以接受,用户再通过
/api/asset-studio/asset-items/{id}/media把它登记进去。
最后这一步非常重要。生成完成之后,结果不会立刻进入 Asset Studio 的资源库。生成结果首先被当成审核对象来处理。只有当用户按下登记动作,它才会变成运营资产。实现上这一点也非常明确。生成 hook 会根据 SSE 事件建立预览状态,而另外一个动作会调用 createGeneratedMediaApi,把结果正式放进资源库。
正因为如此,Asset Studio 不是一个提示词输入框,而是一条带审核的制作流水线。失败的结果、模糊的结果,以及管线后处理产生的结果,都可以先以临时状态存在,等人工决定后再登记。
game-service 负责什么
game-service 里的 Asset Studio 并不是简单的 CRUD 集合。它实际上更接近资源库的真正拥有者。即使只看 controller 层,目录、项目、导入、export/import portability、generated media、winner 指定、reorder、bulk move 和 import source 查询,也都被归在同一个领域里。
这个服务之所以重要,主要有三个原因。
1. 它会从现有游戏领域拉取来源
AssetStudioImportService 会读取 Mystery 的 DraftLocation、DraftCharacter,以及 Story Quiz 的 StoryQuizBackgroundAsset、StoryCharacter,再把它们转换成可导入的 source 卡片。这里带过来的不只是名称,还包括 prompt、provider、stylePreset 和元数据。所以 Asset Studio 不是一个所有资源都从零开始的地方,而是一个承接既有游戏数据继续做资源生产的地方。
2. 它会把生成结果整理成可运营资产
AssetGeneratedMediaService 会把生成结果保存成资源库记录,并允许其中一个结果被标记为 winner。当图片类型的 winner 改变时,项目缩略图也会一起更新。反过来,如果某个资源已经在实际内容中使用,它的代表媒体就不能被随便删除。也就是说,它把收集生成结果和采纳运营资产这两件事分开了。
3. 它会追踪资源是否真的正在被使用
AssetReferenceSyncService 会检查某个 URL 是否真的在 Story Quiz 和 Mystery 中被使用,并同步 isReferenced。在 Story Quiz 一侧,它会扫描封面、卡片、角色默认图、情绪图、背景和 block 媒体。在 Mystery 一侧,它会检查剧情缩略图、角色头像、笔记和物件图片。所以 Asset Studio 不是一个好看的资源仓库,而是一个与线上内容连接在一起的资源管理工具。
ai-service 负责什么
ai-service 的职责更清晰。这个服务并不知道资源库本身,它只负责执行生成。它会解析 backoffice 发来的 generationConfig,决定使用哪个 provider 和 workflow,把工作放进异步任务里执行,并通过 SSE 持续推送进度。
从实现上看,MediaGenerationService 会先检查按账号划分的并发执行槽位。然后 GenerationConfigParser 开始解析配置。这里的优先级非常关键。generationConfig.media[mediaType].providers[providerCode] 优先级最高,其次是 default,最后才是根级别的 fallback。这个结构让同一个资源可以按媒体类型、按 provider 保存不同的配置。
解析完成之后,ai-service 并不会立刻开始生成,而是先把任务放进 DTE Job 队列。前端拿到的 jobId 会继续作为 SSE 订阅时使用的同一个键。换句话说,backoffice 不需要阻塞在一个长请求上,而是围绕 job ID 去追踪异步生成过程。
为什么 ComfyUI 集成很重要
就当前实现来看,Asset Studio 最有意思的部分之一就是对 ComfyUI 的集成。ComfyUIProvider 并没有把 workflow 硬编码在应用代码里。运行时配置来自 provider 配置,实际执行的 workflow 来自数据库中的 workflow 记录。而且它支持的不只是 prompt-to-image,也支持 media-to-media 管线。
这点重要,是因为 Asset Studio 的生成配置并不是单纯的一段 prompt 文本。用户可以同时传入 workflowId、workflowPipeline、seed、width、height 以及输入图片 URL 这类运行时覆盖项。实际实现中,选择某个 pipeline workflow 之后,还可以把已有的 generated media 作为输入,用来做后处理或者变形。
因此,这个 AI 生成界面不再只是一个通用提示词输入框,而更像一个数据驱动的 workflow 执行器。同一个角色可以换 provider、换 workflow、换输入图重新跑,再把结果审核后积累进资源库。
之后单独写一篇更详细讨论 ComfyUI 本身的文章,应该也会很合适。
为什么要拆分 game-service 和 ai-service
这不是单纯的 MSA 偏好问题。资源领域和生成执行系统的失败方式不同、生命周期不同、运营视角也不同。
game-service 处理的是需要长期保留的信息,比如目录结构、审核状态、实际使用情况、portability 和 history。相对地,ai-service 处理的是哪些 provider 还活着、该选哪个 workflow、当前还能跑多少任务、生成推进到了哪一步。如果把这两类职责混在一起,短期内也许会方便一些,但边界很快就会变得模糊。
在现在的结构里,Asset Studio 正好把两者连接起来。backoffice 虽然在一个界面里工作,但对保存和执行是分开处理的。正因为有这个拆分,即使未来除了 Story Quiz 和 Mystery 之外还会增加更多类型,资源库和生成基础设施也可以分别演进。
结尾
在上一篇文章里,Asset Studio 只是一个一带而过的句子。但如果顺着真实实现继续看,它并不只是一个图片生成功能。对 Omnilude 来说,Asset Studio 是一个完整的制作系统:它收集游戏资源,从现有领域拉取来源,执行 AI 生成任务,审核结果,并持续追踪这些资源是否真的被使用。
所以我并不把 Asset Studio 看成游戏开发里的一个附属页面。它更像是 Story Quiz、Mystery 乃至整个制作流水线汇合的交点。下一篇我会把范围再收窄一些,继续说明这个资源系统是如何进入真正的出版流水线,或者如何进入某个具体类型的制作流程。
即使以审稿者的角度回头来看,这篇文章的内容也显得有些密,甚至有一点像暗号。
我准备让博客支持图片附件,并把现有文章也一起调整,尽快把整体可读性再往上提一层。
不过既然这篇文章花了这么多力气,还是先发出来吧。