正文写作

一行就是一段、缩进不算数、标点随你写;加粗变色换字体,以及把悬念停在句子中间。

写正文是 Kiny 里最不需要学的部分:一行普通文字,就是读者会读到的一段话

雾从港口涌上来,遮住了路灯。
你站在码头边,鞋底能感到木板在动。

远处传来一声汽笛。
-> END

没有任何标记。这一篇讲的是在这个基础上还能做什么。

行与段落

  • 每一行非控制行 = 一段输出,行末自动换行。
  • 空行会被忽略。源文件里想怎么空行就怎么空——分隔场景、隔开长段落,对输出没有任何影响。
  • 行首行末的空白会被裁掉。

想让两段之间不换行(下一段直接贴上来),在前一段末尾加 <>,见选项与分支那一篇末尾的「粘连」。

哪些行不是正文

一行只要以下面这些符号起首,就是控制结构而非正文:

起首 是什么
=== / = 节点 / 子节点声明
* / + 选项
> 分支体层级
-> 跳转
~ / ~~~ JS 逻辑
@if / @elif / @else 条件块
@名字(...) 命令
// / /* */ 注释

除此之外的一切都是正文。

缩进不算数

普通正文里的缩进没有任何语义,纯粹是给你自己看的。下面三段完全等价:

你打开门。
你走进去。
    你打开门。
    你走进去。
你打开门。
    你走进去。

分支的层级不靠缩进,靠行首的 >(见选项与分支)。所以你可以放心地用缩进排版,不会改变故事的结构。

标点随你写

引号、书名号、破折号、省略号——全都是普通字符,没有任何特殊含义。中英文标点想怎么混用就怎么混用:

「想要点什么?」老板问。
'Hello,' he said.
"我累了,"我说,"明天再聊吧。"
——他没有回答。
-> END

Kiny 不管你的标点风格。

需要转义的符号

少数符号在正文里有含义,想输出它们本身就在前面加一个反斜杠 \

任何位置都要转义(这几个在行内随处都有意义):

