选项与分支
给读者选择:一次性与粘性、条件选项、后备选项、选项标签;分支体怎么写、怎么汇合,以及把两段话缝成一句。
选项是互动小说唯一真正必需的东西。这一篇讲怎么给读者选择、选完之后往哪走、几条路怎么再合到一起。
最简单的选项
=== 岔路口 ===
你站在岔路口。
* [走向客栈] -> 客栈
* [沿码头继续走] -> 码头
=== 客栈 ===
窗子里透出橘色的光。
-> 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>和文本里的换行符产生的是段内换行。一段里换了行,不影响它末尾的<>照常缝下一段。
接下来
到这里,一个完整分支故事需要的语法你都有了。再往下是让故事记住读者做过什么,以及让画面动起来——见后面的「变量、条件与逻辑」与「演出命令」两篇。