進行ルールとゲームアクション
以下は、自分のスクリプトやコミュニティの連携機能でエクスポートを使うためのルールです。JSONに入っているのはデータであり、実行するソースコードではありません。エディターのレイアウト、選択状態、画面上の配置はゲームの動作を制御しません。
最初のステップの前に
format = vnle.story、containerVersion = 1を確認し、execution.contractVersionとrequiredCapabilitiesに対応しているか調べます。作例は1.1、core.v1、substory.call-return.v1を使用します。JSON Schemaによる構造の検証に加え、参照、型、対応コマンドの確認も必要です。未知のkindを黙って飛ばしてはいけません。
各ノードの動作
| kind | 動作 |
|---|---|
dialogue | 文章を表示してContinueを待ち、その後continuationへ進みます。 |
choice | optionOrderの順に選択肢を並べ、option.conditionで絞り込みます。選ばれた回答のeffectsを一度だけ適用してcontinuationへ進みます。whenEmptyの指定にも従ってください。 |
branch | conditionを評価し、whenTrueまたはwhenFalseへ進みます。 |
action | effectsを順番に適用してからcontinuationへ進みます。 |
jump | 新しい戻り先を保存せず、指定された開始地点へ移ります。 |
call | continuationを保存してから、指定された開始地点へ移ります。 |
return | 直近に保存したcontinuationから再開します。Callを経ていないReturnはエラーです。 |
command | 引数を評価してゲームアクションを要求します。結果を待ち、対応する結果の進行先へ進みます。 |
end | サブストーリーの中でも、現在の会話全体を終了します。呼び出し元に戻りたい場合はReturnを使います。 |
条件と型付きの値
オペランドには、型付きの値を持つliteralか、variableRefを持つvariableを使います。compareはeq、ne、lt、lte、gt、gteに対応します。allはすべての子条件、anyは少なくとも一つの子条件の成立を求め、notは子条件を反転します。真偽値を文字列の"true"/"false"と混同しないでください。
変数の変更にはset、add、subtractを使います。add/subtractで扱う整数は−2147483648から2147483647です。変更は順番に評価しますが、無効な変更があれば一部だけを確定しないでください。会話のたびに保存済みの値を初期値defaultValueで上書きしてはいけません。
Jump · 購入後に倉庫へ進む
ジャンプ
鍵をすでに購入している場合は、Warehouseの開始地点へ直接ジャンプします。targetはflowRefとentryRefを持ち、execution.entries[entryRef].nodeRefから次のノードを取得します。Jumpがエンジンのシーンやマップを自動で開くわけではありません。それには対応するゲームアクションが必要です。
{
"flowRef": "1f69a7b6-1bdc-4dff-b8ee-9b2819b35168",
"id": "2e1710dc-c32f-418f-abca-e055d6fd28f3",
"kind": "jump",
"target": {
"entryRef": "a4dd48dc-b0e2-47fc-b102-def52583a8fe",
"flowRef": "2a0a259c-b76a-4de0-8cee-567713c2d320"
}
}Call Substory / Return · ミラが倉庫を説明する
サブストーリー呼び出し/復帰
この流れもJSONで表現されています。挨拶の後、鍵のかかった倉庫の説明を呼び出します。ゲームはcontinuationをスタックに積み、targetへ移ります。ミラの説明後にreturnへ到達したら、直近のcontinuationを取り出して「Already have the key?」へ進みます。入れ子の呼び出しも同じルールです。
{
"continuation": {
"kind": "node",
"nodeRef": "95e9a55f-f6b1-4696-9c52-6ea3ea2ac7e5"
},
"flowRef": "1f69a7b6-1bdc-4dff-b8ee-9b2819b35168",
"id": "85b3aadf-2b95-4150-bc44-c0c796337874",
"kind": "call",
"target": {
"entryRef": "2bac8555-3cae-4a21-9b8c-b410ab207ebb",
"flowRef": "ea13b52e-092a-44ff-b087-0ce65d040c9a"
}
}{
"flowRef": "ea13b52e-092a-44ff-b087-0ce65d040c9a",
"id": "f8b8673a-cb86-40ae-a4f6-d37a5c1563c3",
"kind": "return"
}関係するすべてのノードはstory.jsonに含まれます。Callは別のJSONやスクリプトを読み込む操作ではありません。入れ子の深さはmaxCallDepthで制限します。空のスタックでReturnするとエラーです。一方、Endは会話全体を終了し、スタックを空にします。
Command · 店を開く、鍵を渡す
コマンド
例えば店を開く場合、ゲーム側が店の画面を開いて会話を一時停止します。店を閉じてから、purchasedやcancelledなどの合意済みの結果を返します。結果の名前と引数はVNLEプロジェクトのゲームアクションで定義してください。open_shopはVNLEの組み込みコマンドではありません。
港の実際のエクスポートではgive_itemを使います。コマンドは定義を参照し、アイテムIDと2つの結果を持ちます。ゲームは鍵をインベントリに追加するか、受け取りを拒否します。どちらの結果にも、その後の進行先が作成者によって設定されています。
{
"arguments": {
"3cac6185-4404-4a2a-be5f-14afa0f97137": {
"kind": "literal",
"value": {
"entityType": "item",
"type": "reference",
"value": "2d71e57e-9b58-4fdb-a33f-42b89bd8bf04"
}
}
},
"commandRef": "0723beb0-daba-4602-a665-4efc27511bc3",
"flowRef": "1f69a7b6-1bdc-4dff-b8ee-9b2819b35168",
"id": "500c28a1-ed85-4c36-9a38-bedacce1c6b7",
"kind": "command",
"outcomes": {
"12bc86a9-b2eb-4eb4-836c-a9efd936bf1b": {
"continuation": {
"kind": "node",
"nodeRef": "382640aa-5b50-4715-9148-b470bc2f7cd6"
},
"resultBindings": {}
},
"1c527580-69a6-4e99-8503-1b0de3fc8e78": {
"continuation": {
"kind": "node",
"nodeRef": "221d6b0c-99e1-45a8-bc73-768c4ee679eb"
},
"resultBindings": {}
}
}
}{
"id": "0723beb0-daba-4602-a665-4efc27511bc3",
"key": "give_item",
"name": "Give item",
"category": "custom",
"inputs": [
{
"id": "3cac6185-4404-4a2a-be5f-14afa0f97137",
"name": "Item",
"valueType": {
"entityType": "item",
"kind": "reference"
}
}
],
"outcomes": [
{
"id": "12bc86a9-b2eb-4eb4-836c-a9efd936bf1b",
"name": "Received",
"results": []
},
{
"id": "1c527580-69a6-4e99-8503-1b0de3fc8e78",
"name": "Declined",
"results": []
}
]
}- commandRefをexecution.commandsで調べてシグネチャを取得し、content.commandDefinitionsで読みやすいキーを取得します。
- 入力IDを使って引数を評価します。省略可能な引数がなければ、定義されたdefaultValueを使います。
- 明示的に対応しているゲームアクションを一度だけ要求します。結果を待つ間は、Continueや二重実行を許可しないでください。
- 結果IDと型付きの戻り値を検証します。outcome.resultBindingsに従って値を物語の変数へ代入し、outcome.continuationへ進みます。
- 技術的な失敗を、自動的に作成者のキャンセル結果として扱ってはいけません。エラーを表示し、ゲーム側で再試行やキャンセルの手順を用意します。
このプロジェクトでは、鍵を受け取れないと5ゴールドを返金します。これはエクスポート内の進行先による動作で、すべてのCommandに自動で付く処理ではありません。インベントリ、クエストログ、マップはゲーム側の仕組みです。JSONの項目だけでは作られません。
プレースホルダー:「残りは5ゴールドです」
テキストとプレースホルダー
{
"continuation": {
"kind": "node",
"nodeRef": "2e1710dc-c32f-418f-abca-e055d6fd28f3"
},
"flowRef": "1f69a7b6-1bdc-4dff-b8ee-9b2819b35168",
"id": "0d7f959e-8fba-4882-ae98-b66d8416d4d2",
"kind": "dialogue",
"speakerRef": "807b2957-7bab-4a28-83a4-1f3d1589bbc2",
"text": {
"bindings": {
"1084cf8e-515e-4b33-92f8-b5c260e88a9e": {
"kind": "variable",
"variableRef": "a547610e-661f-4b8d-a25b-21d042f1a2d5"
}
},
"textRef": "fcc775ff-558e-4a41-823d-183aaff03e4b"
}
}kind: textのセグメントはそのまま連結します。kind: placeholderではplaceholderRefを使い、テキスト項目内の定義とnode.text.bindings内のバインディングを探します。バインディングはリテラル値か変数値を提供します。セリフを表示する時点の値を保持し、型に合った形式で表示してください。型と書式はフィールドリファレンスで確認できます。
進捗、制限、エラー
現在位置、物語の変数、戻り先のスタック、応答待ちのゲームアクションをゲームの状態と一緒に保存します。言語は独立した設定です。storyBuildIdでセーブと実行データのバージョンを対応付けられます。互換性のない物語で進行先を推測してはいけません。
maxAutomaticTransitions、maxConditionDepth、maxConditionTerms、maxCallDepthで、自動進行と深い入れ子を制限します。参照先がない、whenEmpty: faultなのに回答がない、未知のコマンド、無効な型などの場合は、明確なエラーを表示します。UIの再描画で購入や変数の変更を繰り返してはいけません。
開発担当者やAIに実装を依頼する
実際のエクスポート、エンジンのバージョン、対象プラットフォーム、この進行ルール、エンジンのガイドを渡します。UI要素と既存のインベントリ・店舗機能を説明し、実際のJSONフィールドと公式APIに基づく実装を依頼します。以下のケースもテストしてください。
- 10ゴールドなら鍵を受け取り、残りは5。2ゴールドなら鍵を受け取れず、所持金も減らないこと。
- インベントリが受け取りを拒否したら、物語の指定どおり返金すること。成功したものとして扱わないこと。
- Call/Returnで意図したノードへ戻り、Call内のEndでは会話全体が終了すること。
- 言語を変えても進行状況が保たれ、未翻訳や意図的に空にした文章も正しく扱えること。
- 同梱したJSON・画像・フォントが開発フォルダーの外でも使えること。画像がないときに前のポートレートが残らないこと。