3. Ink/Inky の使い方

はじめに

Inkというスクリプト言語では、複雑な分岐を持つインタラクティブな物語を制作できます。

1. Inkの基本用語

用語 読み 説明
Knot ノット 物語の「章」や「シーン」にあたる大きな単位。=== knot_name === のように定義します。
Stitch スティッチ Knot内の「場面」や「段落」のような小さな単位。整理のために使います。= stitch_name のように定義します。
Divert ダイバート 物語の流れを別のKnotやStitchにジャンプさせる命令。-> で示します。いわゆる「goto文」です。
Choice チョイス プレイヤーに提示する選択肢。* で示します。
Variable バリアブル プレイヤーの名前、スコア、フラグなどを保存する「変数」です。VAR で定義します。
Content コンテンツ プレイヤーに表示されるテキスト部分です。

2. 早見表(チートシート)

記法 機能
// コメント コメント(ゲームに表示されないメモ) // この行はゲームに表示されない
=== KnotName === Knot(ノット)の定義 === chapter1 ===
= StitchName Stitch(スティッチ)の定義 = forest_enter
-> Target Divert(ダイバート) -> chapter2 or -> forest.exit
* Text 選択肢 * ドアを開ける
+ Text 表示され続ける選択肢(Sticky Choice) + 地図を見る
VAR x = 10 変数の宣言 VAR score = 0
~ x = 11 変数の変更 ~ score = score + 10
{x} 変数の値を表示 現在のスコアは {score} 点です。
`{ A B C }`
{ condition: ... } 条件分岐 * {has_key} 鍵を使う
EXTERNAL func() 外部関数の宣言 EXTERNAL PlaySound(soundName)

3. よく使う基本機能

ここからは、具体的な機能と書き方を例文を交えて解説します。

3.1. KnotとDivert

物語は最初のKnotから始まり、-> を使って別のKnotへジャンプすることで進行します。

// === 森の入口 ===
// 物語はここから始まる
=== forest_entrance ===
森の入口に着いた。
目の前には二つの道がある。
-> path_choice // path_choiceというKnotに移動する

=== path_choice ===
どちらの道へ進みますか?
// この先は選択肢で分岐(後述)
* 左の道へ進む -> left_path
* 右の道へ進む -> right_path

=== left_path ===
左の道を進むと、美しい泉を見つけた。
-> END // 物語を終了する

=== right_path ===
右の道を進むと、恐ろしいオオカミに遭遇した!
-> END

3.2. 選択肢 (Choice)

プレイヤーの行動を決めさせるには * を使います。

  • 基本的な選択肢 *: 一度選ぶと消えます。
  • 表示され続ける選択肢 + (Sticky Choice): 選んだ後も、再びその場面に戻ってきたときに表示され続けます。インベントリや地図の確認などによく使います。
=== crossroad ===
十字路だ。どうしよう?
* まっすぐ進む
    まっすぐ進むと、城が見えてきた。
    -> castle_gate
+ 地図を見る
    地図を開くと、この先が城だとわかった。
    // 同じ場所に戻ることで、他の選択肢を選び直せる
    -> crossroad
* 引き返す
    来た道を引き返した。
    -> END

3.3. 変数

変数を使うと、プレイヤーの行動や状態を記憶し、物語をダイナミックに変化させられます。

  • VAR で変数を宣言します。(物語の最初にまとめて書くのがおすすめです)
  • ~ で変数の値を変更します。
  • {} で囲むと、テキスト内に変数の値を表示できます。
VAR score = 0
VAR player_name = "勇者"

=== cave_entrance ===
洞窟の入口で宝箱を見つけた。
* 開ける
    ~ score = score + 100
    中から100ゴールド見つけた!
    現在のスコア: {score}
* 無視する
    今は先を急ごう。

-> cave_exit

=== cave_exit ===
{player_name}は洞窟を抜けた。
最終スコアは {score} 点だった。
-> END

一時変数 (temp)

tempで一時変数を宣言できます。

=== classroom ===
~ temp status = getStatus("quest_id")
{ status:
- 0:  ステータスは0です
- 1:  ステータスは1です
- else: ステータスはその他です
}

3.4. 分岐

変数と条件分岐を組み合わせることで、物語は格段に面白くなります。

if

{ x > 0:
  ~
}

if/else

{ x > 0:
  ~
- else:
  ~
}

if/elseはこちらでもよい

{
  - x > 0:
    ~
  - else:
    ~
}

switch

{ x:   ~
- 0:   ~
- 1:   ~
- else:  ~
}

ネスト

条件分岐の中に、さらに条件分岐を入れることもできます。

VAR has_key = true
VAR time = "night"

=== gate ===
城の門の前に来た。
{ has_key:
    // まず鍵を持っているかチェック
    鍵を使って門を開けよう。
    { time == "night":
        // 次に夜かどうかチェック
        夜だったため、衛兵は居眠りしていた。
        あなたは静かに中へ入った。
        -> inside
      - else:
        衛兵に見つかってしまった!
        -> jail
    }
  - else:
    門には鍵がかかっている。
    -> END
}

3.5. ゲームエンジンと連携 (外部関数)

Inkはテキストを管理する言語ですが、音を鳴らしたり、画面を揺らしたりといった演出はできません。そこで使うのが外部関数 (External Function) です。

EXTERNAL で「こういう名前の関数をゲームエンジン側で呼び出しますよ」とInkに宣言しておけば、物語の途中でその関数を呼び出せます。

主な用途

  • BGMや効果音の再生
  • キャラクターの立ち絵や表情の変更
  • 画面のシェイクやフラッシュといった演出

例:効果音を鳴らす

Ink側の記述

// ゲームエンジン側で実装する関数を宣言
EXTERNAL PlaySound(soundName)

=== treasure_chest ===
宝箱を開けると、まばゆい光が溢れ出した!
// "Fanfare"という名前の効果音を再生するよう指示
~ PlaySound("Fanfare")
中から伝説の剣を手に入れた!
-> END

ゲームエンジン側 この後、Unityなどのゲームエンジン側でPlaySoundという名前の関数を実装し、「“Fanfare"という文字列を受け取ったら、ファンファーレの音源を再生する」という処理を書くことで、連携が完成します。

3.6. 繰り返しとランダム表示

同じ場面でも、来るたびに違うテキストを表示させると、物語が生き生きします。

記法 機能
{once: ...} 一度しか表示されないテキスト
{ A | B | C } 呼び出すたびにA, B, Cの順で表示
~ { A | B | C } A, B, C, A, B, C … と循環して表示
{shuffle: ...} 毎回ランダムな順で表示
=== town_gate ===
町の門番が立っている。
{once:
    - 「ようこそ、旅人さん。」 // 最初に訪れた時だけ表示
  - else:
    // 2回目以降はこちらが表示される
    - 門番はこちらを一瞥しただけだ。
}

彼は言った。「{今日はいい天気だな。|何か用かい?|異常なし。}」
// 訪れるたびにセリフが順番に変わる

-> town_square

4. 応用機能

機能 記法 説明
関数 === my_func(arg) === 引数を受け取り、値を返すことができる再利用可能なコードブロック。計算などで使います。
トンネル -> my_tunnel -> 関数のように呼び出せますが、処理後に自動で呼び出し元に戻ります。物語の「寄り道」に便利です。
スレッド ->> my_thread メインの物語とは別に、裏側で並行して処理を動かします。時間経過の管理などに使います。
リスト LIST fruits = apple, orange 複数の値をまとめて管理します。インベントリ管理などに強力です。
定数 CONST MAX_HP = 100 変更できない値を定義します。

まとめ

Inkについて解説しました。