すべての記事

2 年を経て、NarraLeaf-React は 1.0 へ

2024 年 8 月のプロトタイプから 1.0 まで、NarraLeaf-React の 2 年間の歩みと設計思想。

NarraLeaf は、2024 年夏のあるビジュアルノベルゲームの制作から始まりました。当時主流だったビジュアルノベルエンジンは技術的に古く、多くは専用のスクリプト言語に依存していました。画面のカスタマイズにも限りがあり、バージョン管理やチームでの共同作業にも組み込みにくいものでした。

2024 年 8 月 10 日、私たちは OtherSideProject から演出部分を切り出し、プロトタイプ Elecnovel を作りました。Elecnovel は Electron 上に構築され、最も基本的な action システムを実装していました。このシステムの中核となる設計は、現在も受け継がれています。8 月 14 日、Elecnovel は NarraLeaf に改名されました。名前は Narrator(語り手)と Leaf(軽量)に由来し、これが NarraLeaf という名称を確認できる最も古い記録です。

プロトタイプから NarraLeaf-React へ

プロジェクトが進むにつれ、Elecnovel は単一の Electron アプリケーションから複数の独立したソリューションへと分割されていきました。最初に独立したのは演出部分で、単体で公開される React ライブラリになりました。分割の目的は、この部分を軽量に保ち、演出という本来の役割に専念させることです。利用者が Electron アプリケーション一式の容量を抱える必要もありません。

2024 年 9 月 8 日に narraleaf-react のリポジトリが作成され、9 月 30 日に 0.0.1 が npm に公開されました。NarraLeaf-React が何よりも重視しているのは、直感的であることです。この原則はインターフェースの仕組みを作り始めた時点で確立され、NarraLeaf プロジェクトのあらゆる技術的な細部の基礎になっています。台詞、地の文、選択肢はすべてアクションとして記述され、コードの構造は脚本そのものの構造と一致します。次の例は、会話と選択肢を含むシーンです。

import { Character, Menu, Scene } from "narraleaf-react";

const scene = new Scene("序章", { background: "/bg/park.jpg" });
const emma = new Character("エマ");

scene.action([
  "木漏れ日が差し込んでいる。",
  emma`おはよう!`,
  Menu.prompt("朝ごはんを食べに行く?")
    .choose("いいよ", [emma`じゃあ、行こう。`])
    .choose("もう少し待って", [emma`うん、ここで待ってるね。`]),
]);

シーン内のアクションは、ゲームの開始前に静的なアクションツリーとして構築されます。エンジンはこのツリーに従って物語を進め、ゲームを実行せずに静的に解析することもできます。

NarraLeaf-React の脚本は TypeScript で直接記述し、専用の DSL は設けていません。開発者は使い慣れたエディタ、型チェック、パッケージマネージャーをそのまま使え、プロジェクトは他のフロントエンド開発と同じようにバージョン管理や CI に組み込めます。ツールは開発者がすでに使っている方法と技術スタックに合わせるべきだと、私たちは考えています。

軽量であることは、NarraLeaf の名前にある Leaf の意味であり、NarraLeaf-React が最初のバージョンから守り続けている原則でもあります。画面は React コンポーネントと CSS だけで描画し、Canvas や WebGL を用いた重いレンダリングライブラリには依存しません。この選択は互換性を優先したもので、NarraLeaf-React はスマートフォンや、単体 GPU を搭載していないローエンドの端末でも動作します。

2024 年末には、1 行ごとのボイス指定、ルビ、永続データ、シーン間のジャンプなど、ビジュアルノベルに必要な基本機能がそろっていました。

インターフェースの確立

2025 年、NarraLeaf-React のインターフェースは形を整えていきました。2 月に公開した 0.3 では React 19 を必須とし、画像、テキスト、トランスフォーム、シーン、オーディオのインターフェースを全面的に作り直しました。4 月の 0.4 ではコンポーネントのカスタマイズを開放し、ダイアログボックスやメニューなどの画面要素を、開発者自身の React コンポーネントに丸ごと置き換えられるようにしました。スタイルは CSS だけで決まり、命名規則に縛られることもありません。7 月の 0.7 では、ゲーム内の階層的なページの構成と切り替えを管理する LayoutRouter を導入しました。

これらの変更によって、NarraLeaf-React の役割が明確になりました。外に向けてはそのまま使える直感的なインターフェースを、内側には高度にカスタマイズできる仕組みを提供することです。脚本を書くだけの開発者は既定の画面をそのまま使え、深くカスタマイズしたい開発者はその任意の部分を置き換えられます。この過程で、見た目の設定もグローバルな設定からコンポーネント自身へと移っていきました。

2025 年半ば、NarraLeaf-React はライブラリ全体の中核となる設計である StackModel を導入しました。それまでのエンジンは、実行中のアクションを一つの参照で指すだけでした。物語に入れ子、ループ、分岐が現れると実行状態を追いにくくなり、セーブデータにも完全には書き込めませんでした。StackModel は実行をスタックとして管理し、入れ子のアクションを子スタックとして積み、非同期の演出を中断可能な Awaitable と Timeline でまとめて扱います。これによりアクションツリーは静的なまま保たれ、ゲームはどの時点でも中断してシリアライズし、セーブデータから同じ位置に復元できます。その後のセーブ、ロード、履歴の機能は、すべてこのモデルの上に作られています。

NarraLeaf の基盤へ

2025 年以降、NarraLeaf Desktop と NarraLeaf Studio が相次いで NarraLeaf-React の上に構築されました。Desktop はデスクトップアプリケーションに必要なウィンドウ管理、セーブ、パッケージングを加え、Studio はエディタのストーリープレビューと Dev Mode で NarraLeaf-React を直接動かしています。こうして NarraLeaf-React は、単独のプレイヤーから NarraLeaf プロジェクト全体の基盤になりました。

2026 年夏、NarraLeaf-React は 7 週間で 0.13 から 0.47 まで更新を重ね、演出、テキスト、オーディオ、アセット読み込みの機能が大きく広がりました。縦書き、圏点、オーディオバス、元に戻す・やり直しなどの機能も、この時期に加わっています。これらの機能も、軽量でカスタマイズ可能であるという原則に沿って設計されています。Live2D や Spine のアニメーションキャラクターを例に挙げると、これらのランタイムにはそれぞれ独自のライセンスと配布条件があり、NarraLeaf-React と一緒に配布することはできません。そのために追加した Puppet が担うのは、ステージ上の位置、レイヤー、カメラ、セーブデータだけで、描画は開発者が登録するバックエンドに任せます。NarraLeaf Studio の Live2D と Spine への対応も、このインターフェースを通じて実現しています。アセットのプリロードも同じ考え方で、エンジンはインターフェースを提供し、読み込みの方針は開発者が決めます。

1.0 とこれから

2026 年 9 月 30 日、NarraLeaf-React 1.0 を公開しました。1.0 は 0.47.2 にいくつかの修正を加えたもので、ゲームの動作は一切変わりません。非推奨とされた API も引き続き利用でき、0.47 からはコードを変更せずにアップグレードできます。

この 2 年間で、NarraLeaf-React のインターフェースと実装は何度も見直されてきましたが、直感的であること、軽量であること、そして開発者の技術スタックを尊重する姿勢は変わっていません。1.0 以降も、NarraLeaf-React の機能を広げ、既存の機能を磨き上げ、より高いカスタマイズ性を提供していきます。

使い方は NarraLeaf-React のドキュメント を、ソースコードは GitHub をご覧ください。

npm install narraleaf-react

謝辞

すべての支援をいただいた MewBaka Studio に感謝します。