选项与分支

给读者选择:一次性与粘性、条件选项、后备选项、选项标签;分支体怎么写、怎么汇合,以及把两段话缝成一句。

选项是互动小说唯一真正必需的东西。这一篇讲怎么给读者选择、选完之后往哪走、几条路怎么再合到一起。

最简单的选项

=== 岔路口 ===
你站在岔路口。
* [走向客栈] -> 客栈
* [沿码头继续走] -> 码头

=== 客栈 ===
窗子里透出橘色的光。
-> END

=== 码头 ===
潮水正在退去。
-> END
  • * 开头的一行就是一个选项。
  • [方括号] 里是给读者点的按钮文字。
  • 后面通常跟一个 -> 目标

*+:选过还在不在

写法 行为
* 一次性选项,选过就消失
+ 粘性选项,选过还在,可以反复选

差别只有在读者能回到同一处时才显出来:

=== 大厅 ===
你在大厅里。
* [看那幅画] -> 画
+ [看那座钟] -> 钟
* [出去] -> END

=== 画 ===
画上是一片海。
-> 大厅

=== 钟 ===
钟停在三点二十。
-> 大厅

运行它:看过画之后那一条就没了,而钟可以一直看。

做循环菜单(商店、调查现场、对话轮盘)用 +;一次性事件用 *

选项文字与正文文字

[] 分开的是两样东西:选项列表里显示什么,和点完之后印进正文的是什么

写法 选项显示 点完后正文里出现
* 文本 -> 目标 文本 同一段文本
* [选项文本] -> 目标 选项文本 什么都没有
* [选项文本] 正文文本 -> 目标 选项文本 正文文本
=== 客栈 ===
老板抬起头。
* [我累了。] "辛苦你了,"他回答。 -> 休息
* [还早。] -> 喝酒

=== 休息 ===
你上了楼。
-> END

=== 喝酒 ===
他给你倒了一杯。
-> END

第一条读者看到的按钮是「我累了。」,点完正文里出现的是老板的回答——读者的选择和它引出的叙述可以是两句不同的话,这一条用熟了,对话会自然很多。

条件选项

{条件} 紧跟在 * / + 之后,条件为假时这个选项不显示

~ let gold = 6
-> 摊位

=== 摊位 ===
你数了数:口袋里还有 {gold} 枚铜板。
+ {gold >= 5} [买下灯笼] -> 买
+ {gold < 5} [钱不够,看看就走] -> 走
+ [问问价钱] -> 问

=== 买 ===
~ gold = gold - 5
你把灯笼拎在手里。
-> 摊位

=== 问 ===
「五个铜板。」
-> 摊位

=== 走 ===
你把手缩回袖子里。
-> END

运行它,先买一盏灯笼:回到摊位时「买下灯笼」变成了「钱不够,看看就走」——同一段代码,因为 gold 变了而显示出不同的选项。这就是条件选项要做的事。

条件是一段 JS 表达式,写法见后面的「变量、条件与逻辑」一篇。

后备选项

当一组里所有选项都不可用时,自动走后备选项。形式只有一种:* -> 目标——没有文字、没有 []、没有条件

~ let tried_a = false
~ let tried_b = false
-> 门前

=== 门前 ===
门上有两个锁孔。
* {!tried_a} [试左边] -> 试左
* {!tried_b} [试右边] -> 试右
* -> 没招了

=== 试左 ===
~ tried_a = true
左边纹丝不动。
-> 门前

=== 试右 ===
~ tried_b = true
右边也一样。
-> 门前

=== 没招了 ===
你退后一步,决定另想办法。
-> END

一组选项里最多一个后备选项。它是「读者被逼到死路」时的安全网——没有它,两把锁都试过之后故事就卡住了。

选项标签

给选项打个标签,引擎会自动记住它被选过几次

=== 路口 ===
他站在那里。
* (greet) [问候他] "你好。"
* [无视他] 我什么也没说。

