dialogue-systems について
ゲームのライターや開発者向けに、分岐会話、選択肢、条件、ナラティブ状態を扱う対話システムのスキルです。Ink、Yarn Spinner、自作のデータ駆動ランナーの選択とゲームループへの組み込みを解説します。
主な機能
- 01Ink、Yarn Spinner、自作方式の比較
- 02ノード、選択肢、条件分岐の設計
- 03会話変数と状態の管理
- 04ローカライズ用の行ID
- 05ランナーのゲームループへの接続
活用シーン
- 01条件付き選択肢のある分岐会話の設計
- 02対話の作成方式の選定
- 03台詞、選択肢、コマンドのゲームへの統合
- 04分岐経路と状態変更の検証
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ gamedev-skills/ awesome-gamedev-agent-skills --skill dialogue-systemsインストール後、Claude Code で/dialogue-systemsを入力して読み込まれたか確認します。
同梱ファイルreferences/ink-and-yarn.mdreferences/runner.md
詳細
ダイアログシステム
会話をグラフとしてモデル化します。ノードにはセリフを格納し、選択肢によってフローが分岐し、条件によって選択肢を表示するかどうかを決め、変数によってプレイヤーの行動を記録します。最初に決めるべきことは、実績のあるオーサリングツール(Ink または Yarn Spinner)を採用するか、小規模なデータ駆動型ランナーを自作するかです。このスキルは Ink と Yarn の両方を扱い、visual-novel と rpg のジャンルで利用します。
使用する場面
- 分岐する会話、選択肢メニュー、後のダイアログに影響するナラティブ状態(フラグ、関係値など)を設計するときに使います。
- Ink、Yarn Spinner、カスタム JSON/リソース形式のどれを使うか決めるときに使います。
- ダイアログスクリプトをゲームループに組み込むときに使います(セリフの進行、選択肢の提示、コマンドの実行、変数の解決)。
使用しない場面: エンジンの UI(テキストボックス、ポートレート、選択肢ボタン)には、godot-ui-control またはエンジンの UI スキルを使います。ナラティブ変数をセッション間で永続化するには、save-systems を使います。Godot/Unity のリソースとしてのデータについては、godot-resources / unity-scriptableobjects を参照してください。
基本的なワークフロー
- オーサリング方法を選びます。
- Ink — 文章中心で、ライターに使いやすく、ウィーブ/ギャザー形式のフローを使います。ダイアログの多い作品や CYOA 形式のナラティブに最適です。Ink ランタイム/inkle プラグインを使って統合します。
- Yarn Spinner — ノードベースで、
<<commands>>を明示的に記述します。エンジンとの連携が多い、ゲーム主導のダイアログに適しています。 - カスタムランナー — 完全な制御が必要な場合や依存関係を最小限にしたい場合に、JSON/リソースグラフと小規模なインタープリターを使います。言語を作るのではなく、グラフを作りましょう。
- ノードの契約を定義します。 ノードは次のいずれかを返します。セリフ(話者+テキスト)、選択肢のセット、コマンド/副作用、終了/ジャンプです。ランナーがノードを進め、セリフ/選択肢を UI に渡します。
- 変数とフローを分離します。 ダイアログが読み書きする変数ストア(ブール値、数値、文字列)を用意し、その値に対する条件で選択肢の表示を制御します。
- 最初からローカライズを考慮します。 生の文字列ではなく行 IDを使って作成し、表示テキストはロケールをキーとする文字列テーブルから取得します。
- ゲームループから実行します。 ランナーはステートマシンです。
currentノード → コンテンツを出力 → 入力を待つ(続行または選択)→ 次へ進みます。 - 分岐をたどって検証します。 それぞれの選択肢の経路を実行し、条件や変数の書き込みを確認するとともに、すべての分岐が終了または有効なジャンプに到達することを確かめます。
パターン
1. エンジンに依存しないダイアロググラフ(コードではなくデータ)
{
"start": "guard_intro",
"nodes": {
"guard_intro": {
"speaker": "Guard", "line": "DLG_GUARD_001",
"choices": [
{ "text": "DLG_OPT_BRIBE", "to": "bribe", "if": "gold >= 50" },
{ "text": "DLG_OPT_LEAVE", "to": "end" }
]
},
"bribe": {
"speaker": "Guard", "line": "DLG_GUARD_BRIBED",
"set": { "gate_open": true, "gold": "gold - 50" },
"next": "end"
},
"end": { "end": true }
}
}
line/text は文字列テーブルの ID(ローカライズ用)であり、リテラルテキストではありません。if は選択肢の表示を制御し、set は変数ストアを変更します。このグラフをたどる完全なインタープリターは references/runner.md にあります。
2. ランナーのステップ(グラフ上のステートマシン)
# The runner holds the current node and a variable store; the UI calls advance().
func present(node):
if node.has("line"):
ui.show_line(node.speaker, localize(node.line))
if node.has("choices"):
var shown = node.choices.filter(func(c): return eval_cond(c.get("if", "")))
ui.show_choices(shown) # only choices whose condition passes
func choose(choice): # called when the player clicks a choice
apply_set(choice.get("set", {})) # write variables
goto(choice.to)
func goto(id):
current = graph.nodes[id]
apply_set(current.get("set", {}))
if current.get("end", false): ui.close(); return
present(current)
if current.has("next") and not current.has("choices"):
goto(current.next) # auto-advance linear nodes
3. Ink — ノット、選択肢、変数による分岐(inkle)
// Ink: '*' = once-only choice, '+' = sticky. [bracketed] text shows only in the
// choice, not the printed result. '->' diverts; '-> END' stops the flow.
VAR gold = 60
=== guard_intro ===
The guard blocks the gate.
* {gold >= 50} [Offer 50 gold] "Here, take it."
~ gold = gold - 50
The guard pockets it and steps aside. -> END
* [Leave] You turn back. -> END
Ink は各ノットが何回表示されたかを追跡するため、{visited_knot} は組み込みの条件として使えます。変数はグローバル(VAR)または一時的(~ temp)です。
4. Yarn Spinner — ノード、選択肢、コマンド(Yarn Spinner 3.x)
title: GuardIntro
---
<<declare $gold = 60>>
<<declare $gate_open = false>>
Guard: You can't pass.
-> Offer 50 gold <<if $gold >= 50>>
<<set $gold = $gold - 50>>
Guard: ...fine. Go on through.
<<set $gate_open to true>>
-> Leave
Guard: Good choice.
===
Yarn のセリフは Speaker: で始まる場合があります。選択肢には -> を使い、<<set>>/<<declare>> で $variables を管理し、<<if>> で選択肢の表示を制御し、<<jump NodeName>> でノード間を移動します。テキスト内の値は {$gold} で補間します。すべての変数は初めて使う前に宣言してください。未宣言の変数でもコンパイルは通ります(Yarn が型とゼロ/false/空の初期値を推測します)が、タイプミスが見過ごされる原因になります。これらの構文は Yarn Spinner 2.x から変更されていません。
よくある落とし穴
- 表示文字列をハードコードすると、ローカライズのたびに書き直しが必要になります。最初から文字列テーブルを参照して作成してください。
- シンプルな分岐ツリーに対して独自のスクリプト言語を作ることです。行テキスト、選択肢、フラグだけが必要なら、JSON/リソースグラフと50行のランナーを使うほうが、保守が必要なパーサーを作るよりも適しています。ライターに本格的なフロー制御が必要な場合は、Ink/Yarn を使ってください。
- 変数を UI と結び付けることです。同じダイアログをカットシーン、メニュー、テストで使えるように、ナラティブの状態は分けて保存してください。
save-systemsを使って永続化します。 - 到達不能ノードや行き止まりノードです。
next、選択肢、終了のいずれもないノードがあると、処理が何も通知されずに停止します。すべてのノードが終了するか、分岐することを検証してください。 - プレイヤーが再訪できる行ノードで状態を変更すると、処理が二重に適用されます(
goldが2回減るなど)。遷移時にsetを適用するか、既読フラグで重複を防いでください。 - Ink の
*(一度だけ)と+(固定)の選択肢を誤って混同することです。ループするメニューでは固定の+選択肢が必要です。そうしないと、選択肢が一度使っただけで消えてしまいます。
参考資料
references/ink-and-yarn.md— 構文を並べて比較できるチートシート(選択肢、分岐/ジャンプ、変数、条件、インクルード)と統合に関するメモ。references/runner.md— カスタムダイアログランナーの完全な実装例:グラフスキーマ、条件/式の評価、変数ストア、ローカライズの検索。
関連スキル
save-systems— ナラティブ変数と既読フラグを永続化します。godot-resources、unity-scriptableobjects— ダイアログをエンジンのデータとして保存します。godot-ui-control— テキストボックス、ポートレート、選択肢ボタンを表示します。visual-novel、rpg— このスキルと組み合わせるジャンルです。