4. Ink(inkjs)とPhaser併用時の注意点

はじめに

Phaserでinkjsを使う場合の注意点をメモ

1. それぞれのランタイムモデル

Ink (inkjs) Phaser (Scene/Game Loop)
実行単位 story.Continue() を呼ぶたびに 1 行(か選択肢が出るまで)だけ進む 毎フレーム update() が自動で呼ばれ続ける
スレッド JavaScript 呼び出しスタック上で 同期的 に完了 ブラウザの Event Loop に載った 非同期 フレーム駆動
停止方法 単に 次の Continue() を呼ばなければ止まる this.scene.pause() などだが、ゲームループ自体は回り続ける
外部 I/O の扱い “とにかく即値を返す” 前提。Promise を返しても Ink は待たない ほぼすべてがコールバック/Promise/イベントで完結

2. なぜ “真逆” と感じるのか

  1. Ink: 呼び出し元がハンドルする Pull 型

    • Ink ランタイムには「自分で動く」機構がない。

      while (story.canContinue) {
        story.Continue();  // こちらが引っぱる(Pull)
      }
    • つまり「停止=呼び出さない」「再開=呼び出す」だけの同期世界

  2. Phaser: ランタイムが押し流す Push 型

    • update() は 60 fps なら毎秒 60 回 勝手に呼ばれる(Push)。
    • 非同期イベント(入力・アセットロード・タイマー)も どこかで完了したらコールバックが 勝手に飛んでくる

同期的に “こちらが進める” Ink と、 非同期的に “向こうから進んでくる” Phaser―― 制御の向きが正反対 だから橋渡しが必要。


3. 橋渡しは「Ink を止める」のではなく「呼ばない」だけ

sequenceDiagram
  participant GameLoop as Phaser
  participant Ink
  GameLoop->>Ink: Continue()   ++呼ぶまで止まっている++
  loop while canContinue
    Ink-->>GameLoop: 行テキストを返す (同期)
    Note over GameLoop: 1フレーム分の処理
  end

  Note over Ink,GameLoop: 非同期イベント(Scene 完了など)が来る
  GameLoop->>Ink: Continue()(再開)
  • Ink 側から見ると Promise は“すでに解決済み”として扱われるので、 非同期が必要なら JavaScript 側で 完了コールバック内から 次の Continue() を呼び出してやるしかない。
  • 逆に Phaser から見ると Ink は「何行でも即返す関数」 にしか見えない。

4. 典型的な落とし穴

誤解 起こる問題 正しい対処
return await asyncFunc() すれば Ink が待ってくれる Ink は待たずに即 Promise {} が返り、物語が暴走 外部関数で await しない。終了イベントで Continue() を呼ぶ
lookaheadSafe=true で外部関数を登録したまま副作用を行う “先読み” でも呼ばれ、2回実行 される 副作用がある関数は lookaheadSafe=false
Ink の canContinue が true だから Continue() してよさそう 外部 Scene がまだ終わっておらず処理が競合 「いま待っている外部呼び出しがあるか」でガードする

5. 覚えておくべき 3 つのルール

  1. Ink は pull 型同期──Continue() を“呼ぶか呼ばないか”が全て。
  2. Phaser は push 型非同期──終了をイベントで教えてくれる。
  3. 橋渡し用のフラグ/カウンタを JS 側で持つ── 待っている間は Continue() を呼ばず、完了時だけ呼ぶ。

まとめ

以上を押さえておけば「Ink の物語進行」と「Phaser のゲーム進行」を スムーズに噛み合わせることができます。