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. なぜ “真逆” と感じるのか
-
Ink: 呼び出し元がハンドルする Pull 型
-
Ink ランタイムには「自分で動く」機構がない。
while (story.canContinue) { story.Continue(); // こちらが引っぱる(Pull) } -
つまり「停止=呼び出さない」「再開=呼び出す」だけの同期世界。
-
-
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 つのルール
- Ink は pull 型同期──
Continue()を“呼ぶか呼ばないか”が全て。 - Phaser は push 型非同期──終了をイベントで教えてくれる。
- 橋渡し用のフラグ/カウンタを JS 側で持つ──
待っている間は
Continue()を呼ばず、完了時だけ呼ぶ。
まとめ
以上を押さえておけば「Ink の物語進行」と「Phaser のゲーム進行」を スムーズに噛み合わせることができます。