DOCUMENTATION · 文本剧本语法
文本剧本语法
编辑器中间栏的「文本」模式用的就是这套语法:文本是真源,模型是它的产物。 (另外两种模式:表单是点选式编辑;JSON 直接显示引擎真正加载的那份剧情,可改 —— 见 ARCHITE…
硬契约:
parse(stringify(doc))必须深等于doc(tests/editor-text.test.ts钉住)。 所以本文档里的每种写法都有对应的回写规则 —— 你写的东西不会被静默改写。
0. 一张全貌
// 整行注释(也支持 > 开头)
# 剧名 ← 第一个一级标题是剧名
作者:某某 ← 可选
# 第一幕 ← 之后的一级标题都是「幕」,一直延续到后面的场
## 门厅(内景 · 锈蚀的门厅 · 夜) ← 场:## 场名(可选:打印用的场标题行)
铁门被推开,灰尘落下来。 ← 不带「:」的普通行 = 动作 / 舞台指示
铁颚(低声):进去,或者滚。 ← 「名字(表演提示):台词」——提示可省
我说完了。 ← 缩进的行 = 上一句对白的**续段**(同一人连续多段)
旁白:风从缝里灌进来。 ← 说话人写「旁白」就是旁白
设置:item.gold = item.gold + 10 ← 写入状态(右边是**表达式**)
选择:怎么办? ← 选择块
- 拿钥匙 -> 里间
- 空手进去(如果 item.gold >= 10)-> 里间
- 就此离开 ← 省略 `->` 就是"选它剧本结束"
如果 item.gold >= 10 -> 富人区 ← 条件分支
否则 -> 贫民窟
跳到 门厅 ← 跳转(目标写场名)
结局:好结局 ← 结局块;其后的普通行是结局正文
你活着出来了。1. 标题与幕
| 写法 | 含义 |
|---|---|
# 剧名 | 第一个一级标题 = 剧名 |
# 第一幕 | 之后的一级标题 = 幕;幕会一直延续到后面的场,直到出现下一个幕 |
## 场名 | 场 |
## 场名(内景 · 地点 · 夜) | 场 + 场标题行(括号里是给打印稿用的行头) |
幕只影响结构与排版(大纲分组、打印稿的幕标题),不影响引擎。 体检会提醒"部分场有幕、部分没有"这种不一致。
2. 行与节拍
| 写法 | 编译成引擎的 | 说明 |
|---|---|---|
铁门被推开。 | say(无说话人) | 不带「:」的普通行 = 动作 / 舞台指示 |
铁颚:台词。 | say | 对白 |
铁颚(低声):台词。 | say(文本 (低声)台词。) | 表演提示;编译时并进台词,因为游戏里也要交代 |
旁白:…… | say(无说话人) | 就是说话人叫"旁白" |
缩进的行 | 并入上一句对白 | 续段:同一说话人连续多段(段落之间以换行分隔) |
选择:提示 + - 选项 -> 场名 | choice | 见 §3 |
如果 表达式 -> 场名 + 否则 -> 场名 | observe/goto 分支 | 见 §4 |
设置:item.gold = item.gold + 10 | set | 写入状态;右边就是表达式 |
跳到 场名 | goto | 目标写场名(不是 id) |
结局:名字 + 正文行 | end | 结局正文可多行 |
续段的规则:行首有空白(半角/全角空格、制表符)就算续段。 如果它前面不是对白(例如前面是动作或什么都没有),这一行按动作处理 —— 不丢内容。
3. 选择
选择:怎么办? ← 提示可省:直接写「选择:」也行
- 拿钥匙 -> 里间
- 空手进去(如果 item.gold >= 10)-> 里间
- 就此离开 ← 没有 `->` = 选它剧本结束- 条件写在箭头之前的括号里:
(如果 <表达式>)(全角/半角括号都收)。 - 选项、条件、跳转目标都会被体检检查(未登记场名、写错引用等)。
4. 条件分支
如果 item.gold >= 10 -> 富人区
如果 flag.kind -> 被救的人
否则 -> 贫民窟 ← 兜底;没有 `->` 表示这一支结束「如果」后面到箭头之前的整段就是条件表达式。 表达式合法与否由引擎求值器判定,编辑器会把不合法的地方指到具体行号。
5. 表达式(可用状态项)
状态分四个命名空间:var(数值)、flag(布尔)、item(物品计数)、actor(按角色的对象)。
item.gold >= 10
flag.brave == true
num(var.gold) + 10 > var.debt
actor.hero.hp <= 0支持的运算符:== != >= <= > < + - * / && || !,
以及 num() / str() / bool() 等转换函数(详见剧情格式里的表达式一节)。
文本模式不做"人话翻译":条件就是表达式。编辑器负责的是把你写错的地方指出来 (行号 + 原因),而不是替你绕开它。想点选式构造条件,用「表单」模式。
6. 状态项:编辑器给你的是"信息",不是"翻译"
编辑器会自动收集本剧本里出现过的状态项(var. / flag. / item. / actor.),
在右侧「状态项」面板里列出谁写的、谁读的:
item.gold 写×1 · 读×1
var.debt 只读没写 ← 这种值得你确认一下:是不是忘了写?
flag.brave 读×2它的用途是省去"去别处翻变量名",以及暴露"读了但从没写过"这种可疑情况。 它不会把表达式翻译成人话 —— 条件与写入就是表达式,编辑器负责把写错的地方指出来 (行号 + 原因),而不是替你绕开它。
7. 注释与空行
// 这一行是注释或> 这一行也是注释:整行忽略。- 空行随意;连续空行会被规整(生成文本时最多留一个空行)。
8. 出错时会怎样
解析不抛异常,返回带行号的诊断:
| 诊断 | 例子 |
|---|---|
| 跳向不存在的场 | 跳到 门厅 但没写过 ## 门厅(会指出这一行的行号) |
| 场名重复 | 两个 ## 门厅(会同时指出第一次在哪一行) |
| 条件不合法 | 如果 item.gold >= -> 场 |
| 选项没有选择块 | - xxx 前面没有 选择: |
## 之前就写内容 | 自动放进「未命名场」并提醒 |
只要还有 error,编辑器就不会用解析结果替换你的内容 —— 写坏了不会毁稿。 离开文本模式时如果内容有变,会压一次撤销栈(表单模式里 Ctrl+Z 能整段退回来)。
9. 编辑器表达不了的节点会怎样
导入别人的剧情时,遇到编辑器表达不了的东西(插件的 plugin、监听事件的 observe、
等待的 wait、自定义 ui 等),它们不会丢:编辑器把这一整段原样带走,
并在节拍列表里标成 ⟪原样保留:1 个节点(wait)⟫。你改你的台词,导出时它们接回原位,
引用也跟着重新指向(即使编辑器给你认识的节点重新编过号)。
一句话:你看不到它们的内容、也改不了,但不会把它们弄丢。
另外,"类型认识"的节拍上多带的字段(say 的 effects、choice 的 timeoutMs / timeoutNext…)
也一样保留:表单里只读标出,导出时原样写回、引用跟着重新指向。
但文本是真源 —— 你在文本里改动了某一场的节拍数量/顺序(结构变了),那一场的附加字段
就接不回去了(编辑器会明确告诉你,不会悄悄弄错)。要精确保住它们,用 JSON 模式编辑。
10. 有意不做的(以及为什么)
- 双栏对白 / 页标记 / 修订标记:属于排版工具(Final Draft 之类)的领域, 本编辑器只负责"能编译进游戏、能导出可打印稿"的那一层。
- 行内混合标记(粗体、颜色):文本模式的输出会被回写成规范的等价写法, 引入富文本标记会破坏 §0 那条往返契约。