「嗯,」他回应。

* {greet} [问他叫什么] -> 问名字
* [道别] -> 离开

=== 问名字 ===
「叫我老周就行。」
-> END

=== 离开 ===
你转身走了。
-> END
  • (标签) 写在 * / + 之后、[ 或文字之前。
  • 标签等于一个自动计数的全局变量,值是被选过的次数(0 = 没选过)。后面用 {标签} 读它。
  • 标签名必须 ASCII,不能有空格,且全局唯一——不能和别的标签或变量重名。

上面第一组选项没有 ->、也没有分支体,所以选完之后接着往下执行到「「嗯,」他回应。」——那是下一节「分支体」要讲的汇合。(第二组两条都有 ->,各自跳走。)

命名的分界线一以贯之:节点名和正文可以中文,变量名(包括选项标签)必须英文。一眼就能分出哪些是叙事、哪些是逻辑。

分支体:选完之后就地写

选项不一定要跳走。用行首的 > 就地写「选了这条之后发生什么」:

=== 路口 ===
「什么事?」我问。

* [我累了。]
> 「辛苦你了。」
* [没什么。]
> 「好吧。」
* [假装没听见。]
> 我什么也没做。

我们继续前行。
-> END

三条分支各自执行完,遇到没有 > 的那一行——自动汇合,不管读者选了哪条,都会走到「我们继续前行。」

> 的个数就是层级。 层级减少就是内层汇合到外层:

=== 餐厅 ===
「你想吃什么?」

* [吃米饭]
> 你点了米饭。
> 「需要什么菜?」
> * [青菜]
> > 你点了青菜。
> * [肉]
> > 你点了肉。
> 服务员记下了。
* [吃面]
> 你点了面。

「好嘞,」服务员说。
-> END
  • 第一层 > 是顶层选项的分支体。
  • 第二层 > > 是嵌套子选项的分支体。
  • > > 回到 > = 子选项汇合(「服务员记下了。」)。
  • 回到没有 > = 整组结束、全部汇合(「好嘞,服务员说。」)。

建议不要超过三层,再深可读性就崩了。

写法细节

规则 说明
行首 > 的个数 决定层级
行首空白 会被裁掉。建议不要写前导空白
> 之间的空白 可有可无,>>>> > > 等价。建议加空格并与 * 对齐
> 之后的空白 可有可无。建议加空格,好读
行首要输出字面的 > \> 转义
> 体里的空行 忽略,随便空

跳走的就不汇合

选项有两种「跳走」,都使它不参与汇合

* [回家] -> 家中            ← 直接跳走,不汇合
* [等等]
> 等了又等。                 ← 体结束后自动汇合
* [离开]
> 转身就走。
> -> 街上                    ← 体内显式跳走,不汇合

都白等了。                   ← 只有「等等」会走到这里

把两段话缝成一句

Kiny 默认每段文本之后换行。<> 贴在一段文本的末尾,取消它之后的换行,让下一段直接接上来——哪怕中间隔着一次跳转

我转身离开<>
-> 走廊

=== 走廊 ===
,头也不回。
-> END

读者看到的是一句话:我转身离开,头也不回。

  • <> 永远贴在文本一侧,不贴在 -> 上。跳转独立成行时它落在文本行末;跳转内联时它夹在中间:文本<> -> 目标
  • 缝句一律在源头声明。如果一个节点被好几处跳进来、都想缝上,得在每个入口各写一个 <>
  • 没有行首的 <>。要输出字面的 <\<
  • 它和段内换行是两回事:<> 管的是「这一段之后接不接下一段」,而 <br> 和文本里的换行符产生的是段内换行。一段里换了行,不影响它末尾的 <> 照常缝下一段。

接下来

到这里,一个完整分支故事需要的语法你都有了。再往下是让故事记住读者做过什么,以及让画面动起来——见后面的「变量、条件与逻辑」与「演出命令」两篇。