高阶 Inky 残卷

作者:逊狼
2019-12-23
14 13 3

原文 https://www.inklestudios.com/ink/web-tutorial   /  文稿 https://www.yuque.com/inky/pcke0w/gix3ga

特别感谢:Saryta 与 jaxyong

INK 最初被设计为游戏引擎的脚本插件,因此具有丰富而强大的功能。我将官方文档中,部分我有兴趣,且对你使用 Inky 可能有帮助的内容翻译至此。

如你发现“好像少了些什么”,或想进一步了解,请阅读官方文档

1. 基础

1.1 Inky 基础教程

1.2 针脚 stitches

结点可有子节点

随着故事变长,在没有其他结构的情况下,它们会越来越乱,难以井井有条。

结点可以包括称为“针脚”(stitches)的子结点,这些使用单个等号(=)标记。

=== the_orient_express === //东方快车 结点

= in_first_class //头等厢 针脚

  ...

= in_third_class //三等厢 针脚

  ...

= in_the_guards_van //警务车厢 针脚

  ...

= missed_the_train //错过火车 针脚

  ...

例如,可以使用一个结点来写拍摄场景,用结点里的多个针脚来写场景中的事件。


针脚有唯一的名称

针脚可被用于跳转的地址。

* [前往三等厢]

  -> the_orient_express.in_third_class //跳转到 东方快车结点 下的 三等舱针脚

* [前往警务车厢]

  -> the_orient_express.in_the_guards_van //跳转到 东方快车结点 下的 警务车厢针脚


第一个是默认针脚

如果结点包含针脚,默认跳转到第一个针脚。

* [前往头等厢]

  “头等厢, 先生。 还有其他地方吗?”

  -> the_orient_express

等同于(...除非我们改变结点内针脚的顺序!):

* [前往头等厢]

  “头等厢, 先生。 还有其他地方吗?”

  -> the_orient_express.in_first_class 

你还可以在针脚外的顶部加入任何内容,但注意此时不会自动运行第一个针脚。

=== the_orient_express === 

我们上车了,但是去哪?

* [头等厢] -> in_first_class

* [二等厢] -> in_second_class

= in_first_class //不会自动运行第一个针脚

  ...

= in_second_class

  ...


本地跳转

在结点内部,你无需完整的针脚地址。

-> the_orient_express

=== the_orient_express ===

= in_first_class 

  我安顿好我的主人。

  * [前往三等厢]

    -> in_third_class

= in_third_class

  我坐下来了。

这意味着针脚和结点的名称不能相同,但是不同的结点里可以有相同名称的针脚。(你不能把某个车厢也叫东方快车,但东方快车和蒙古专列里都可以有头等舱。)如果使用了不明确的名称,编译器发出警告。

脚本文件可以合并

1.3 选项 choices

后备选项 fallback choices

如果某个地方都是一次性选项,那么多次经历后,可能会出现“内容用光(ran out of content)”的错误。

我们可通过“后备选择”解决问题。后备选项不会直接显示给玩家,但如果没有其他选项可选是,则游戏会自动选择。

后备选项只是“没有选项时的选择”:*-> out_of_options

并且,在轻微滥用语法下,我们可以使其中的内容做出默认选择:

*   -> 

  穆德无法解释他是怎么从那辆燃烧着的货车里出来的。 -> season_2

举个例子:

=== find_help ===

  你在人群中拼命地寻找一张友好的脸。 
  * 戴帽子的女人[?] 粗暴地把你推到一边。 -> find_help
  * 拿公文包的男人[?] 很厌恶的从旁边匆匆而过。 -> find_help 

  * ->  //后备选项
    但为时已晚:你倒在站台上。一切都结束了。
    -> END

1.4 可变文本 variable text

序列

序列(默认)每次都会显示下一个元素。当“没有下一个”时,它将继续显示最后一个元素。

电台嘶嘶作响。 {“三!”|“二!”|“一!”|传来了爆炸的响声。|这只有些噪声}

{我用五磅钞票给我买了杯咖啡。|我买了第二杯咖啡给朋友。|我没钱买咖啡了。}


周期,一次性,随机

循环(标记 &)就像序列,但它循环内容。

今天是 {&周一|周二|周三|周四|周五|周六|周日}。

一次性(标记 !)也像序列,但当“没有下一个时”,它将不显示。

他给我讲了个笑话。

