权威性说明:本文档所有结论均对照
OpenWebGAL/WebGAL解析器源码与官方 demo 场景核实, 而非来自 LLM 记忆。核对源:
packages/parser/src/config/scriptConfig.ts(命令全集)packages/parser/src/scriptParser/{scriptParser,commandParser,contentParser}.ts(解析规则)packages/webgal/src/Core/gameScripts/say.ts(say的 speaker 继承与-clear)packages/webgal/public/game/scene/*.txt(官方 demo)
⚠️ OpenWebGAL/script-specification仓库目前只有流程说明,没有实际规范内容,不要指望它。
| 常见错误说法 | 真相 |
|---|---|
场景文件是 .wg |
❌ 是 .txt,放在 game/scene/ |
对话写 say:角色:文本 |
❌ 对话是 角色名:文本;;旁白是 say:文本 -clear;(见 §2.1,漏 -clear 会继承上一句说话人) |
有 webgal build / webgal serve CLI |
❌ 不存在。npm webgal 是 0.0.0 占位包,WebGAL-Server 2022 年已 archived。正确做法是静态模板克隆 + 文件覆盖,然后任意静态服务器托管 |
命令:内容 -参数=值 -布尔参数; // 分号之后是行内注释
- 一行一条语句,按
\n切分。 ;终结语句,其后同行内容全部是注释。- 空行安全(解析为 comment 类型,跳过)。
裸的 // 注释 行会被当成对话渲染出来。因为解析器先按 ; 切,//注释 前面没有 ;,
整行就成了语句内容。
;这是安全的注释 ← 以分号开头,前段为空 → 识别为注释
changeBg:bg.webp; // 这也安全 ← 跟在分号之后
// 这行会显示在游戏里! ← 错误
\: \, \. \; 分别转义冒号、逗号、句点、分号。choose 内还需 \|。
WebGAL:我会显示出来:\:\,\.\;不信你看看;
\. 尤其重要——choose 的跳转目标若含 . 会被当成场景文件名而非 label。
// commandParser.ts
function getCommandType(command) {
return SCRIPT_CONFIG_MAP.get(command)?.scriptType ?? commandType.say; // 默认 say
}
if (type === commandType.say && commandRaw !== 'say') {
additionalArgs.push({ key: 'speaker', value: commandRaw }); // 命令名 → 角色名
}WebGAL 遇到不认识的命令不会报错,而是把它当成角色名,把参数当成台词渲染出来。
| 输入 | 实际渲染 |
|---|---|
WebGAL:你好; |
角色「WebGAL」说「你好」✅ |
say:你好 -clear; |
旁白「你好」(无角色名)✅ |
:你好 -clear; |
旁白「你好」✅ |
showCode:print(1); |
❌ 角色「showCode」说「print(1)」——LLM 幻觉命令的典型下场 |
say:小明:你好 -clear; |
❌ 旁白显示字面文本「小明:你好」 |
这是 Repo2Gal 必须做 validator 的根本原因:产物永远"能跑",但会静默错渲染。 靠肉眼看游戏发现问题的成本极高,必须在生成后、打包前用白名单校验。
packages/webgal/src/Core/gameScripts/say.ts(4.6.2)对 speaker 的处理是先继承:
let showName = stageState.showName; // 先默认继承
if (speaker !== null) {
showName = speaker;
}
if (clear) {
showName = '';
}
stageStateManager.setStage('showName', showName);say:文本; 没有 speaker 也没有 clear,于是显示的是上一句角色名。
正确写法是:
角色A:你好;
say:这是旁白 -clear; ← 不清除就会顶着「角色A」的名字显示
无冒号的纯文本行更糟:commandParser.ts 会把整行当作 speaker,
同一段文字又出现在台词里。因此 Repo2Gal 的 validator 会把所有旁白
(含 say:、:文本、无冒号文本、未知命令降级)确定性改写为
say:文本 -clear;,LLM 和 --script 输入都不例外。
| 命令 | 示例 | 说明 |
|---|---|---|
say |
say:文本 -clear; |
旁白;等价于 :文本 -clear;。-clear 必带,否则继承上一句 speaker |
| (角色名) | WebGAL:文本 -v1.wav -left; |
对话。任何非命令词都作为 speaker |
changeBg |
changeBg:bg.webp -next; |
切背景 → game/background/ |
changeFigure |
changeFigure:stand.webp -left; |
切立绘 → game/figure/ |
bgm |
bgm:s_Title.mp3 -volume=80 -enter=3000; |
BGM → game/bgm/ |
playEffect |
playEffect:se.mp3; |
音效 → game/vocal/ |
intro |
intro:第一行|第二行 -hold; |
黑屏文字演出,| 分行 |
changeScene |
changeScene:next.txt; |
切场景(不返回) |
callScene |
callScene:sub.txt; |
调用子场景(会返回) |
end |
end; |
结束游戏 |
miniAvatar |
miniAvatar:avatar.webp; |
小头像 |
changeFigure:none是官方「清空立绘」的保留取值,不是素材名:确定性编译内核用它实现 角色退场(performance._compile_action的figure.exit),validator 的素材白名单通过webgal.RESERVED_ASSET_REFERENCES放行它。把它当缺失素材降级会导致角色不退场, 并且--strict下整次运行失败。
| 命令 | 示例 |
|---|---|
label |
label:chapter2; |
jumpLabel |
jumpLabel:chapter2; |
jumpLabel + 条件 |
jumpLabel:high_score -when=varScore>1; |
choose |
choose:选项A:label_a|选项B:label_b; |
setVar |
setVar:a=3; / setVar:a=a + 3; |
setVar + 条件 |
setVar:bg=x.webp -when=a>2; |
if |
if:条件;(条件跳转,实际更常用 jumpLabel -when=) |
showVars |
调试用,打印所有变量 |
pixiInit pixiPerform playVideo setAnimation setTempAnimation setComplexAnimation
setFilter setTransform setTransition setTextbox filmMode applyStyle wait
unlockCg unlockBgm getUserInput chooseLabel callSteam
choose:选项文本:跳转目标|选项文本:跳转目标 -defaultChoose=1;
跳转目标含 . → 视为场景文件;否则视为 label。
choose:(varHasTicket==true)->可见路径:var_ticket_ok|[varDoorPower>1]->能量路径:var_power_path;
| 语法 | 含义 |
|---|---|
(条件)->选项:目标 |
显示条件,不满足则选项不出现 |
[条件]->选项:目标 |
启用条件,不满足则灰显不可选 |
-defaultChoose=N |
快速预览时默认选第 N 项 |
setVar:speaker=WebGAL;
{speaker}:背景现在是 {bg}。; ← {var} 插值
{speaker}:\{bg\} 不会插值。; ← 转义花括号
WebGAL:这是[彩色文本](style=color:#B5495B\;),还支持[注](zhù)[音](yīn)。
WebGAL:[整段富文本](style-alltext=font-style:italic\; style=color:#66327C\;)
[文本](style=CSS)— 局部样式,CSS 内的;要写成\;[文本](style-alltext=CSS)— 作用于整条语句[汉字](拼音)— 注音
引擎原生支持富文本,配
game/userStyleSheet.css可做等宽代码块。 早期 v2 文档里那套「三层降级策略」是在解决一个不存在的问题。
game/
├── config.txt # 游戏配置
├── scene/ # 场景脚本 *.txt ← changeScene / callScene
├── background/ # 背景 ← changeBg / unlockCg
├── figure/ # 立绘 ← changeFigure / miniAvatar
├── bgm/ # 背景音乐 ← bgm / unlockBgm
├── vocal/ # 语音与音效 ← playEffect / 对话的 -v1.wav
├── video/ # 视频 ← playVideo
├── animation/ # 动画预设
├── template/ # UI 模板
├── userStyleSheet.css # 自定义 CSS
└── flowchart.json
脚本里只写文件名,不写路径——引擎按命令类型自动补目录:
changeBg:bg.webp; → game/background/bg.webp ✅
changeBg:assets/bg/x.jpg;→ game/background/assets/bg/x.jpg ❌ 早期文档的错误写法
入口场景固定为 game/scene/start.txt。
Game_name:游戏标题;
Game_key:唯一存档键;
Title_img:标题背景图.webp;
Title_bgm:标题音乐.mp3;
Game_Logo:logo.webp;
Enable_Appreciation:true;
Enable_Continue:true;
Enable_flowchart:true;
生成剧本时把模型限制在这几条内,配合 validator 白名单,出错面最小:
;=== 允许使用的全部语法 ===
changeBg:文件名.webp -next;
changeFigure:立绘文件名.webp -left;
bgm:文件名.mp3;
角色名:台词;
say:旁白 -clear;
intro:黑屏文字|第二行;
label:标签名;
jumpLabel:标签名;
choose:选项一:标签一|选项二:标签二;
end;
不在此列的一律由 validator 降级为旁白,绝不放行到产物里。
模型即使漏写 -clear,validator 也会为所有旁白确定性补上。
使用 Asset Pack 时,LLM 与 validator 阶段的三个资源参数是逻辑 ID(例如
changeBg:background.archive;),打包阶段再由 webgal_assets.py 改写成上述裸文件名。
未声明 ID 或素材类型错配会被 validator 注释降级,不会进入 WebGAL 产物。
动态演出不再由 LLM 直出 WebGAL 命令:三轮生成中的第三轮输出 Director Plan v1 语义 JSON,
repo2gal/director.py 再根据固定的 WebGAL 4.6.2 能力表生成 setTransform、
setTempAnimation、pixiInit 和 pixiPerform。模型不能提供坐标、关键帧、runtime target
或 -next、-parallel、-continue 参数。设计和动作 Schema 见 docs/dev/director-plan-spec.md。
screen.transition 是例外:编译器不在台词前追加独立动画,而是修改同 beat 的背景命令,例如
changeBg:bg.webp -enter=shockwaveIn -enterDuration=1200;,保证切换和效果同时发生。
编译产物由确定性代码生成,经 validator 校验时使用比 SAFE_COMMANDS 更宽的
COMPILE_COMMANDS 白名单(含上述四个命令);--script 用户脚本仍收敛在
SAFE_COMMANDS 内。validator 对两类输入都是硬边界。
Asset Pack 角色声明 framing.mode=upper-body 时,WebGAL Adapter 会在最终脚本中确定性追加:
changeFigure:character.png -transform={"position":{"x":-9,"y":326},"scale":{"x":1.538,"y":1.538}};
WebGAL 4.6.2 使用 2560×1440 Pixi 设计舞台;position 是相对默认槽位的设计坐标偏移,
scale 作用于已经按完整纹理 contain 适配后的立绘。正 y 向下移动,超出 1440 的腿部由
舞台裁切。不得用 userStyleSheet.css 尝试选择单个 Pixi sprite。