← ドキュメント一覧

プラットフォーム抽象化 — 同じ作品が Switch まで動く設計

作成日: 2026-06-10 / 更新日: 2026-06-10

kaedevn は「一度書けば、どこでも動く」を最初の設計目標に据えています。同じ作品(スクリプト+アセット)が、Web ブラウザ・ネイティブ(macOS / Windows / iOS / Android)・そして将来の Nintendo Switch で、作り直しなく動きます。これを支えるのが、プラットフォーム差を抽象境界の裏に閉じ込める設計です(Godot と同型の考え方)。

抽象の4本柱

ゲーム実行に必要な要素を、少数のインターフェースに分離しています。ロジック層(スクリプトエンジン)は、これらのインターフェースだけを通して世界と関わります。

抽象役割Web 実装ネイティブ実装
IInputすべての入力を論理アクションに変換PixiJS のポインタ / キーボードSDL_Event(Joy-Con / タッチ / キーボード)
IAudioBGM / SE / VOICE をカテゴリ別に再生・音量管理Web Audio APISDL_mixer
IStorageセーブ / ロードの抽象化IndexedDBファイルシステム(JSON)
IOpHandlerコンパイル済み命令(Op)の実行WebOpHandler(PixiJS)SDL2Engine + GLRenderer(OpenGL)

IInput / IAudio / IStoragepackages/core に定義された必須3抽象IOpHandler は Op 命令の実行を担う4本目です。KSC(コード記法)の VM はこれに加えて HostAPIpackages/ksc-compiler)を通じて描画・音声に委譲します。

物理ボタンの配置や音声 API の違いは、すべてこの境界の裏側に隠れます。作者は「OK が押されたらテキストを進める」というロジックだけを書けば、Web でも Switch でも正しく動きます。

Op[] / IR — プラットフォーム非依存の共通言語

抽象化のもう一つの柱が、プラットフォームに依存しない中間表現です。

  • KS(タグ記法) は事前に Op[](命令配列)へコンパイルされ、OpRunner が 1 命令ずつ IOpHandler に委譲して実行します。
  • KSC(コード記法) は型チェックを経て IR(バイトコード) にコンパイルされ、KscVM(Web は TypeScript、ネイティブは C++ 移植 ksc-vm-cpp)が同一の IR を実行します。

どちらも最終的に IOpHandler / HostAPI に収束します。同じ命令列が、プラットフォームを問わず共通です。だからこそ、描画・音声・入力の実装を差し替えるだけで、同じ作品が別環境で動きます。

KS  → Op[]  → OpRunner ┐
                        ├→ IOpHandler 実装(Web: PixiJS / ネイティブ: SDL2+OpenGL)
KSC → IR    → KscVM ────┘   (KSC は HostAPI 経由)

設計判断1: async と sync の分離

IOpHandler のメソッドは、完了を待つ必要があるもの(async)撃ちっぱなしでよいもの(sync) に分けています。

  • async(Promise を返す): textAppend(クリック待ち)、choice(選択結果を返す)、bgSet / chSet(トランジション完了を待つ)、waitMs / waitClick / waitVoiceEnd(待機が目的)、battleStart(勝敗を返す)など。
  • sync(void を返す): bgmPlaysePlayvoicePlaybgmStop など、再生を開始すれば十分で完了を待たない処理。

この分離で、エンジン側の実装が素直になります。await が必要なのは「待つ意味がある処理」だけです。

await handler.textAppend("赤音", "おはよう!");  // クリック待ち
await handler.bgSet("school_day");               // フェード完了を待つ
handler.bgmPlay("morning_theme");                // 即座に次へ(await 不要)
handler.sePlay("door_open");

設計判断2: 最小実装でも動く

IOpHandler は多くのメソッドを持ちますが、全部を真面目に実装する必要はありません。テキスト表示・選択肢など中核メソッドさえ実装すればスクリプトは動き、立ち絵・エフェクト・カメラなどの拡張メソッドは任意(未実装なら no-op)です。

  • 移植の参入障壁を下げる: 新プラットフォームへは、まず中核だけ実装すれば作品が動く。
  • 段階的に積める: 背景 → 立ち絵 → 音声 → 演出、の順に実装を進められる。
  • テストが容易: 本物の描画がなくても、検証用のハンドラを注入すれば、画面なしでロジック全体を CI で検証できる。

Switch の現在地

Switch はノベルゲーム路線の本命ターゲットで、設計は最初から Switch 移植を見据えています。

  • 抽象境界は整備済み: IInput / IAudio / IStorage / IOpHandler、課金抽象 IBilling、そして同一 IR を実行する ksc-vm-cpp。Switch 固有実装を差し込むだけで動く状態を目指しています。
  • ネイティブエンジンは実機ビルド済み: macOS / Windows / Android は SDL2 + OpenGL で動作確認済み(iOS はシミュレータ)。Switch も同じ抽象の裏に同じエンジンを置く構造です。
  • 公開ビルドは不可: Nintendo SDK は NDA 配下のため、公開リポジトリにツールチェーンや実装は含めません。移植は承認された開発環境で、コンソール移植サービスとして対応します。

これを成立させるため、以下は凍結(frozen)仕様です。

  • 論理解像度 1280×720 + セーフエリア 5%
  • セーブスキーマ v1(参照 ID のみ、画像・音声は埋め込まない)
  • 入力アクション 10 種(OK / Back / Menu / SkipToggle / AutoToggle / Log / QuickSave / QuickLoad / Screenshot / HideWindow)
  • Core コマンド集合(text / choice / jump / set / if / show / hide / move / fade / playBgm / playSe / playVoice / wait / overlay)。メインシナリオは Core のみで書け、Web 限定コマンド(openUrl / share / analytics 等)はメニュー・ナビゲーション限定で分離。

なぜここまで徹底するのか

  1. 全書き直しの回避 — 従来のノベル系エンジンは、プラットフォームごとにエンジンを書き直す悲劇が多発していました。抽象境界があれば、書き直すのは境界の裏だけです。
  2. AI 実装との相性 — 抽象が明確だと、AI エージェントはプラットフォームの細部を気にせず純粋なロジックに集中でき、バグが減ります。
  3. テストの容易性 — 本物の描画・物理ボタンがなくても、検証用ハンドラを注入するだけで CI 上で全ロジックをテストできます。

関連ドキュメント

Ad: inContent (336x280)
Ad: stickyBottom (728x90)
kaedevn - ノベルゲームを作れるプラットフォーム