LSDE Dialog Engine — Full Guide [Japanese] (plain text, auto-generated) ============================================================ Concatenates all guide sections for LLM consumption. Source: lsde-ts/docs/ja/guide/*.md ============================================================ # LSDEDE とは? **LSDE**(LS Dialog Editor)は、ゲームおよびソフトウェア開発者向けの無料ツールで、ビジュアルダイアローググラフ編集、AI翻訳、音声生成、i18nコード統合、プロジェクト診断を統合しています。対話グラフを、scene、block、connection、dictionary、action signature を含む blueprint(JSON、XML、YAML、CSV)として出力します。詳細: https://lepasoft.com/ja/software/ls-dialog-editor **LSDEDE**(LSDE Dialog Engine)は、これらの blueprint を読み込み実行するマルチランタイム engine です。複数の言語で利用可能なため、各種ゲームエンジンやフレームワークにネイティブ統合できます。 ## 利用可能なランタイム | ランタイム | 言語 | 対象 | ソース | |---------|----------|--------|--------| | **TypeScript** | TypeScript / JavaScript | リファレンス実装 | [lsde-ts](https://github.com/jonlepage/LS-Dialog-Editor-Engine/tree/master/lsde-ts) | | **C#** | C# (.NET Standard 2.1) | Unity, Godot Mono, .NET | [lsde-csharp](https://github.com/jonlepage/LS-Dialog-Editor-Engine/tree/master/lsde-csharp) | | **C++** | C++17 | Unreal Engine, カスタムエンジン | [lsde-cpp](https://github.com/jonlepage/LS-Dialog-Editor-Engine/tree/master/lsde-cpp) | | **GDScript** | GDScript | Godot 4 | [lsde-gdscript](https://github.com/jonlepage/LS-Dialog-Editor-Engine/tree/master/lsde-gdscript) | すべてのランタイムは同じ blueprint フォーマットを共有し、共通のクロス言語テストスイート(42テストケース)に合格しています。 ## アーキテクチャ すべてのランタイムは同じ **callback 駆動型グラフディスパッチャー** パターンに従います: 1. **Blueprint** — LSDE から出力されたファイル(JSON、XML、YAML)。scene、block、connection を含みます。 2. **Engine** — blueprint を検証し、内部グラフを構築して block を handler にディスパッチします。 3. **Handler** — 各 block タイプ(dialog、choice、condition、action)に反応するホストアプリケーション側の関数です。 4. **ゲーム本体** — condition、action、キャラクター解決は、登録された handler callback によって処理されます。 ``` Blueprint │ ▼ Engine ◄── next() ──┐ │ │ dispatch │ │ │ ▼ │ Handlers ────────────┘ ``` ## 設計原則 - **ゼロ依存** — どの言語でもランタイム依存関係なし。 - **フレームワーク非依存** — あらゆるゲームエンジンや UI フレームワークで動作します。 - **Callback 駆動型** — 内部レンダーループなし。準備ができたら `next()` を呼びます。 - **2階層 handler** — グローバル(engine レベル)と scene レベルの handler、`preventGlobalHandler()` 付き。 - **クロス言語準拠** — すべてのランタイムが同じ blueprint に対して同一の出力を生成します。 ================================================================================ # はじめに ## インストール ## 基本的な使い方 engine はグラフ走査マシンです — block を handler にディスパッチし、ホストアプリケーション側でそれに意味を与えます。handler がなければ、engine は何も出力しません。 > TIP: engine は `BlueprintExport` オブジェクトを受け取ります(ファイルではありません)。JSON、XML、YAML のいずれかを、プラットフォームに適したパーサーで読み込んでください。[解析とインポート](./parsing)を参照してください。 ## Blueprint の検証 `engine.init()` はエラー、警告、統計情報を含む[診断レポート](/api-ref/interfaces/DiagnosticReport)を返します。`check` オプションでゲーム側の機能とのクロスバリデーションが可能です: ================================================================================ # Blueprint と Scene ## Blueprint の構造 `BlueprintExport` は [LSDE](https://lepasoft.com/ja/software/ls-dialog-editor "Lepasoft Dialog Editor") エディターから出力される JSON ファイルです。engine が必要とするすべてのデータを含んでいます。 ## Scene scene は独立した対話シーケンスです — 会話、カットシーン、チュートリアル、ショップのやり取りなど。ゲームでは通常、スクリプトイベントによってトリガーされます:プレイヤーが NPC に話しかける、ゾーンに入る、アイテムを拾うなど。 各 scene は独自のエントリーブロック、独自のフロー、独自の状態を持ちます。複数の scene を並行して実行できます(例:メインダイアログとチュートリアルオーバーレイ)。scene は [`BlueprintScene`](/api-ref/interfaces/BlueprintScene) インターフェースで定義されます: ## Connection connection は block 間のワイヤーです — どの block がどの block に繋がるかを定義します。エディター上では視覚的に描画し、エクスポートではソース → ターゲットのフラットなリストになります。[`BlueprintConnection`](/api-ref/interfaces/BlueprintConnection) インターフェースで定義されます: 通常、connection を直接検査する必要はありません — engine が内部でルーティングを処理します。ただし、必要に応じて [`onValidateNextBlock`](/api-ref/classes/DialogueEngine#onvalidatenextblock) で参照できます。 ## Dictionary dictionary はゲームのレジスタを記述します — スイッチ、変数、インベントリなど。開発者が [LSDE](https://lepasoft.com/ja/software/ls-dialog-editor "Lepasoft Dialog Editor") エディターで宣言し、ナラティブデザイナーにゲーム内で利用可能な変数を公開します。ランタイムでは、開発者が各 dictionary をゲームの対応するシステムにマッピングします。[`condition`](/api-ref/interfaces/ExportCondition) と [`onResolveCondition`](/api-ref/classes/DialogueEngine#onresolvecondition) がこれらのキーを使ってゲーム状態を評価します。[`Dictionary`](/api-ref/interfaces/Dictionary) インターフェースで定義されます: ## Action Signature signature はゲームで利用可能なアクションタイプを記述します — `set_flag`、`play_sound`、`give_item`。開発者が [LSDE](https://lepasoft.com/ja/software/ls-dialog-editor "Lepasoft Dialog Editor") エディターで宣言し、ナラティブデザイナーが型付きパラメーターでアクションシーケンスを構成できるようにします。ランタイムでは、signature の `id` を開発者が自分のシステムにマッピングします。[`ActionSignature`](/api-ref/interfaces/ActionSignature) インターフェースで定義されます: ================================================================================ # Block タイプ block は dialogue scene の構成要素です — エディターグラフの各ノードが block です。engine は block から block へフローをルーティングし、各タイプに対応する handler を呼び出します。 タイプは5種類あります:**Dialog**、**Choice**、**Condition**、**Action**、**Note**。最初の4つは専用の handler(`onDialog`、`onChoice`、`onCondition`、`onAction`)を持つコンテンツ block です — 4つとも**必須**で、`start()` 呼び出し時に検証されます。Note block は自動的にスキップされます。 handler は2つのレベルで構成されます:**global handler**(engine に登録)はすべての scene をカバーし、ほとんどのゲームではこれだけで十分です。**scene handler**([`SceneHandle`](/api-ref/interfaces/SceneHandle) に登録)は、特定の scene で global を補完または上書きできます。詳細は [Handlers](/ja/guide/handlers) を参照してください。 ## DIALOG dialog block はセリフを表します — キャラクターの会話、ナレーター、画面上のテキスト。engine は `onResolveCharacter` callback で話しているキャラクターを解決し、`context.character` として公開します。典型的な dialog handler はゲーム内でテキストインスタンス(テキストボックス、吹き出し、字幕…)を作成し、プレイヤーやアニメーションの完了を待ち、`next()` を呼び出して engine を進めます。オプションの cleanup 関数で、engine が次の block に移る際に副作用をクリーンアップできます。 ナラティブデザイナーがキャラクターごとに専用の出力を割り当てた場合([`portPerCharacter`](/api-ref/interfaces/NativeProperties#portpercharacter))、handler は `resolveCharacterPort()` を呼び出して `next()` 時にどのパスを辿るかを engine に伝える必要があります。 ## CHOICE choice block はプレイヤーが選択する分岐点です — ダイアログメニュー、選択肢リスト。`context.choices` に全ての選択肢が含まれます。[`onResolveCondition()`](/ja/guide/choice-visibility) が設定されている場合、各選択肢は `visible: true | false` でタグ付けされ、handler が表示する選択肢をフィルタリングします。プレイヤーの操作後、`selectChoice(uuid)` で engine にどのパスを辿るかを伝え、`next()` でフローを進めます。 完全なオプトイン方式のタグ付けシステムについては [Choice の表示制御](/ja/guide/choice-visibility) を参照してください。 ## CONDITION condition block は不可視のスイッチです — ゲーム状態を評価し、プレイヤーに見えることなくフローを2つのパスのどちらかに送ります。handler は block の条件(変数、フラグ、インベントリ…)を評価し、`context.resolve(result)` を呼び出します — `true` は port 0 に、`false` は port 1 に従います。`choice:` で始まるキーの条件はプレイヤーの過去の選択を参照しており、`scene.evaluateCondition(cond)` が内部の履歴から自動的に解決します。 condition block は2つの評価モードをサポートしています: - **switch モード**(デフォルト):条件グループを順番に評価します。最初にマッチしたグループが対応する port(`true`/`case_N`)にルーティングします。マッチしない場合は `false`/`default` port に従います。 - **dispatcher モード**([`enableDispatcher`](/api-ref/interfaces/NativeProperties#enabledispatcher) `= true`):マッチした**すべて**のグループが async track として同時に発火します。`false`/`default` port はメインの継続 track("Continue")となり、**常に実行されます**。条件 port に接続される block は async でなければなりません。 ## ACTION action block はゲーム内で副作用を発動します — アイテムの付与、サウンドの再生、フラグの設定。各アクションは開発者が自身のシステムにマッピングする `actionId` を参照します。handler はアクションリストを実行し、`context.resolve()` で "then" port を、`context.reject(error)` で "catch" port を辿ります("catch" 接続がない場合は "then" にフォールバック)。 ## NOTE note block はナラティブデザイナーのためのメモです — コメント、リマインダー、コンテキスト。走査中は自動的にスキップされます。[`onBeforeBlock`](/ja/guide/lifecycle) で note block をインターセプトすることは技術的に可能ですが、推奨されません — action block がすべての副作用のニーズをカバーできます。 ## 共通プロパティ すべての block は以下の基本フィールドを共有します([`BlueprintBlockBase`](/api-ref/interfaces/BlueprintBlockBase)): | フィールド | 型 | 説明 | |-------|------|-------------| | [`uuid`](/api-ref/interfaces/BlueprintBlockBase#uuid) | `string` | 一意識別子 | | [`type`](/api-ref/interfaces/BlueprintBlockBase#type) | `BlockType` | 判別タイプ | | [`label`](/api-ref/interfaces/BlueprintBlockBase#label) | `string?` | 人間可読な名前 | | [`parentLabels`](/api-ref/interfaces/BlueprintBlockBase#parentlabels) | `string[]?` | エディター内の親フォルダー階層 | | [`properties`](/api-ref/interfaces/BlueprintBlockBase#properties) | `BlockProperty[]` | キー・バリュープロパティ | | [`userProperties`](/api-ref/interfaces/BlueprintBlockBase#userproperties) | `Record?` | 自由形式のユーザープロパティ | | [`nativeProperties`](/api-ref/interfaces/BlueprintBlockBase#nativeproperties) | `NativeProperties?` | 実行プロパティ | | [`metadata`](/api-ref/interfaces/BlueprintBlockBase#metadata) | `BlockMetadata?` | 表示メタデータ(キャラクター、タグ、カラー) | | [`isStartBlock`](/api-ref/interfaces/BlueprintBlockBase#isstartblock) | `boolean?` | エントリー block を示す | ### NativeProperties | フィールド | 型 | 説明 | |-------|------|-------------| | [`isAsync`](/api-ref/interfaces/NativeProperties#isasync) | `boolean?` | 並列 async トラックで実行 | | [`delay`](/api-ref/interfaces/NativeProperties#delay) | `number?` | 実行前の遅延(`onBeforeBlock` で処理) | | [`timeout`](/api-ref/interfaces/NativeProperties#timeout) | `number?` | 実行タイムアウト | | [`portPerCharacter`](/api-ref/interfaces/NativeProperties#portpercharacter) | `boolean?` | metadata 内のキャラクターごとに1つの出力 port | | [`skipIfMissingActor`](/api-ref/interfaces/NativeProperties#skipifmissingactor) | `boolean?` | 参照アクターが不在の場合 block をスキップ | | [`debug`](/api-ref/interfaces/NativeProperties#debug) | `boolean?` | エディタ用デバッグフラグ | | [`waitForBlocks`](/api-ref/interfaces/NativeProperties#waitforblocks) | `string[]?` | この block が進行する前に訪問済みでなければならない block UUID | | [`waitInput`](/api-ref/interfaces/NativeProperties#waitinput) | `boolean?` | プレイヤー入力制御用パッシブフラグ | | [`enableDispatcher`](/api-ref/interfaces/NativeProperties#enabledispatcher) | `boolean?` | dispatcher モード:マッチしたすべての条件が async track として発火、false/default port は継続 track | ================================================================================ # Choice の表示制御 ## 概要 CHOICE block がディスパッチされると、`context.choices` には blueprint で定義された**すべての** choice が常に含まれます — 事前にフィルタリングされるものはありません。engine は配列から choice を削除することはありません。 表示制御フィルタリングが必要な場合(例:ゲームステートや以前の選択に基づいて choice を非表示にする)、engine は**オプトイン方式のタグ付け**システムを提供します。condition リゾルバーを一度インストールすると、`onChoice` handler が呼ばれる前に、engine が各 choice に `visible: true | false` をタグ付けします。 ## セットアップ engine に condition リゾルバーを登録します — scene を開始する前に一度だけ: インストールされると、engine は `onChoice` を呼び出す**前に**各 choice の `visibilityConditions` を評価します。同じリゾルバーは condition block のグループも事前評価します — 詳細は [Condition blocks](/ja/guide/block-types#condition) を参照してください。 - **`choice:` condition**(以前のプレイヤー選択を参照)は、engine の内部 choice 履歴によって自動的に解決されます — 登録された callback には渡されません。 - **ゲームステート condition**(その他すべて)は、登録された callback に委任されます。 - `&`(AND)と `|`(OR)によるチェーンは、両方のタイプにまたがって正しく動作します。 ## onChoice でのフィルタリング handler 内で、1行でフィルタリングできます: ### なぜ `visible !== false` であって `=== true` ではないのか? **リゾルバーがインストールされていない**場合、`visible` は `undefined` です。`undefined !== false` は `true` に評価されるため、すべての choice が通過します — デフォルトで後方互換性があります。リゾルバーが**インストールされている**場合、choice は明示的に `true` または `false` でタグ付けされます。 | `visible` の値 | 意味 | `!== false` | |---|---|---| | `true` | リゾルバーインストール済み、choice は通過 | `true` | | `false` | リゾルバーインストール済み、choice は非表示 | `false` | | `undefined` | リゾルバー未インストール | `true` | ## RuntimeChoiceItem リゾルバーがインストールされている場合、`context.choices` 内の各 choice は `RuntimeChoiceItem` です — `visible` タグが追加された `ChoiceItem` の拡張です: ```ts [TypeScript] interface RuntimeChoiceItem extends ChoiceItem { visible?: boolean; // true | false | undefined } ``` ```csharp [C#] public class RuntimeChoiceItem : ChoiceItem { public bool? Visible { get; set; } // true | false | null } ``` ```cpp [C++] struct RuntimeChoiceItem : ChoiceItem { std::optional visible; // true | false | nullopt }; ``` ```gdscript [GDScript] # RuntimeChoiceItem is a Dictionary with an extra "visible" key: # { "uuid": "...", "dialogueText": {...}, "visible": true/false/absent } ``` リゾルバーなしの場合、choice は `RuntimeChoiceItem` のままですが、`visible` は `undefined`/`null`/`nullopt`/absent のままです。 ## 使用例 ### 標準 — 表示可能な choice を表示 ```ts [TypeScript] engine.onChoice(({ context, next }) => { const visible = context.choices.filter(c => c.visible !== false); ui.showChoices(visible, (uuid) => { context.selectChoice(uuid); next(); }); }); ``` ```csharp [C#] engine.OnChoice(args => { var visible = args.Context.Choices .Where(c => c.Visible != false).ToList(); ShowChoicesUI(visible, uuid => { args.Context.SelectChoice(uuid); args.Next(); }); return null; }); ``` ```cpp [C++] engine.onChoice([](auto*, auto*, auto* ctx, auto next) -> CleanupFn { std::vector visible; for (const auto& c : ctx->choices()) if (!c.visible.has_value() || c.visible.value()) visible.push_back(&c); showChoicesUI(visible, [ctx, next](const auto& uuid) { ctx->selectChoice(uuid); next(); }); return {}; }); ``` ```gdscript [GDScript] engine.on_choice(func(args): var visible = [] for c in args["context"].choices: if c.get("visible") != false: visible.append(c) show_choices_ui(visible, func(uuid): args["context"].select_choice(uuid) args["next"].call() ) return Callable() ) ``` ### タイムアウト付き choice — タイムアウト時に自動選択 ```ts [TypeScript] engine.onChoice(({ block, context, next }) => { const visible = context.choices.filter(c => c.visible !== false); const timeout = block.nativeProperties?.timeout; const resolve = (choice) => { context.selectChoice(choice.uuid); next(); }; if (timeout) { const timer = setTimeout(() => resolve(visible[0]), timeout * 1000); ui.showChoices(visible, (uuid) => { clearTimeout(timer); resolve(visible.find(c => c.uuid === uuid)); }); } else { ui.showChoices(visible, (uuid) => resolve(visible.find(c => c.uuid === uuid))); } }); ``` ```csharp [C#] engine.OnChoice(args => { var (_, block, context, next) = args; var visible = context.Choices .Where(c => c.Visible != false).ToList(); var timeout = block.NativeProperties?.Timeout; void Resolve(RuntimeChoiceItem choice) { context.SelectChoice(choice.Uuid); next(); } if (timeout.HasValue) { // use your engine's timer — cancel on player selection var timer = ScheduleTimer((float)timeout.Value, () => Resolve(visible[0])); ShowChoicesUI(visible, uuid => { timer.Cancel(); Resolve(visible.First(c => c.Uuid == uuid)); }); } else { ShowChoicesUI(visible, uuid => Resolve(visible.First(c => c.Uuid == uuid))); } return null; }); ``` ```cpp [C++] engine.onChoice([](auto*, auto* block, auto* ctx, auto next) -> CleanupFn { std::vector visible; for (const auto& c : ctx->choices()) if (!c.visible.has_value() || c.visible.value()) visible.push_back(&c); auto timeout = block->nativeProperties ? block->nativeProperties->timeout : std::nullopt; auto resolve = [ctx, next](const std::string& uuid) { ctx->selectChoice(uuid); next(); }; if (timeout.has_value()) { // use your engine's timer — cancel on player selection auto timer = scheduleDelay(timeout.value(), [&]() { resolve(visible[0]->uuid); }); showChoicesUI(visible, [resolve, timer](const auto& uuid) { timer->cancel(); resolve(uuid); }); } else { showChoicesUI(visible, resolve); } return {}; }); ``` ```gdscript [GDScript] engine.on_choice(func(args): var ctx = args["context"] var next_fn = args["next"] var block = args["block"] var visible = [] for c in ctx.choices: if c.get("visible") != false: visible.append(c) var timeout_val = block.get("nativeProperties", {}).get("timeout", 0) if timeout_val > 0: # use your engine's timer — cancel on player selection var timer = get_tree().create_timer(timeout_val) timer.timeout.connect(func(): ctx.select_choice(visible[0]["uuid"]) next_fn.call() ) show_choices_ui(visible, func(uuid): timer.time_left = 0 # cancel ctx.select_choice(uuid) next_fn.call() ) else: show_choices_ui(visible, func(uuid): ctx.select_choice(uuid) next_fn.call() ) return Callable() ) ``` ### 非表示の choice をグレーアウト表示 ```ts [TypeScript] engine.onChoice(({ context, next }) => { for (const choice of context.choices) { if (choice.visible === false) { ui.addGreyed(choice); // show but disabled } else { ui.addNormal(choice); // selectable } } // wait for player selection... }); ``` ```csharp [C#] engine.OnChoice(args => { foreach (var choice in args.Context.Choices) { if (choice.Visible == false) AddGreyed(choice); // show but disabled else AddNormal(choice); // selectable } // wait for player selection... return null; }); ``` ```cpp [C++] engine.onChoice([](auto*, auto*, auto* ctx, auto next) -> CleanupFn { for (const auto& choice : ctx->choices()) { if (choice.visible.has_value() && !choice.visible.value()) addGreyed(choice); // show but disabled else addNormal(choice); // selectable } // wait for player selection... return {}; }); ``` ```gdscript [GDScript] engine.on_choice(func(args): for choice in args["context"].choices: if choice.get("visible") == false: add_greyed(choice) # show but disabled else: add_normal(choice) # selectable # wait for player selection... return Callable() ) ``` ### チュートリアル — 表示制御を完全に無視 ```ts [TypeScript] tutorial.onChoice(({ context, next }) => { // force-select the first choice, no filtering context.selectChoice(context.choices[0].uuid); next(); }); ``` ```csharp [C#] tutorial.OnChoice(args => { // force-select the first choice, no filtering args.Context.SelectChoice(args.Context.Choices[0].Uuid); args.Next(); return null; }); ``` ```cpp [C++] tutorial->onChoice([](auto*, auto*, auto* ctx, auto next) -> CleanupFn { // force-select the first choice, no filtering ctx->selectChoice(ctx->choices()[0].uuid); next(); return {}; }); ``` ```gdscript [GDScript] tutorial.on_choice(func(args): # force-select the first choice, no filtering args["context"].select_choice(args["context"].choices[0]["uuid"]) args["next"].call() return Callable() ) ``` ## エバリュエーターの共有 `onResolveCondition` を使えば、1つの callback で choice の可視性と condition block の事前評価の**両方**を処理できます。ロジックを重複させる必要はありません: > TIP: `onResolveCondition` 以前は、同じ `gameState.check(...)` ロジックを `setChoiceFilter` と `onCondition` に別々に登録する必要がありました。統合リゾルバーでは1つの callback で済みます — engine が両方を自動的に処理します。 ## 上級: 手動フィルタリング グローバルリゾルバーをインストールしたくない場合、`LsdeUtils` がローレベルのユーティリティを提供します: ```ts [TypeScript] import { LsdeUtils } from '@lsde/dialog-engine'; const visible = LsdeUtils.filterVisibleChoices( block.choices ?? [], (cond) => gameState.check(cond.key, cond.operator, cond.value), scene, // optional — enables choice: condition resolution via history ); ``` ```csharp [C#] var visible = LsdeUtils.FilterVisibleChoices( block.Choices ?? new(), cond => GameState.Check(cond.Key, cond.Operator, cond.Value), scene // optional — enables choice: condition resolution via history ); ``` ```cpp [C++] auto visible = lsde::LsdeUtils::FilterVisibleChoices( block->choices, [](const auto& cond) { return gameState.check(cond.key, cond.op, cond.value); }, scene // optional — enables choice: condition resolution via history ); ``` ```gdscript [GDScript] var visible = LsdeUtils.filter_visible_choices( block.get("choices", []), func(cond): return game_state.check(cond), scene # optional — enables choice: condition resolution via history ) ``` `scene` パラメーターを指定すると、`choice:` condition の自動解決が有効になります。指定しない場合、すべての condition は登録されたリゾルバー callback に委任されます。 ================================================================================ # ハンドラー ## Handler handler は engine とゲームを繋ぐ橋です。オブザーバーのように機能します — 関数を登録すると、対応するイベントが発生した時に engine がそれを呼び出します。テキストの表示、アニメーションの再生、状態の評価など、ゲームエンジンで適切な動作をトリガーするのは handler を通じて行います。 engine は以下の handler を公開しています: | Handler | レベル | 説明 | |---------|--------|------| | [`onDialog`](/api-ref/classes/DialogueEngine#ondialog) | global / scene | dialog block — テキスト表示 | | [`onChoice`](/api-ref/classes/DialogueEngine#onchoice) | global / scene | choice block — 選択肢の提示 | | [`onCondition`](/api-ref/classes/DialogueEngine#oncondition) | global / scene | condition block — 評価と分岐 | | [`onAction`](/api-ref/classes/DialogueEngine#onaction) | global / scene | action block — 副作用のトリガー | | [`onResolveCharacter`](/api-ref/classes/DialogueEngine#onresolvecharacter) | global / scene | どのキャラクターが話しているかを解決 | | [`onBeforeBlock`](/api-ref/classes/DialogueEngine#onbeforeblock) | global | 各 block の前(delay、開始アニメーション…) | | [`onValidateNextBlock`](/api-ref/classes/DialogueEngine#onvalidatenextblock) | global | block に進む前のバリデーション | | [`onInvalidateBlock`](/api-ref/classes/DialogueEngine#oninvalidateblock) | global | バリデーション失敗時の処理 | | [`onSceneEnter`](/api-ref/classes/DialogueEngine#onsceneenter) | global / scene | scene の開始 | | [`onSceneExit`](/api-ref/classes/DialogueEngine#onsceneexit) | global / scene | scene の終了 | | [`onBlock`](/api-ref/interfaces/SceneHandle#onblock) | scene | UUID で特定の block をオーバーライド | | [`onDialogId`](/api-ref/interfaces/SceneHandle#ondialogid) | scene | UUID で特定の DIALOG block をオーバーライド(型安全) | | [`onChoiceId`](/api-ref/interfaces/SceneHandle#onchoiceid) | scene | UUID で特定の CHOICE block をオーバーライド(型安全) | | [`onConditionId`](/api-ref/interfaces/SceneHandle#onconditionid) | scene | UUID で特定の CONDITION block をオーバーライド(型安全) | | [`onActionId`](/api-ref/interfaces/SceneHandle#onactionid) | scene | UUID で特定の ACTION block をオーバーライド(型安全) | | [`onResolveCondition`](/api-ref/classes/DialogueEngine#onresolvecondition) | global | 統合 condition リゾルバー(choice の可視性 + condition の事前評価) | | ~~[`setChoiceFilter`](/api-ref/classes/DialogueEngine#setchoicefilter)~~ | global | _非推奨 — 代わりに `onResolveCondition` を使用してください_ | `onDialog`、`onChoice`、`onAction` は**必須**です — `start()` 呼び出し時に engine がその存在を検証し、欠けている場合は記述的なエラーをスローします。`onCondition` は `onResolveCondition` がインストールされている場合は**オプション**です — engine が事前評価された condition グループから自動ルーティングします。 ## Two-Tier Handler System engine は handler を2つの階層で解決します: - **Global handler** — engine に登録され、すべての scene のデフォルト動作を定義します。ほとんどの場合これだけで十分です。 - **Scene handler** — 特定の [`SceneHandle`](/api-ref/interfaces/SceneHandle) に登録され、scene が異なるレンダリングや制御フローを必要とする場合にデフォルト動作をオーバーライドまたは拡張できます。まれですが、利用可能です。 block がディスパッチされると、engine は以下の順序で handler を解決します: 1. `handle.onBlock(uuid)` または `handle.onDialogId(uuid)` / `handle.onActionId(uuid)` / ... — block 固有のオーバーライド 2. `handle.onDialog()` / `handle.onChoice()` / ... — scene レベルのタイプ handler 3. `engine.onDialog()` / `engine.onChoice()` / ... — global handler 両方の階層が存在する場合、両方が順番に実行されます — scene が先、次に global — ただし scene handler が `context.preventGlobalHandler()` を呼び出して global パスを抑制した場合を除きます。 ## Character Resolution キャラクター解決はオプションです。`onResolveCharacter` callback を登録すると、engine は `metadata.characters` にキャラクターを持つすべての block の前にそれを呼び出します。callback は block に割り当てられたキャラクターのリストを受け取り、アクティブにすべきキャラクターを返します — 利用可能なキャラクターがいない場合は `undefined` を返します。解決されたキャラクターは、すべての handler で `context.character` としてアクセスできます。 これはゲーム状態を照会するための理想的な統合ポイントです:キャラクターがシーンに存在するか、生存しているか、カメラ範囲内にいるかなどを確認できます。`undefined` を返すことで、[`skipIfMissingActor`](/api-ref/interfaces/NativeProperties#skipifmissingactor) による block スキップ、`handle.cancel()` による scene キャンセル、handler 内での直接処理など、複数の戦略が可能になります。 ## Scene Lifecycle `onSceneEnter` と `onSceneExit` callback で、scene の開始と終了に反応できます — シネマモードの有効化、NPC の停止、UI の準備、リソースのクリーンアップなど。global レベル(engine 上)と scene レベル(`handle.onEnter()` / `handle.onExit()` 経由)の両方で利用可能です。scene handler が定義されている場合、global を置き換えます。 ## Block Override `onBlock(uuid)` で特定の block をその識別子で指定し、専用の handler を割り当てることができます。これはまれなユースケースです — ジェネリック handler が大半のニーズをカバーします — ただし、個別の block が異なる動作を必要とする非常に特殊なシナリオでは利用可能です。 ## Type-Safe Block Override `onDialogId(uuid)`、`onChoiceId(uuid)`、`onConditionId(uuid)`、`onActionId(uuid)` は `onBlock(uuid)` の型安全な代替メソッドです。動作は全く同じ — 同じ優先度、同じ `preventGlobalHandler` サポート — ただし handler がジェネリックユニオンではなく、特殊化された block 型とコンテキストを受け取ります。 登録時に block タイプが分かっていて、`block` と `context` のオートコンプリートが必要な場合に使用してください。 ## Visual Reference ### Two-Tier Handler Dispatch ```mermaid flowchart TD A[block dispatched] --> B{resolve scene handler} B --> B1{"onBlock(uuid) /\nonDialogId(uuid) etc.?"} B1 -- found --> S B1 -- not found --> B2{"handle.onDialog() etc.?"} B2 -- found --> S B2 -- not found --> G S[execute scene handler] --> D{preventGlobalHandler?} D -- yes --> Z[done] D -- no --> G["execute global handler\nengine.onDialog() etc."] G --> Z ``` ================================================================================ # ゲームエンジン統合 LSDE はエンジン非依存です — ゲームエンジン、UIフレームワーク、オーディオシステムへの依存はありません。グラフを走査し、登録された handler を呼び出します。このページでは、主要なゲームエンジンへの組み込み方法を示します。 handler の詳細な実装については、[Block Types](./block-types) と [Handlers](./handlers) を参照してください。 ## 完全な統合例 以下の例は、各エンジンに LSDE を統合する一つの方法を示しています。必須の 4 つの handler — dialog、choice、condition、action — を一つのクラスにまとめた、出発点となるコードです。 ゲームごとにニーズは異なります。構造、レイアウト、UI をプロジェクトに合わせて調整してください。 ## 4つの Handler 各 handler は block データと `next()` コールバックを受け取ります。開発者がエンジン内でデータを処理し、block の処理が完了したら `next()` を呼び出します。その呼び出しのタイミングはゲーム側に委ねられています。 - **Dialog** — テキスト、キャラクター、ネイティブプロパティ。UI にダイアログを表示し、プレイヤーの入力またはディレイを待ってから `next()` を呼び出します。engine が次の block に移る際に UI を非表示にするクリーンアップ関数を返します。 - **Choice** — `choiceFilter` が設定されている場合、`visible` タグ付きの選択肢リスト。対応する UI 要素を作成します — ボタン、リスト、ラジアルメニュー。プレイヤーが選択したら、`selectChoice(uuid)` で分岐先を engine に伝え、`next()` でフローを進めます。 - **Condition** — block に定義された条件。ゲームロジックで評価します — フラグ、クエスト、インベントリのチェック。`context.resolve(true)` はポート 0 へ、`context.resolve(false)` はポート 1 へフローを送ります。 - **Action** — block に定義されたアクション。エンジンで実行します — サウンド再生、アイテム付与、シネマティックのトリガー。`context.resolve()` は成功を確認、`context.reject(err)` は失敗を通知します。 ## Tips - **`next()` はリモコンです。** 高速ダイアログのために即座に呼び出すか、アニメーションが完了するまで保持します。engine は待機します — 時間の概念を持ちません。 - **クリーンアップ関数が後片付けします。** どの handler からでも関数を返せば、engine が次の block に移る時に呼び出します。UI の非表示、オーディオの停止、ノードの解放に最適です。 - **`onBeforeBlock` が delay を処理します。** engine は `nativeProperties.delay` を強制しません — `onBeforeBlock` がそれを読み取り、タイマー後に `resolve()` を呼び出します。完全な制御権があります。 - **async track は並列フローです。** カットシーンでダイアログとカメラ移動を同時に行う場合、エディタで `isAsync` マークされた block は独立した track で実行されます。 ================================================================================