RFC 0005:WebGAL 静态组合立绘
目标版本: WebGAL 主分支(以 4.6.2 立绘行为为基线)
composeFigure Demo、-composite 参数为个人维护的立绘合成分支的实验性命令,如果看到请无视~~
摘要
本 RFC 为 WebGAL 引入静态组合立绘。作者在单个角色目录中以 figure.json 声明画布、位图部件和可复用组合预设;剧本通过 changeFigure:<角色模板选择符>/<组合列表> 请求组合。引擎将最终有序部件序列预先合成为一张静态图,再完整复用现有图片立绘的位置、变换和进出场演出。
背景与目标
WebGAL 当前立绘演出以单张图片为基本单位。AI 生成资源和常规差分立绘常由底图、服装、表情等静态图片叠加而成;若为每个组合导出完整图片,会增加资源管理和导出成本。
本 RFC 的目标是:
- 用简单的有序叠加语义表达静态组合,不引入槽位、替换、去重或部件树语义。
- 复用 WebGAL 既有的立绘定位、变换和演出逻辑。
- 为常用组合提供内存缓存、可选的持久化缓存和进度预热。
- 让 Terre 提供可视化编辑、校验、指纹生成和剧本命令生成。
非目标
- 不支持动态部件、部件级演出、局部替换、口型、眨眼或角色自动管理。
- 不合成 GIF、APNG、视频、SVG、Live2D 或 Spine,也不组合多个模型。
- 不更改现有普通图片、Live2D、Spine 立绘的路径语义和行为。
- 首期不实现本轮合成立绘的 LRU、内存池或容量回收策略。
- 不通过运行时监听文件变化来发现作者在 Terre 外直接修改的资源。
术语
- 角色模板:一个角色一份
figure.json,声明部件和组合预设。
- 角色模板选择符:组合语法斜杠前的名称,如
hana/summer_uniform 中的 hana。
- 组合预设:可递归引用部件或其他预设的有序名称列表。
- 组合列表:
changeFigure 中斜杠后的逗号分隔名称列表。
- 叠加序:组合列表及预设展开后得到的有序部件名称序列;后者绘制在前者之上。
- 组合逻辑 URI:舞台状态和存档使用的内部字符串,如
webgal-composite:hana/summer_uniform_smile,cry。
角色模板格式
角色模板路径固定为:
game/figure/<角色模板选择符>/figure.json
部件资源与模板同处该角色目录,src 从 figure.json 所在目录解析。
{
"Version": 1,
"fingerprint": "6a7f9b7d1c4f5c4cb9c6b5e45d6fdf41f5bce4f0ddcfd482b8ba8541a357fb90",
"canvas": {
"width": 1600,
"height": 3000
},
"components": {
"body": {
"src": "body.webp",
"x": 0,
"y": 0,
"scale": 1
},
"uniform_summer": {
"src": "parts/uniform-summer.webp",
"x": 0,
"y": 0,
"scale": 1
},
"face_smile": {
"src": "faces/smile.webp",
"x": 0,
"y": 0,
"scale": 1
}
},
"presets": {
"summer_uniform": ["body", "uniform_summer"],
"summer_uniform_smile": ["summer_uniform", "face_smile"]
}
}
ABI 与名称
- 根字段
Version、fingerprint、canvas、components、presets 均为必填。
- 首期仅接受
Version: 1。缺失字段、未知版本或非法指纹是模板错误,不进行尽力兼容。
- 角色模板选择符、部件名和预设名均匹配
[A-Za-z0-9_-]+,大小写敏感。
- 选择符必须与角色目录名逐字一致。
- 部件名与预设名共享唯一名称空间,重名无效。
画布与部件
canvas.width 和 canvas.height 为正整数,单边不超过 8192,总像素不超过 16,777,216。
- 每个部件的
src、x、y 为必填;scale 可省略,默认值为 1。
x、y 必须是有限数,scale 必须是正有限数,允许小数。
x/y 表示部件左上角相对组合画布左上角的设计像素。图片按原始尺寸绘制后等比应用 scale。
src 只能是角色目录内的相对路径。允许子目录和非 ASCII 文件名;禁止绝对路径、URL 和 .. 路径段。
- 静态部件格式只接受 PNG、WebP、JPG、JPEG,扩展名不区分大小写。
- 可声明暂未被任何预设使用的部件;同一
src 可由多个不同部件引用。
预设展开
- 每个预设是非空名称数组,数组项可引用部件或其他预设。
- 预设在引用位置进行深度展开,数组顺序就是绘制顺序。
- 最终展开必须至少包含一个部件。
- 重复引用保留,例如
['body', 'face_smile', 'face_smile'] 绘制三层。
- 缺失引用、循环引用、空预设和空展开结果均为模板错误。
剧本语法
组合立绘使用既有 changeFigure 命令:
changeFigure:hana/summer_uniform_smile,cry,sad -left;
changeFigure:hana/summer_uniform_smile -id=hana-copy -enter=universalSoftIn;
- 斜杠前是角色模板选择符,斜杠后是一个或多个逗号分隔的部件/预设名称。
- 列表项按书写顺序与预设展开结果组成最终叠加序。
- 无
-id 时继续使用左、中、右固定舞台槽位;有 -id 时继续使用同名自由立绘。
- 同一个角色模板允许在多个舞台目标同时出现,不建立角色唯一性。
changeFigure:none 和 -clear 保持既有清除语义,并优先于组合解析。
- 含图片或模型扩展名的内容继续被当作原有文件路径处理。例如
changeFigure:hana/stand.webp 不是组合语法。
-composite 与现有 composeFigure Demo 不属于正式语法,将与 Demo 代码一并移除。
支持与拒绝的参数
组合立绘支持位置、-id、-transform、缓动、进出场、时长、-zIndex、-blendMode 和现有默认演出配置。
下列动态立绘参数与首期静态组合不兼容:-motion、-skin、-expression、-bounds、-blink、-focus、-mouthOpen、-mouthClose、-mouthHalfOpen、-eyesOpen、-eyesClose。组合命令使用其中任一项时,引擎记录错误并保持该舞台目标当前立绘,不静默忽略。
渲染与舞台行为
引擎将最终部件序列绘制到组合画布:
- 使用普通
source-over 叠加;后出现的部件覆盖先出现的部件。
- 组件允许越出画布,输出在画布边界裁剪。
- 输出像素尺寸等于
canvas.width 与 canvas.height,保留透明通道,采用普通图像平滑,不按显示设备 DPR 放大。
- 合成结束后,输出作为一张普通图片立绘交给现有 Pixi 舞台。
因此,缓存未命中时旧立绘会持续显示,新组合准备完成后才按现有切换和进场演出替换;首次出场则在准备完成前保持为空。合成或校验失败时保留当前立绘;读档时没有旧立绘可保留的目标保持为空,但其他目标和读档流程继续完成。
模板加载、缓存与持久化
模板加载
figure.json 是角色资源中的静态声明文件,不是运行时注册表。某角色第一次被请求时,引擎读取、校验并放入本次进程的模板缓存;同一角色后续切换、预热和合成复用该缓存。游戏重启后首次使用时重新读取模板,以获得当前指纹。
引擎还对已读取的原始模板内容计算摘要。该摘要参与缓存验证,使 Terre 外直接修改 JSON 后不会错误采用旧缓存;直接替换图片后仍必须通过 Terre 保存模板或重新导出游戏重建资源指纹。
缓存键与查询顺序
组合缓存键由以下内容组成:
角色模板选择符 + 资源指纹 + SHA-256(最终展开后的有序部件名称序列)
资源指纹已覆盖画布、坐标、缩放、模板定义和资源内容,所以缓存键不重复写入 x/y/scale。舞台状态和存档始终保存作者原始的组合逻辑 URI,不保存 Blob URL、运行时对象 ID 或缓存路径。
查询顺序为:
本轮已生成、可直接显示的合成立绘 -> 持久化 WebP Blob -> 重新合成
持久化 Blob 解码失败时删除该项并重新合成;失败结果永不缓存,后续相同请求可重试。
持久化缓存
- 默认关闭,由玩家在设置页按游戏开启。
- 使用独立的
localforage 存储库,以游戏键隔离,不写入频繁保存的用户设置对象。
- 持久化格式为浏览器原生 Canvas 编码的 WebP Blob,编码质量参数取最高值。
- 开启后,已在本轮生成的组合在后台重新栅格化、编码并补写;不为等待开关长期保留高内存原始画布。
- 关闭时立即删除持久化项但保留本轮可显示的合成立绘。
- “清理组合缓存”只清理当前游戏的持久化 Blob 和索引,不改变当前画面。
- 编码或写入失败时,游戏继续使用本轮缓存,向玩家提示一次,并自动关闭开关。
已知编码限制
首期使用浏览器原生 canvas.toBlob('image/webp', 1),不新增 WASM 编码依赖。Web 平台没有可移植的 lossless 或 exact 控制项,质量参数 1 也只是编码器提示;因此持久化 WebP 不能保证逐像素无损,可能与本轮直接合成的画面存在轻微差异。该限制只影响持久化缓存,不能生成 WebP 或写入失败时按既有错误策略回退本轮缓存。
未来若引入可控制的 libwebp WASM 编码器,必须自动使现有原生编码缓存失效并重新生成,不能要求玩家手动清理缓存。
预取、预热与快进
现有资源预取仍以 <link rel="prefetch"> 为基础:初始场景窗口为前 24 行,运行中按当前语句后 20 行继续预取。组合立绘在模板尚未加载时先读取模板,随后预取最终需要的普通部件图片;在这些资源可用后才进入组合预热。
- 后台预热在浏览器空闲时间运行,一次只合成一个组合。
- 实际要显示的组合优先于未开始的预热任务,并与相同缓存键的预热共享结果。
- 持久化 WebP 编码和写入始终在后台,不延迟画面切换。
- 快进时,同一舞台目标尚未开始的旧组合任务由最新请求替换。
- 已开始的绘制或编码不强行中断;其结果只可进入缓存,不能覆盖该舞台目标的最新请求。
存档、诊断与资源更新
读档从组合逻辑 URI 按当前模板和资源指纹恢复,不保存历史合成像素。因此,作者更新立绘后旧存档会显示当前组合,而非旧图。
恢复舞台时,引擎等待所需组合准备完成后再继续游戏。模板、资源或合成错误记录选择符、组合列表和原因,供作者诊断;这类错误不向玩家弹窗。只有持久化编码或写入失败才提示玩家,因为它影响玩家主动开启的本地空间功能。
Terre 保存某角色模板时重建该模板指纹;导出游戏前扫描、校验并重建全部角色模板。Terre 外直接修改 figure.json 可由运行时模板摘要发现;Terre 外直接替换图片则需要作者保存模板或重新导出游戏。资源指纹改变后旧持久化项不再命中,并异步清理。
Terre 交付
Terre 与引擎同一期交付角色模板编辑器。
- 双击
game/figure/<角色模板选择符>/figure.json 打开专用编辑器;其他 JSON 保持普通文本资源行为。
- 编辑器从角色目录选择静态图片,编辑画布、部件坐标和等比缩放,维护有序预设引用,并提供实时预览。
- 保存时执行模板校验并自动生成资源指纹;无效模板不能保存或导出。
- 资源侧的
ChangeFigure 面板提供“普通图片 / 组合立绘”模式;组合模式选择模板和可排序名称列表,可保留重复项,并可跳转模板编辑器。
- 保存模板后,Terre 通过现有预览同步通道通知预览引擎失效该选择符。预览引擎清除该模板的本轮缓存,仅重新合成舞台上正在使用该选择符的组合,不刷新预览页面。
建议:共享规则与手写模板支持
无论最终采用共享包、共享源码或其他交付方式,Terre 与引擎必须使用行为一致的模板校验、展开和指纹规则。
建议在 WebGAL 仓库提供一个版本化、纯 TypeScript 的组合规则包,供 Terre 锁定兼容版本,而不复制规则源码。该包可以负责:
- 模板校验。
- 组合选择符解析。
- 预设展开。
- 缓存输入规范化。
- 资源指纹算法。
兼容性与迁移
- 没有
figure.json 的游戏、既有普通图片路径和既有单模型立绘不受影响。
figure.json 不进入普通立绘资源 URL;否则现有 .json 立绘识别会将其视作 Live2D 模型。
- 组合语法仅由严格的
角色模板选择符/组合列表 识别,含资源扩展名的内容继续视为路径。
- 当前
composeFigure Demo、-composite 参数、相关解析分支和测试从主线移除,不提供与正式语义并存的兼容层。
实现顺序与验收
- 建立共享规则包、Node 指纹工具和覆盖有效/无效模板、展开、规范化、指纹的测试夹具。
- 移除
composeFigure Demo,在 WebGAL 解析器、运行时、舞台状态、存档和缓存层实现正式组合语义。
- 接入预取、空闲预热、快进合并、持久化 WebP 和玩家设置。
- 在 Terre 实现模板编辑器、剧本命令面板、导出扫描和预览失效通知。
- 验收普通图片回归、组合顺序与重复项、缓存命中/失效、读档、快进、持久化开关、Terre 保存及导出,并验证 Web、Electron、Android 资源路径与大小写行为。
结论
静态组合立绘以角色模板、顺序叠加、预合成单图和可选持久化缓存为核心。它扩展静态差分立绘的表达力,同时保持 WebGAL 现有单图片立绘架构、舞台演出和存档模型稳定;动态角色能力留给后续独立设计。
RFC 0005:WebGAL 静态组合立绘
目标版本: WebGAL 主分支(以 4.6.2 立绘行为为基线)
摘要
本 RFC 为 WebGAL 引入静态组合立绘。作者在单个角色目录中以
figure.json声明画布、位图部件和可复用组合预设;剧本通过changeFigure:<角色模板选择符>/<组合列表>请求组合。引擎将最终有序部件序列预先合成为一张静态图,再完整复用现有图片立绘的位置、变换和进出场演出。背景与目标
WebGAL 当前立绘演出以单张图片为基本单位。AI 生成资源和常规差分立绘常由底图、服装、表情等静态图片叠加而成;若为每个组合导出完整图片,会增加资源管理和导出成本。
本 RFC 的目标是:
非目标
术语
figure.json,声明部件和组合预设。hana/summer_uniform中的hana。changeFigure中斜杠后的逗号分隔名称列表。webgal-composite:hana/summer_uniform_smile,cry。角色模板格式
角色模板路径固定为:
部件资源与模板同处该角色目录,
src从figure.json所在目录解析。{ "Version": 1, "fingerprint": "6a7f9b7d1c4f5c4cb9c6b5e45d6fdf41f5bce4f0ddcfd482b8ba8541a357fb90", "canvas": { "width": 1600, "height": 3000 }, "components": { "body": { "src": "body.webp", "x": 0, "y": 0, "scale": 1 }, "uniform_summer": { "src": "parts/uniform-summer.webp", "x": 0, "y": 0, "scale": 1 }, "face_smile": { "src": "faces/smile.webp", "x": 0, "y": 0, "scale": 1 } }, "presets": { "summer_uniform": ["body", "uniform_summer"], "summer_uniform_smile": ["summer_uniform", "face_smile"] } }ABI 与名称
Version、fingerprint、canvas、components、presets均为必填。Version: 1。缺失字段、未知版本或非法指纹是模板错误,不进行尽力兼容。[A-Za-z0-9_-]+,大小写敏感。画布与部件
canvas.width和canvas.height为正整数,单边不超过8192,总像素不超过16,777,216。src、x、y为必填;scale可省略,默认值为1。x、y必须是有限数,scale必须是正有限数,允许小数。x/y表示部件左上角相对组合画布左上角的设计像素。图片按原始尺寸绘制后等比应用scale。src只能是角色目录内的相对路径。允许子目录和非 ASCII 文件名;禁止绝对路径、URL 和..路径段。src可由多个不同部件引用。预设展开
['body', 'face_smile', 'face_smile']绘制三层。剧本语法
组合立绘使用既有
changeFigure命令:-id时继续使用左、中、右固定舞台槽位;有-id时继续使用同名自由立绘。changeFigure:none和-clear保持既有清除语义,并优先于组合解析。changeFigure:hana/stand.webp不是组合语法。-composite与现有composeFigureDemo 不属于正式语法,将与 Demo 代码一并移除。支持与拒绝的参数
组合立绘支持位置、
-id、-transform、缓动、进出场、时长、-zIndex、-blendMode和现有默认演出配置。下列动态立绘参数与首期静态组合不兼容:
-motion、-skin、-expression、-bounds、-blink、-focus、-mouthOpen、-mouthClose、-mouthHalfOpen、-eyesOpen、-eyesClose。组合命令使用其中任一项时,引擎记录错误并保持该舞台目标当前立绘,不静默忽略。渲染与舞台行为
引擎将最终部件序列绘制到组合画布:
source-over叠加;后出现的部件覆盖先出现的部件。canvas.width与canvas.height,保留透明通道,采用普通图像平滑,不按显示设备 DPR 放大。因此,缓存未命中时旧立绘会持续显示,新组合准备完成后才按现有切换和进场演出替换;首次出场则在准备完成前保持为空。合成或校验失败时保留当前立绘;读档时没有旧立绘可保留的目标保持为空,但其他目标和读档流程继续完成。
模板加载、缓存与持久化
模板加载
figure.json是角色资源中的静态声明文件,不是运行时注册表。某角色第一次被请求时,引擎读取、校验并放入本次进程的模板缓存;同一角色后续切换、预热和合成复用该缓存。游戏重启后首次使用时重新读取模板,以获得当前指纹。引擎还对已读取的原始模板内容计算摘要。该摘要参与缓存验证,使 Terre 外直接修改 JSON 后不会错误采用旧缓存;直接替换图片后仍必须通过 Terre 保存模板或重新导出游戏重建资源指纹。
缓存键与查询顺序
组合缓存键由以下内容组成:
资源指纹已覆盖画布、坐标、缩放、模板定义和资源内容,所以缓存键不重复写入
x/y/scale。舞台状态和存档始终保存作者原始的组合逻辑 URI,不保存 Blob URL、运行时对象 ID 或缓存路径。查询顺序为:
持久化 Blob 解码失败时删除该项并重新合成;失败结果永不缓存,后续相同请求可重试。
持久化缓存
localforage存储库,以游戏键隔离,不写入频繁保存的用户设置对象。已知编码限制
首期使用浏览器原生
canvas.toBlob('image/webp', 1),不新增 WASM 编码依赖。Web 平台没有可移植的lossless或exact控制项,质量参数1也只是编码器提示;因此持久化 WebP 不能保证逐像素无损,可能与本轮直接合成的画面存在轻微差异。该限制只影响持久化缓存,不能生成 WebP 或写入失败时按既有错误策略回退本轮缓存。未来若引入可控制的 libwebp WASM 编码器,必须自动使现有原生编码缓存失效并重新生成,不能要求玩家手动清理缓存。
预取、预热与快进
现有资源预取仍以
<link rel="prefetch">为基础:初始场景窗口为前 24 行,运行中按当前语句后 20 行继续预取。组合立绘在模板尚未加载时先读取模板,随后预取最终需要的普通部件图片;在这些资源可用后才进入组合预热。存档、诊断与资源更新
读档从组合逻辑 URI 按当前模板和资源指纹恢复,不保存历史合成像素。因此,作者更新立绘后旧存档会显示当前组合,而非旧图。
恢复舞台时,引擎等待所需组合准备完成后再继续游戏。模板、资源或合成错误记录选择符、组合列表和原因,供作者诊断;这类错误不向玩家弹窗。只有持久化编码或写入失败才提示玩家,因为它影响玩家主动开启的本地空间功能。
Terre 保存某角色模板时重建该模板指纹;导出游戏前扫描、校验并重建全部角色模板。Terre 外直接修改
figure.json可由运行时模板摘要发现;Terre 外直接替换图片则需要作者保存模板或重新导出游戏。资源指纹改变后旧持久化项不再命中,并异步清理。Terre 交付
Terre 与引擎同一期交付角色模板编辑器。
game/figure/<角色模板选择符>/figure.json打开专用编辑器;其他 JSON 保持普通文本资源行为。ChangeFigure面板提供“普通图片 / 组合立绘”模式;组合模式选择模板和可排序名称列表,可保留重复项,并可跳转模板编辑器。建议:共享规则与手写模板支持
无论最终采用共享包、共享源码或其他交付方式,Terre 与引擎必须使用行为一致的模板校验、展开和指纹规则。
建议在 WebGAL 仓库提供一个版本化、纯 TypeScript 的组合规则包,供 Terre 锁定兼容版本,而不复制规则源码。该包可以负责:
兼容性与迁移
figure.json的游戏、既有普通图片路径和既有单模型立绘不受影响。figure.json不进入普通立绘资源 URL;否则现有.json立绘识别会将其视作 Live2D 模型。角色模板选择符/组合列表识别,含资源扩展名的内容继续视为路径。composeFigureDemo、-composite参数、相关解析分支和测试从主线移除,不提供与正式语义并存的兼容层。实现顺序与验收
composeFigureDemo,在 WebGAL 解析器、运行时、舞台状态、存档和缓存层实现正式组合语义。结论
静态组合立绘以角色模板、顺序叠加、预合成单图和可选持久化缓存为核心。它扩展静态差分立绘的表达力,同时保持 WebGAL 现有单图片立绘架构、舞台演出和存档模型稳定;动态角色能力留给后续独立设计。