自用 ink 语法总结
至于 ink 是什么,因为我是懒蛋就不写了,请看文内链接。
目录
ink 简介(抱意思真就这么懒,一个字不肯多写了……)以及 inklewriter。
本篇包括了 ink 的大多数基础语法,并在进阶内容部分标注了官方文档的链接,仅建议在对 ink 有所了解的情况下作为快速查找的索引使用。本篇不涉及任何开发相关内容。
注释(comment) #
// 单行注释
/*
多行注释
*/
TODO: 任务提醒
选项(choice) #
一次性选项使用星号。
这是一段正文
* 第一个选项
第一个选项后的文字
* 第二个选项
第二个选项后的文字
// 回退选项(fallback choice)
* -> next // 当所有选项都被选择过,没有选项剩余可供选择时,该选项会自动触发,跳转至`next`部分
// 另一种结束手段,直接跳转文字
* ->
这里没有更多选项了
-> DONE
(关于DONE
,参见官方文档。)
会重复出现的选项使用加号。
这是一段正文
+ 第一个选项
第一个选项后的文字
+ 第二个选项
第二个选项后的文字
选项与输出的显示 #
默认情况下,选项会出现在内容文本中,就像是整个故事的一部分。如果不希望显示选项,用方括号包裹即可。
这是一段正文
* [第一个选项] // 后续文字出现时不会和这行文字一起
第一个选项后的文字
* [第二个选项] // 后续文字出现时不会和这行文字一起
第二个选项后的文字
另外,当选项代码呈现为AAA[BBB]CCC
格式时,AAA
部分选项和输出均显示,BBB
部分只有选项中显示,CCC
部分只有输出中显示。即选项显示为AAABBB
,输出显示为AAACCC
。
而当选项代码呈现为[BBB]CCC
格式时,则选项显示为BBB
,输出显示为CCC
。
条件检查 #
这是一段正文
* {sample_knot} 第一个选项
第一个选项后的文字
* {sample_stitch} 第二个选项
第二个选项后的文字
访问过sample_knot
时,才会出现选项一。访问过sample_stitch
时,才会出现选项二(关于 knot 和 stitch,详见后文。)
这是一段正文
* {not sample_knot} 第一个选项
第一个选项后的文字
* {not sample_stitch} 第二个选项
第二个选项后的文字
未访问过sample_knot
时,才会出现选项一。未访问过sample_stitch
时,才会出现选项二。
这是一段正文
* 第一个选项
第一个选项后的文字
* {sample_knot} {sample_stitch} 第二个选项
第二个选项后的文字
条件可以叠加。
这是一段正文
* 第一个选项
第一个选项后的文字
* {sample_knot > 2} 第二个选项
第二个选项后的文字
访问sample_knot
大于 2 次时,才会出现选项二。
结点(knot)& 跳转(divert) #
结点由两个或更多等号表示,末尾的等号非必要。
=== 这是一个结点 ===
实际命名结点时,需要使用没有空格的单个英文词
-> END // 跳转至故事结束
这是一段正文
* 第一个选项
第一个选项后的文字
-> choice_one // 跳转至结点“choice_one”
* 第二个选项
第二个选项后的文字
-> choice_two // 跳转至结点“choice_two”
结点之间不能重名。每个结点的末尾,都必须有一个跳转。
// 这样的语法也成立,区别在于最终呈现时,两句话不分行。
这是一段正文 -> next
=== next ===
这是另一段正文
INK 始终从文件的顶部开始,然后一直向下运行。但如果你将所有内容拆分为结点,则务必确保在文件顶部至少有一个初始跳转(如
-> top_knot
),以告诉它以哪个结点开始。修改初始跳转,可更方便的进行测试。
胶水(glue) #
在句首或句尾使用<>
,同样会使得后接或前接的句子强制不换行。
子结点(stitch) #
=== 这是一个结点 ===
实际命名结点时,需要使用没有空格的单个英文词
-> next
= 这是一个子结点
实际命名时,同样需要使用没有空格的单个英文词
-> END
一个结点内部的子结点不能重名。
作用域(scope) #
在结点外部时,需要使用结点.子结点
的格式才能跳转,如下所示:
-> sample_knot.sample_stitch
=== sample_knot ===
这是一段正文
-> sample_stitch
= sample_stitch
这是另一段正文
-> END
编织(weave) #
聚集(gather) #
作用是将分支剧情收拢到同一个后续剧情上。
聚集前:
* 选项 A
剧情 A
-> 后续结点
* 选项 B
剧情 B
-> 后续结点
=== 后续结点 ===
后续剧情
聚集后:
* 选项 A
剧情 A
* 选项 B
剧情 B
- 后续剧情
无需短横线,继续编写剧情即可
* 选项 C
剧情 C
* 选项 D
剧情 D
- 聚集也可以连续使用
嵌套流程(nested flow) #
作用是在分支剧情上继续展开分支剧情。
* 选项 A
剧情 A
** 选项 A1
剧情 A1
** 选项 A2
剧情 A2
-- 后续剧情 A // 也可以加入聚集
* 选项 B
剧情 B
- 后续剧情
选项和聚集能够无限嵌套,只要在每一层使用对应数量的星号和短横线即可。
追踪(tracking)& 标签(label) #
标签(label_name)
用于标记选项或剧情文本。
* (choice) 选项 A
剧情 A
* 选项 B
剧情 B
- (content) 后续剧情
标签可以用大括号检查是否访问过,也可以作为跳转的目标。
* {choice} 选项 C
剧情 C
* {content} 选项 D
剧情 D
剧情 1 -> choice
剧情 2 -> content
作用域(scope) #
如果标签在结点内被引用,则可以直接使用。如果标签在结点外被引用,则采用结点名.标签名
的格式。如果标签在子结点内,但在结点外被引用,则采用结点名.子结点名.标签名
的格式。如下所示:
=== knot_1 ===
- (gather_1)
* {knot_2.stitch_2.gather_2} 选项 A
=== knot—_2 ===
= stitch_2
- (gather_2)
* {knot_1.gather_1} 选项 B
变量(variable) #
VAR name = value // 全局变量
CONST name = value // 常量,无法更改
~ temp name = value // 局部变量,仅能在同一个结点中访问,结点外无法使用
// 在剧情中为变量赋值
这是一段正文
~ name = "名字"
// 在剧情文本中使用变量时,要用花括号将其包裹
我的名字是{name}
变量可存储的五种数据类型如下所示:
VAR string = "名字" // 字符串
VAR integer = 5 // 整数
VAR float = 3.14 // 浮点数
VAR boolean = true
VAR boolean = false // 布尔值
VAR divert = -> next // 跳转
变量可进行的数学运算包括:加法(+
)、减法(-
)、乘法(*
)、除法(\
)、取余数(%
)。
变量可进行的逻辑运算包括:相等(==
)、大于(>
)、小于(<
)、大于或等于(>=
)、小于或等于(<=
)、与(and
,&&
)、或(or
,||
)、非(not
,不建议使用!
)。
// 组合使用,可以形成十分复杂的判定条件
{ conditionA and not conditionB:
这是一段正文
}
{ (conditionA or conditionB or conditionC) and not conditionD:
这是一段正文
}
任何使用花括号包裹的内容,都会进行相关的计算和判断。{ x > 1 }
这样的代码会输出true
或者false
,也可以直接进行{x + y}
、{5 + 3}
这样的运算。
逻辑运算(conditional logic) #
内联逻辑(inline logic) #
VAR variableOne = true
VAR variableTwo = 4
{variableOne:如果变量为true,这句话将出现|如果变量为false,这句话将出现}
{variableTwo < 5 :如果`variableTwo < 5`为真,这句话将出现|如果`variableTwo < 5`为假,这句话将出现}
多行逻辑(multi-line logic) #
VAR variableOne = true
{variableOne:
如果变量为true,这句话将出现
- else:
如果变量为false,这句话将出现
}
关于 if-else(和同样适用于多行的 switch)语法,详见下文。
参数(parameter) #
类似变量的还有参数。
-> main
=== main ===
你选择的宝可梦是?
* [妙蛙种子]
-> chosen("妙蛙种子")
* [小火龙]
-> chosen("小火龙")
* [杰尼龟]
-> chosen("杰尼龟")
=== chosen(pokemon) ===
你选择了{pokemon}!
-> END
函数(function) #
// 定义
=== function add(a, b) ===
~ return a + b
// 调用
~ x = {add(5, 3)}
更多内容,详见官方文档。
内置函数(built-in function) #
详见官方文档。
RANDOM(min, max) // 随机创建数字,包括最小值和最大值
{INT(3.2)} is 3.
{INT(-4.8)} is -4.
{FLOOR(4.8)} is 4.
{FLOOR(-4.8)} is -5.
{FLOAT(4)} is still 4.
流程控制 #
if-else #
// if-else
{x > 0:
~ y = x - 1
- else:
~ y = x + 1
}
// if-else的另一种写法
{
- x > 0:
~ y = x - 1
- else:
~ y = x + 1
}
// 去掉else,仅做if判断
{x > 0:
~ y = x - 1
}
// 多个if判断
{
- x == 0:
~ y = 0
- x > 0:
~ y = x - 1
- else:
~ y = x + 1
}
这同样可以应用于文本输出。
VAR know_about_wager = false
我盯着福克先生看。
{
- know_about_wager:
<>“但是您一定不是认真的吧?”我质问道。
- else:
<>“但是这次旅行一定有原因吧?”我观察到。
}
他没有回答。
switch #
判断变量 x 中的内容,还可以使用如下形式:
{ x:
- 0: zero
- 1: one
- 2: two
- else: lots
}
其他使用举例:
// 例1
VAR item = 30
你有<>
{ item:
- 1: 一个
- 2: 两个
- else: 很多
}
<>道具
// 例2
VAR item = 1
{ item:
- 1: -> part1
- 2: -> part2
- else: part3
}
=== part1 ===
你持有1号道具
-> END
=== part2 ===
你持有2号道具
-> END
=== part3 ===
你持有过多道具
-> END
标记(tag) #
不与标签(label)混淆。更多相关内容,参见官方文档。
插入图片 #
# IMAGE: imageName.jpg
# IMAGE: myImages/imageName.jpg // 使用相对路径
// 可以在行尾添加图片标记。该图像将始终显示在游戏文本上方。
上方有图片。 # IMAGE: imageName.jpg
清空与重启 #
# CLEAR
将清空当前显示的所有文本,从页面顶部重新开始。建议仅在选项后直接插入。如果插入到文本中间,则部分内容将在看到之前就被清除。
# RESTART
将立即重置故事的进度(包括玩家选择的所有经历和变量),并从头开始。由于它会立刻生效,建议放在选项后。示例如下:
你死了
* 从头来过
# RESTART
-> END
添加作者 #
将# author: name
放在 ink 文件的顶部。这将位于主标题的正下方。
深色主题 #
将# theme: dark
放在 ink 文件的最顶部。CSS 文件中的.dark
选择器是深色主题的重写。
自定义 CSS #
// ink 文件
地上有一滩血。 # CLASS: danger
向style.css
文件中添加以下内容,以改变danger
类的外观。
.danger {
color: red;
}
其他 #
多个文件 #
使用INCLUDE
引入另一文件的内容。
INCLUDE anotherFile
可变文本(variable text) #
详见官方文档。
列表(list) #
详见官方文档。
参考来源 #
- 【现代魔法系列】交互式小说编辑器 Inky 教程(一)以及同 UP 主后续的教程视频,为本篇的主参考源,对其举例进行了大量复制和修改。
- Learn Ink (video game dialogue language) in 15 minutes | Ink tutorial 以及汉化。
- 用 Inky 编写基于 Web 的互动小说,官方新手指南的翻译,有关于定制外观和开发相关内容。
- 官方新手指南:Writing web-based interactive fiction with ink
- 官方详细文档:Writing with ink
- Inky 中文文档及教程:网站已经不再运行。鉴于直接阅读源代码十分不便,我没有细读,仅列在此处供查阅。