得到 不转义会被当成
\{ \} { } 表达式插值
\< < 富文本标签 / 粘连
\/ / 注释(写网址时用得上:http:\//…
\\ \ 转义符自己

只有在行首才要转义(它们只在行首是控制标记):

\= \* \+ \> \~ \@ \->

数学里 3 \< 5,集合写作 \{1, 2, 3\}。
\* 这一行以星号开头,但它是正文,不是选项。
-> END

只在选项行里要转义:选项文字里要出现字面的 [ ] ( ) 时写 \[ \] \( \)

注释

写给自己看、不给读者看的话:

// 这一整行是注释
这一行会显示。   // 行尾也可以,从 // 到行末都不输出
/* 跨行的注释
   写草稿、临时关掉一段的时候好用 */
-> END
  • // 到行末为止。正文行与选项行的行尾同样生效——这正是上面那张表里 \/ 要在任何位置转义的原因:网址里的 // 会被当成注释起头,得写成 http:\//example.com
  • /* */ 可以跨行。
  • 注释不进输出,也不进存档。

有两处不能写行尾注释,写了会报出指不到根因的错:

@sleep(100)  // ✗ 报「命令实参缺少右括号」
-> 客栈      // ✗ 会把「客栈 // …」整个当成节点名

命令行与跳转行要注释就另起一行写。~ 逻辑行和 ~~~ 块里的 // 归 JS 自己管,照常有效。

加粗、变色、换字体

正文和选项文字里都可以用一组行内标签:

标签 作用 取值
<b>…</b> 粗体
<i>…</i> 斜体
<u>…</u> 下划线
<s>…</s> 删除线
<color=值>…</color> 颜色 #c00 / #cc0000 / red 这类具名色
<size=倍数>…</size> 字号 相对当前正文的倍数,如 1.50.8
<font=名>…</font> 字体 项目里自动注册的字体族名,或系统字体名
<class=名>…</class> 语义类 交给作品 CSS 去定义样式
<br> 换行 自闭合
她说:<b>别回头</b>,然后<color=#c00>消失在<i>雾</i>里</color>。
这个词<size=1.5>很大</size>,这个<size=0.8>很小</size>。
第一行<br>第二行。
-> END

几条规则:

  • 可以嵌套,样式叠加:<b><color=red>红色的粗体</color></b>。同类标签嵌套时内层覆盖外层。
  • <class> 是累积的——外层 <class=letter> 和内层 <class=old> 会同时挂上。类名渲染时会加 kin- 前缀交给作品 CSS,所以它是「把样式钩子从正文交给样式表」的正规方式。
  • 取值是受限的color 只认颜色、size 只认正数、font 只认单个族名。标签里写不了任意 CSS——那是有意的,防止经正文注入样式。要精细控制就用 <class=名> 加作品 CSS,那边完全开放。
  • <font> 找不到字体会回退到正文默认字体,不影响阅读。
  • 裸的 < 一般没事:只有当它后面正好构成一个合法标签时才会被当标签,否则按字面输出(1 < 2 照常显示)。想保险就写 \<
  • 插值出来的内容不会被二次解析{...} 求值的结果按纯文本插入,里面的 <…> 不当标签。

换行符 = <br>

文本里的换行符和 <br> 完全等价。这一条主要在插值上用得着——JS 里拼出来的多行字符串插进正文,就是按行显示的,不必把 <br> 塞进 JS 字符串:

~ let bag = { 绳子: 2, 火折子: 0, 干粮: 3 }
~~~
let list = ""
for (const [item, n] of Object.entries(bag)) {
  if (n > 0) list += `- ${item}:${n}\n`
}
~~~
{list}

换行不做修剪:写几个就换几行,首尾也一样。上面每项末尾都有 \n,所以最后会多空一行——不想要就给最后一项别加,或者改用 join("\n")

把悬念停在句子中间

<pause> 让打字揭示在句子中间停住。两档:

凶手就是……<pause>你自己。
门开了一条缝<pause=2000>,什么都没有。
-> END
  • 点击档 <pause>:前半句显示后停住、推进提示三角亮起,读者点一下才续显后半句。
  • 毫秒档 <pause=2000>:停满 2 秒自动续显,三角不亮(没在等读者),等待期间点击完全无效

运行上面那段试试两种的差别。

几条细节:

  • 行内任意位置、想放几个放几个;可以落在 <b> / <color> 的范围里,不影响样式。
  • 停顿跳不过去:点击档在打字过程中点击只会让当前这一段立刻显示,仍然停在标记处,再点一次才续下一段。
  • 连着写多个标记只停一次,档位取最后一个<pause><pause=500> 是 500 毫秒档)。写在行尾的会被忽略——行尾本来就是边界。
  • 只对正文行有效。选项列表里的标记不产生停顿(选项是一次性呈现的按钮,没有揭示过程);选中之后印进正文的那部分文字照常生效。
  • 读者开了「减弱动态效果」时每段瞬显,但停顿仍然保留——它是叙事节奏,不是动画效果。
  • 毫秒档的取值必须是正整数<pause=0.5> / <pause=0> / <pause=-1> 都会报错。
  • 自闭合写法 <pause/> / <pause=500/> 等价。要输出字面的 <pause>\<pause>

想在行与行之间留白,用 @sleep(毫秒),见后面的「演出命令」一篇。<pause> 只管句子中间。

台词着色

在项目根声明角色之后,台词行开头标出说话人,那一行就会按角色着色:

<克里斯托弗> 我今天出门了。
阿黎娅:外面在下雨。

两种写法可以混用,只有声明过的名字才会触发——正文里的「他说:」「时间:三点二十」不受任何影响。完整写法与配色见后面的「角色与台词着色」一篇。

写坏了会怎样

标签没闭合、颜色值不合法、字号写了负数——运行期都不会崩:未闭合的标签自动闭到这一段末尾,不合法的取值就是不应用样式。同时编辑器底部会报错标红,方便你修。

接下来

正文之外,故事需要结构——下一篇讲节点与跳转