{!我有礼貌的笑了。|我微笑了。|我做了下鬼脸。|我不想再做出反应了。}

随机(标记 ~)随机显示内容。

我掷了枚硬币。 {~字面|花面}。


更多技巧

可以包含空元素。

我向前迈了一步 {!||||然后灯火熄灭了。 -> eek}

可以进行嵌套。

这个怪兽{&{没浪费时间|}打|爪}{&你|进你{&的腿|的胳膊|的胸}}。

可插入跳转。

我{在等。|等了一会|打盹了。|醒来又等了会。|放弃并离开了 -> leave_post_office}

也可在选项中使用。(注意,{不能出现在选项开头,会与条件选项混淆;/ 出现在里面可能出现错误。)

+   “您好, {&主人|Fogg 先生|你|棕眼}!”[] 我喊道。

可变文字可以在循环内使用,让游戏看起来很智能,而无需特别的工作。

这里是一个单结点版的"打地鼠",体验故事运行。我们使用一次性和后备选项,阻止“地鼠逃走”,游戏始终会结束。

->whack_a_mole

=== whack_a_mole ===

  {我举起了锤子。|{~没打中!|没有!|不好,它在哪?| 偏了!| 见鬼…| 啊,手滑了!| 嗷呜!|啊哈,抓住了!-> over }}
  这个{&地鼠|{&不友好的|可恶的|低级的}{&生物|啮齿动物}}{在某处|藏在某处|仍然逍遥法外|嘲笑我|依旧没打中|注定会失败}。 <>
  {!我会抓住它!|但这次它逃不掉了。}

  *  [{~打|砸|尝试}左上角]  -> whack_a_mole
  *  [{~就是|暴击|猛击}右上角] -> whack_a_mole
  *  [{~砍|锤}中间] -> whack_a_mole
  *  [{~痛击|肯定是}左下角]   -> whack_a_mole
  *  [{~掀开|重击}右下角]  -> whack_a_mole

  * -> 

  你累坏了!地鼠击败了你!->over

= over

# CLASS: end
……全剧终

+ 从头再来?
    # RESTART

-> END

这里有一些生活建议。请注意选项,电视的吸引力永远不会消失:

=== turn_on_television ===  

我打开电视{一次|两次|三次|更多次},但电视上 {没啥有意思的东西,所以关上了电视|依旧没什么有意思的东西|比以前更不能吸引我的兴趣了|只有垃圾|演关于鲨鱼的节目我并不喜欢鲨鱼|什么都没有}。

+ [再试试] -> turn_on_television
* [去外面] -> go_outside_instead

=== go_outside_instead ===

-> END

1.5 查询 queries

INK 提供了一些有用的关于游戏状态“game level”查询,用于条件逻辑。它们不是语言的一部分,但它们总是有用的,并且不能由作者编辑。从某种意义上说,它们是语言的“标准库函数”。惯例是用大写字母来命名。


CHOICE_COUNT()

CHOICE_COUNT 返回到目前为止,当前区域内的选项数,例如:

* {false} Option A
* {true} Option B

* {CHOICE_COUNT() == 1} Option C

false 表示不生成此选项,因此计数是1,显示的 B 和 C。


TURNS()

TURNS 返回自游戏开始以来的回合数。

您总共经历了{TURNS()}个结点。

TURNS_SINCE(-> knot)

TURNS_SINCE 返回自上次访问特定结点或针脚的跳转次数(玩家输入的)。


值为 0 表示"在经历这个区域"。

==laugh

你笑个不停……

+   哈哈哈…… -> laugh

*   {TURNS_SINCE(-> laugh) == 0} 你尝试不笑了->END

值为 -1 表示"从未经历过",其他任何正值都意味着“经历过多次了”。

* {TURNS_SINCE(-> sleeping.intro) > 10} 你觉得很累…… -> sleeping 

注意,这里实际只是向 TURNS_SINCE 传递一个参数“转移的目标”,而非是跳转至结点地址,此处不进行节点跳转。


SEED_RANDOM()

SEED_RANDOM 出于测试目的,固定随机数生成器通常很有用,这会让每次播运行 INK 都固定产生相同的结果。你可以通过“种子”随机系统来实现。

~ SEED_RANDOM(235)

2. 编织 weave

到目前为止,我们一直以最基础的方式构建故事的分支,从“选项”到“页面”。

但这要求我们命名故事中的每个目的地且保持唯一性,可能会减慢编写速度并阻挠较小的分支。

INK 拥有更为强大的语法,旨在简化始终向前的故事流(写起来像大多数的故事,而非计算机程序)。


这种系统称为“编织”(weave),它由基础语法和选项语法,以及两个新功能构成:“收束标记 - ”,“选项与收束的嵌套”。

2.1 收束 gathers

收束(gathers) - 将小分支收回到一起。让我们看这个例子:

“怎么了?”我的主人问。

  * “我有些累。”[]我重复道。
    “确实,” 他答道。 “这太糟了。”

  * “没事,先生!”[] 我回应道。

  *  我说,这次旅行真可怕。”[] 我不想再继续了。”
    “啊,” 他有些动容。 "你看起来很沮丧。明天,情况会好些的。"

在真正的游戏中,这三个选项都可能导致相同的结果,Fogg 先生离开了房间。我们可用收束来执行此操作,而无需创建任何新结点或新跳转。

“怎么了?”我的主人问。

  * “我有些累。”[]我重复道。
    “确实,” 他答道。 “这太糟了。”

  * “没事,先生!”[] 我回应道。

  *  我说,这次旅行真可怕。”[] 我不想再继续了。”
    “啊,” 他有些动容。 "你看起来很沮丧。明天,情况会好些的。"

    - Fogg 先生离开了房间。

我们可将这些“收束和分支”串在一起,让故事始终向前运行。

=== escape === 

我跑出森林,狗跟在我后面。

  * 我检查了下珠宝[],都还在我的钱包里,这感觉就像步入了春天。 <>
  * 我没停下来喘口气[],继续向前跑。<>
  * 我欢呼了出来。 <>

-   路没多远了!Mackie 会发动引擎,然后我就安全了。//收束

  * 我来到路边四处张望[]。 你能相信吗?
  * 我不该总是说,Mackie 一直很可靠[]。他从未让我失望过。更确切地说,从来没有,直到那晚前。

- 路旁边是空的。 Mackie 不见了。//再次收束

这是最基本的编织。本节后面还详细介绍了编织的嵌套、包含选项和跳转、内部跳转、以及根据前面的经历来影响后续的内容。

2.2 嵌套

上面展示的编织是非常简单的“扁平”结构。不管玩家选什么,他们都要经历相同的回合数才能从上到下。但是,有时某些选项需要深或复。因此,编织可以嵌套。

注意,编织的嵌套非常强大且紧凑,但你可能需要一些时间来适应!


选项收束均可

选项嵌套看下方的场景:

- “好吧,Poirot?谋杀还是自杀?”

* “谋杀!”
* “自杀!”

- Christie 女士片刻后放下手抄。台下的评委们都目瞪口呆。

提出的问题首选是“谋杀!” 或“自杀!”。如果 Poirot 宣布自杀,就没有其他事情可做了。但是在谋杀案中,还需要一个后续问题,“他怀疑是谁?”

我们可以通过一组嵌套的子选项来添加新选项。通过使用两个星号(**)而非是一个星号,告诉 INK 这些新选项是另一个选择的一部分。

-  “好吧,Poirot?谋杀还是自杀?”

  * “谋杀!”
    “那么,你怀疑谁?”

    * *  “日本探长!”
    * *  “船长 Hastings!”
    * *  “我自己!”

  * “自杀!”

- Christie 女士片刻后放下手抄。台下的评委们都目瞪口呆。

(用缩进显示嵌套关系是个不错的习惯,即使编译器不在乎这些。)

如果我们想向另一条路线添加新的子选项,可以按类似的方式做。

-  “好吧,Poirot?谋杀还是自杀?”

  * “谋杀!”
  “那么,你怀疑谁?”

    * *  “日本探长!”
    * *  “船长 Hastings!”
    * *  “我自己!” 

  * “自杀!”
  “真的吗,Poirot?你确定吗?

    * *  “确定。”
    * *  “这很明显。”

- Christie 女士片刻后放下手抄。台下的评委们都目瞪口呆。

现在,无论前面选择什么,最后都能收到 Christie 女士的出场。


有时不只是更多的选项,可能还有更多的剧情。我们可以用收束嵌套解决这个问题。

-  “好吧,Poirot?谋杀还是自杀?”

  * “谋杀!”
  “那么,你怀疑谁?”

    * *  “日本探长!”
    * *  “船长 Hastings!”
    * *  “我自己!” 

    - -  “你一定是在开玩笑!”

    * *  “朋友,我是认真的!”  
    * *  “要是……” 

  * “自杀!”
  “真的吗,Poirot?你确定吗?

    * *  “确定。”
    * *  “这很明显。”

- Christie 女士片刻后放下手抄。台下的评委们都目瞪口呆。


收束是直观的,但它们的行为很难说出来:通常在选择之后,故事会找到下一个非更低级的收束,并移动下来。基本思想是:选项将故事的支线分开,并通过收束将它们重新合在一起。(因此,这被称为“编织”!)


嵌套可以很多层

上面我们使用了两层嵌套。事实上,你深入嵌套多少层都没有限制。

- “讲个故事,船长!”

  * “好吧,你们这些海狗。 有个故事...”
    * *   “一个漆黑狂风暴雨的夜晚...”
        * * *   “...船员们都很不安...”
            * * * *  “... 他们对船长说..."”
                * * * * *   “...给我们讲个故事,”

  * “不,已经过了你们睡觉的时间。”

- 作为一个人,船员们开始打起哈欠。

这样下去,整个嵌套都会变得难以阅读或操作。因此,如果子选项变得笨拙,跳转到新针脚上是一种好习惯。

但至少在理论上,你可以把你整个故事都写成一个编织。


再看一个更长的例子:

- 我看着 Fogg 先生

* ... 我再也控制不住自己了。
  “先生,我们这次旅行的目的是什么?”
  “打赌,”他答道。

  * *   “打赌!”[]我回应道。
      他点了点头。 

      * * *   “但这确实很蠢!”
      * * *   “这太糟了吧?”

      - - -   他又点了点头。

      * * *   “可是我们能赢吗?”
              “这正是我们要努力实现的。” 他回答道。
      * * *   “我猜,赌注很明智?”
              “两万英镑,”他回答得直截了当。
      * * *   我当时没在问他别的[],他礼貌得咳嗽了一声,也没在对我说什么。 <>

  * *   “啊[。”],” 我回应道,不确定我在想什么。

  - -   之后, <>

* ... 但我什么也没说[]。<> 

- 我们在沉默中度过了这一天。

- -> END

这里验证了之前的哲学:编织提供了一种紧凑的方式,在有大量分支大量选择的同时,故事依然能顺利的从头走到尾!

2.3 编织定位 

有时候只是编织结构就足够了,但有时我们还需要更多的控制权。

在默认情况下,编织中的内容没有地址或标签,这意味着它们不能被跳转到,也无法对其进行测试。在最基本的编织结构中,选项会改变玩家经历;可一旦走过编织,这些经历就会被遗忘。


但如果我们想记录玩家的经历,我们可贴上“标签语法”(label_name)。


贴标签 (label_name)

任何层嵌套的收束都可以使用括号贴标签。-  (top)

贴上标签后,可以跳转至此或进行测试,就像结点和针脚一样。这意味着在编织中你可使用之前的语法来管理内容,同时保持清晰稳定的优点。

选项也可以用括号贴标签,就像收束一样。标签括号应位于条件之前。

这些地址也可用于条件测试中,这对测试“由其他选项解锁的选项”很有用。

=== meet_guard ===

卫兵在盯着你。

*   (greet) [打招呼]
  “早。”
* (get_out) “让开[。”],” 你对卫兵说。

-   “嗯,”卫兵回应道。

* {greet}   “天气不错哈?” // 当你选打招呼
*   “咦?”[] 你回应道?
* {get_out} [把他推到一旁]   // 当你威胁他时
  你把他推到一旁。作为礼尚往来,卫兵直接拔出了剑。
  -> fight_guard      // 跳出编织

- “嗯。” 卫兵给你一个小纸袋。“太妃糖?”


范围

在同一块编织内,你可只简单的写标签名称;到外面则需要写路径,像同个结点下的不同针脚:

=== knot ===

= stitch_one 

  - (gatherpoint) Some content.

= stitch_two 

  * {stitch_one.gatherpoint} Option

或到另一个结点:

=== knot_one ===

- (gather_one)

  * {knot_two.stitch_two.gather_two} Option

=== knot_two ===

= stitch_two 

  - (gather_two) 

    * {knot_one.gather_one} Option


更多技巧

事实上,即使没有看到收束,INK 内的所有内容都是在编织中的。这意味着游戏中的所有选项都可贴标签,然后使用它的地址。特别的,这意味着你可以测试玩家为达到特定结果,选了哪个选项。

=== fight_guard ===

...

= throw_something 

* (rock) [丢石头] -> throw
* (sand) [扔沙子] -> throw

= throw

你向卫兵扔了{throw_something.rock:一块石头|一把沙子}。

我们能用贴标签创建内部循环,这是询问 NPC 的标准模式,体验故事运行

- (opts)

  * “我从哪能弄到制服吗?”[] 你询问快乐的卫兵。
    “当然,在储物柜那。”他笑着说。 “不过可能不太合身。”
  * “告诉我安防系统。”
    “他很老,” 卫兵向你保证。 “就像古董一样老。”
  * “这里的狗?”
    “很多很多。” 卫兵答道,并咧嘴一笑。 “也都是饿鬼。”

  //要求玩家至少问一个问题

  * {loop} [问够了] 
    -> done

- (loop) // 在警卫厌烦前循环几圈

  { -> opts | -> opts | } //序列可变文本控制卫兵是否厌烦

  他挠挠头,
  “好吧,不能整天只站着说话。”他说到。

- (done)

  你谢过警卫,便离开了。

选项也可以被跳转到,就好像选择了该选项一样。因此,显示的内容会忽略 [] 中的文本,并如果该选项是一次性选项,它将被用掉。

- (opts)

* [做鬼脸]
  你做鬼脸,士兵就冲你来了! -> shove
* (shove) [把警卫推到一旁] 你把卫兵推到一旁,但他回来了。
* {shove} [进入战斗] -> fight_the_guard

-   -> opts

选项中可直接收束。注意下方第一个选项下二级收束并没其收束作用,而是方便第二个选项的跳转。

* “还好吧,先生”[]我问。

  - - (quitewell) “挺好的,”他答道。 

* “你填字游戏怎么样,先生?”[]我问。
  -> quitewell 
* 我什么也没说[],我的主人什么也没说。

- 我们又感到了一次倍感亲切的沉默。
后面的内容较为复杂,本文只选取了部分简单的内容进行翻译。如有兴趣请浏览官方文档

3. 变量与逻辑

到目前为止,我们已经根据玩家看到的东西,做出了条件文本与条件选项。


INK 还支持临时变量与全局变量,存储数字和内容数据,甚至故事流命令;逻辑功能也很齐全,并包含一些额外的结构,以帮助组织更复杂的故事结构。

3.1 全局变量 global variables

最强大的变量,储存游戏某些独特状态属性的变量,对每个故事来说可能都最有用的——从主人公口袋里的钱,到主人公精神状态的 SAN 值。


这种变量称为全局变量(global variables),因为可以从故事中的任何位置访问(包括设置和读取)它。(传统情况下,编程会试图避免这种情况,因为它可能会相互冲突。但故事有所不同,有一些事物贯穿始终,全局需要并一直存在。 )


全局变量可以通过语句 VAR 定义在任何地方。给它们指定一个初始值,定义它们是哪种类型的变量:整数、浮点(十进制)、内容或故事的地址。

VAR knowledge_of_the_cure = false
VAR players_name = "Emilia"
VAR number_of_infected_people = 521
VAR current_epilogue = -> they_all_die_of_the_plague

我们可以控制全局变量以控制选项,并控制条件文本。方式与之前看到的类似。

=== the_train ===

  火车颠簸了一下。 { mood > 0:我觉得还好,没介意奇怪的颠簸|这超出了我所能承受的}。

  * { not knows_about_wager } “但是,先生,我们为什么要旅行?”[] 我问道。
  * { knows_about_wager} 我考虑了我们有些离奇的冒险[]。这可能吗?

变量的值可用类似于序列和条件文本的语法显示出来,这在调试时也很有用:

VAR friendly_name_of_player = "Jackie"
VAR age = 23

我叫 Jean Passepartout,但朋友都叫我 {friendly_name_of_player}。 我{age}岁了。

你可能注意到,上面我们将变量称为能够包含“内容”而非“字符串”。这是有意的,因为在 INK 中定义的字符串能包含 INK ,尽管它始终会计算为字符串(很赞)。

VAR a_colour = ""

~ a_colour = "{~红色|蓝色|绿色|黄色}" 

{a_colour} 

输出“红色,蓝色,绿色或黄色”中的一种颜色。注意,一旦输出了这样的内容,其值就是“确定的”。(量子状态崩溃。)因此,下面内容不会产生有趣的效果。(如果你希望这样做,请使用可变文本!)

呆子撞到了你,你眼冒金星, {a_colour}和{a_colour}闪烁。


这也是为什么 VAR a_colour = "{~red|blue|green|yellow}" 是不推荐的,它会影响到你的整个游戏。

3.2 逻辑运算 logic

显然,我们的全局变量并非为常量,因此我们需要一种语法来改变它们。

默认情况下,INK 中的任何文本都直接显示到屏幕上。因此我们用 ~ 来标记表示某行是在进行数值工作。


以下均是为变量分配值:

=== set_some_variables ===

  ~ knows_about_wager = true  
  ~ x = (x * x) - (y * y) + c
  ~ y = 2 * x * y

以下是测试用的条件:

{ x == 1.2 }
{ x / 2 > 4 }
{ y - 1 <= x * x }

INK 支持数学四则运算(+,-,*和/),以及 % (或 mod )进行求余。还支持 POW (幂)运算:

~ temp dice_roll = RANDOM(1, 6) 
~ temp lazy_grading_for_test_paper = RANDOM(30, 75)  
~ temp number_of_heads_the_serpent_has = RANDOM(3, 8)

可用上文的 SEED_RANDOM() 进行测试。

INK 作为文字游戏引擎,奇怪的是并没有那么多字符串查询(string queries)方式。这有三种基本查询,相等,不相等和子字符串。以下全部返回 true:

{ "Yes, please." == "Yes, please." }
{ "No, thank you." != "Yes, please." }
{ "Yes, please" ? "ease" }

3.3 条件块 if/else

我们已经看到了用于控制选项和故事内容的条件内容,而 ink 还提供了与常规 if/else - if/else 结构等效的功能。


单纯使用 if —— 简单而有效的判断:

if 语法的判断条件,是从开始到现在为止产生的条件线索,而 {... } 语法表示正在测试某些内容。

{ x > 0:

  ~ y = x -1

}

或者加上其他条件 else :

{ x > 0:
  ~ y = x - 1
- else:
  ~ y = x + 1
}

扩展 if/else - if/else 条件块:

上面的语法也可更改形式为另一种结构方式,类似于编程语言中的 switch 语句格式。

{ 
  - x > 0:
    ~ y = x - 1
  - else:
    ~ y = x + 1
}

在此格式基础之上进一步拓展,我们还可以实现 else-if 形式的条件判断:

{ 
  - x == 0:
    ~ y = 0
  - x > 0:
    ~ y = x - 1
  - else:
    ~ y = x + 1
}

(请注意,与其他所有内容一样,空格仅出于可读性考虑,没有语法含义。)


Switch Block (开关座:选择性判断语句):

实际上 INK 的确有和其他编程语言中 switch 类似的语句。

{ x:
  - 0:  zero
  - 1:  one
  - 2:  two
  - else: lots
}


示例:关联上下文内容

请注意,这里的判断条件除了可以使用变量之外,还可以使用玩家是否经历过结点作为条件,并且这种条件使用频繁,例如要做一些与当前游戏状态相关的内容:

=== dream ===
  {
    - visited_snakes && not dream_about_snakes:
      ~ fear++
      -> dream_about_snakes

    - visited_poland && not dream_about_polish_beer:
      ~ fear--
      -> dream_about_polish_beer 

    - else:
      // 清晨的梦只是梦境
      -> dream_about_marmalade
  }

此语法具有易于拓展的优势,优先选择使用此语法。


条件块中不仅限于逻辑运算

条件块可以用于控制故事内容以及发展走向:

我望着 Fogg 先生。

{ know_about_wager:

  <> “但你一定不是认真的吧?” 我诧异道。

- else:

  <> “但这次旅行一定是有一个原因,”我说。

}

他什么也没有回答,只是像昆虫学家研究他最新的发现那样,一丝不苟地研究他的报纸。

你甚至可以将选项放在条件块中:

{ door_open:
  *   我大步走出车厢 [] 并且我仿佛听见我的主人在低声地自言自语。       -> go_outside 

- else:

  *   我请求离开 [] 并且福克先生显得很惊奇。   -> open_door 
  *   我站起来打开了门 [] 。福克先生似乎对这种小小的反抗并没有感到不安。 -> open_door
}

但是请注意,上面的示例中没有出现编织语法和嵌套语法不是偶然:为了避免嵌套带来的内容混淆,不允许在条件块中加入 gathers 收束点。


实现多种特殊功能的多行块 Multiline blocks

它们是另一种特殊的代码块,可以分别实现不同的功能:

// 顺序执行:根据每次的跳转依次执行,全部执行后持续执行显示最后一行
{ stopping:
  -   我进入了赌场。
  -   我又一次进入了赌场。
  -   我又一次走了进去。
}

// 随机洗牌:随机显示执行其中一行
在桌子上,我画了一张卡片。 <>
{ shuffle:
  -   红心王牌。
  -   黑桃王牌。
  -   方块2。
    “你这次输了!”赌场庄家欢叫着。
}

// 循环执行:顺序显示执行,并循环如此
{ cycle:
  -   我屏住呼吸。
  -   我不耐烦地等着。
  -   我停顿了一下。
}

// 一次执行:顺序显示执行,每个分支只显示执行一次,全部显示执行后不再显示执行
{ once:
  -   我的运气好吗?
  -   我能赢吗?
}


高级:修改的随机洗牌

上面的随机洗牌,实际上是 shuffle cycle 循环随机洗牌;它每次随机选择内容,显示内容,然后重新随机显示内容,显示内容。

随机洗牌还有两种方式:

shuffle once 随机显示执行内容,只显示执行一次,之后不会再执行。

{ shuffle once: 
  - 太阳很晒。 
  - 天气很热。
}

shuffle stopping 随机显示执行内容(最后一条除外),当其他项全部随机执行完毕后,将一直保持在最后一条。

{ shuffle stopping:
  - 一辆银色宝马车呼啸而过。
  - 一辆亮黄色的野马车在转弯。
  - 这里有很多车。
}

3.4 临时变量 Temporary Variables

临时变量用于临时计算

有时,全局变量无法满足我们的临时需求,这时候 ink 为我们提供了临时变量 Temporary Variables 。

=== near_north_pole ===

  ~ temp number_of_warm_things = 0
  { blanket:
    ~ number_of_warm_things++
  }

  { ear_muffs:
    ~ number_of_warm_things++
  }

  { gloves:
    ~ number_of_warm_things++
  }

  { number_of_warm_things > 2:
    尽管下雪了,但我还是感到无比舒适。

  - else:

    那天晚上我感觉比以往任何时候都冷。
  }

临时变量只在其定义的当前结点生效,离开结点后临时变量将被丢弃。


结点和针脚可以拥有参数

临时变量有一种非常实用的形式,就是作为传递给任意结点或针脚的一个参数。

* [Accuse Hasting]
    -> accuse("Hastings")
* [Accuse Mrs Black]
    -> accuse("Claudia")
* [Accuse myself] 
    -> accuse("myself")

=== accuse(who) ===

  "我控诉 {who}!" 波洛宣布。
  “真的吗?” Japp 回道。 “{who == "myself":你要这么做?|{who}?}”
  “为什么不可以?” 波洛回击道。

如果要将临时变量从一个针脚传递给另一个针脚,就要用参数。


示例:递归节点中的定义

临时变量可以放心的在递归中使用(与全局变量不同,全局变量改变后递归过程中都会变为改变后的值),因此可以执行下面的内容:

-> add_one_to_one_hundred(0, 1)

=== add_one_to_one_hundred(total, x) ===

  ~ total = total + x
  { x == 100:
    -> finished(total)
  - else:
    -> add_one_to_one_hundred(total, x + 1)
  } 

=== finished(total) ===

  “结果是 {total}!” 你宣布。
  高斯惊恐地盯着你。
  -> END

实际上,这种递归节点中的定义非常实用,也因此 ink 中提供了一种特殊的结点,一个 function 。它有一定的限制,并且可以返回一个值,请参阅以下部分。


高级:将 divert targets (跳转目标)作为参数传递

结点、针脚的地址也是一种类型的值,用 -> 符号表示,可以传递与存储。因此以下的内容是正常的,并且非常有用:

=== sleeping_in_hut ===

  你躺下闭上了眼睛。
  -> generic_sleep (-> waking_in_the_hut)

===  generic_sleep (-> waking)

  你偶尔会做梦之类的。
  -> waking

=== waking_in_the_hut

  你重新站起来,准备继续你的旅程。

要注意的是,在 generic_sleep 的定义中,是有 -> 符号存在的。因为这里是作为一个参数值存在,如果不加 -> 符号,就会变成下面这样:

=== sleeping_in_hut ===

  你躺下闭上了眼睛。
  -> generic_sleep (waking_in_the_hut)

3.5 函数 Functions

3.6 常数 Constants

4.1 隧道(?)tunnels

狼也不知道这里 tunnels 翻译成什么更恰当。无端的想起马里奥中可以往返隐藏区域的“管道”。但最后决定还是直译成了“隧道”。


INK 故事的默认结构是“扁平”的树状结构,分支又重新收束回一起,也许还有循环,但故事始终位于“某个位置”。


但是这种扁平结构会使某些事情变得困难:想象一个游戏,可能发生以下的相互作用:

=== crossing_the_date_line ===


* “先生!”[] 我突然感到震惊。"我才意识到,我们已经穿过了国际日期变更线!"
- Fogg 先生轻轻抬起了眉毛。“我已经调整过了。”
* 我擦了擦额头上的汗水[]。松了口气!
* 我轻轻的点头[]。他当然会做的。
* 我在心里抱怨[]。 我又被小看了一次。

...但它可能发生在故事中的几个不同地方。我不想为不同的地方写相同内容,但是当内容完成后,它还需要知道返回到哪里。我们可以使用参数来做到这一点:

=== crossing_the_date_line(-> return_to) ===

...

-   -> return_to 

...

=== outside_honolulu ===

我们到了 Honolulu 最大的岛。

- (postscript) 
    -> crossing_the_date_line(-> done)

- (done)
    -> END 

...

=== outside_pitcairn_island ===

船在水上驶向小岛。

- (postscript) 
    -> crossing_the_date_line(-> done)

- (done)
    -> END 

这两个位置都调用并执行相同的故事片段,一旦完成,它们就会回到下一步要去的地方。


但如果这个“故事的部分”更复杂怎么办?如果跨越多个结点怎么办?使用上面的方法,我们必须不断地将“ return-to”参数从一个结点传递到另一个结点,以确保我们始终知道该返回的位置。

因此,INK 把跳转和新功能结合到起来加语言中,其功能类似于子程序,称为"隧道"(tunnels)。


隧道的语法看起来很像跳转,末尾还有另一个跳转:

-> crossing_the_date_line ->

这意味着运行一个“crossing_the_date_line 的故事”,然后从这继续。

在隧道内部,语法展示进行了简化:用 ->-> 语句结束隧道,实质上意味着“继续”。


=== crossing_the_date_line === 

// 这里是隧道!

...

-   ->->

注意,隧道结点和普通的结点不同,编译器将不会检查隧道是否真的以“->->”语句结尾,除非在运行时。因此,您需要仔检查进出隧道的跳转。


隧道可串在一起,也可在普通跳转上:

... 

// 这里运行了隧道,然后跳转到 done

-> crossing_the_date_line -> done

...

... 

//这里运行了第一个隧道,第二个隧道,然后跳转到 done

-> crossing_the_date_line -> check_foggs_health -> done

...

隧道可以嵌套,因此以下内容也行:

=== plains ===

= night_time 
  脚边是深色柔软的草丛。
  + [休息]
    -> sleep_here -> wake_here -> day_time

= day_time 
  到了该动身的时间了。

=== wake_here ===

  你随太阳一同醒来。
  + [吃些东西]
    -> eat_something ->

  + [该动身了]
  - ->->

=== sleep_here ===

  你躺下并闭上双眼。
  -> monster_attacks -> 

  接着该睡觉了。
  -> dream ->

  ->->

……

近期点赞的会员

 分享这篇文章

逊狼 

乐观是制胜的调料 

您可能还会对这些文章感兴趣

参与此文章的讨论

  1. Yosmos 2019-12-24

    开始复杂了……我在实际写短篇的时候倒是不用很多这类操作,建议可以稍微举个例子解释一下在什么状况下使用哪种操作会有奇效?

    • 逊狼 2020-02-04

      @Yosmos:有些东西只是为了效率的,简单的语法也可以,如果内容很多就需要高级语法来更高效地写作

  2. 无有时代 2019-12-27

    玩了一下游戏,还真挺有趣的,有空我也弄个~

您需要登录或者注册后才能发表评论

登录/注册