コードフレームワーク
Lantern を本格的なゲームに育てる
クイックスタートのゲームに、選択肢、派生値、子フレームワーク、リストを加え、各ステップをシナリオで確かめます。
始める前に
クイックスタートで作ったプロジェクトから始めます。各ステップでは、作成または変更するファイルをすべて示します。抜粋は変わる部分を示し、展開するとファイル全体を見られます。ステップごとに「検証してプレビュー」を選びます。
4 つのステップ
- 選択肢を加える
2 つの options を持つ Calling の入力を加えます。
schemas/keeper.json"calling": { "label": "Calling", "options": ["Guard", "Scout"], "defaultValue": "Guard" }- 11 行目 固定の選択肢の一覧です。default はその中のどれかにします。
コンストラクタは新しい入力を受け取り、保存フィールドとして返します。
rules/keeper.tsexport function construct( name: string, max_hp: number, calling: string, ): { name: string; max_hp: number; hp: number; calling: string } { return { name: name, max_hp: max_hp, hp: max_hp, calling: calling }; }- 4 行目 新しい入力です。
- 5-6 行目 calling は保存フィールドになりました。
両方のシナリオに、parameters と expectedValues で新しい値が必要です。まず new-keeper.json から始めます。
scenarios/new-keeper.json{ "id": "new-keeper", "framework": "keeper", "parameters": { "name": "Mira", "max_hp": 10, "calling": "Guard" }, "expectedValues": { "name": "Mira", "max_hp": 10, "hp": 10, "calling": "Guard" } }- 4 行目 渡す入力です。
- 9 行目 期待する保存値です。
次に、heal.json にも同じ 2 つの変更を加えます。
scenarios/heal.json"parameters": { "name": "Mira", "max_hp": 10, "calling": "Guard" }, "expectedValues": { "name": "Mira", "max_hp": 10, "hp": 10, "calling": "Guard" },- 4 行目 同じ入力です。
- 9 行目 同じ保存値です。
「検証してプレビュー」を選びます。次へ進む前に、検証が通るようにしてください。
プロジェクトに、次のファイルができました。上の抜粋を展開すると、ファイル全体を見られます。
manifest/project.json schemas/keeper.json rules/heal.ts rules/hurt.ts rules/keeper.ts layouts/keeper.json scenarios/heal.json scenarios/new-keeper.json - 値を派生させる
レジャー関数が calling から Guard を導きます。スキーマで宣言します。
schemas/keeper.json"ledgerFunctions": [ { "id": "guard", "name": "Guard", "field": "guard", "source": "rules/guard.ts" } ]- 24-27 行目 id と name は関数を示し、field は関数が埋める保存値、source はそのルールファイルです。
rules/guard.ts を作成します。
rules/guard.tsfunction base_guard(calling: string): number { return calling === "Guard" ? 3 : 1; } export function guard(currentValue: number, self: ElementRef): number { const calling: string = element.var(self, "calling") as string; return base_guard(calling); }- 1-3 行目 export のないヘルパーです。
- 5-7 行目 レジャー関数は calling を読み、派生値を返します。
コンストラクタは、開始値 0 の guard フィールドも返します。実際の値は、レジャー関数が入れます。
rules/keeper.tsexport function construct( name: string, max_hp: number, calling: string, ): { name: string; max_hp: number; hp: number; calling: string; guard: number } { return { name: name, max_hp: max_hp, hp: max_hp, calling: calling, guard: 0 }; }- 5-6 行目 guard は戻り値に含まれ、0 から始まります。
シナリオが関数を確認します。new-keeper.json の期待値に guard を加え、関数を呼び出す ledger の行も加えます。
scenarios/new-keeper.json{ "id": "new-keeper", "framework": "keeper", "parameters": { "name": "Mira", "max_hp": 10, "calling": "Guard" }, "expectedValues": { "name": "Mira", "max_hp": 10, "hp": 10, "calling": "Guard", "guard": 3 }, "ledger": [{ "function": "guard", "currentValue": 0, "expected": 3 }] }- 10 行目 レジャー関数が実行されたあとの、派生値です。
- 12 行目 現在値 0 で guard 関数を呼び出し、3 を期待します。
同じ期待値を heal.json にも加えます。
scenarios/heal.json"parameters": { "name": "Mira", "max_hp": 10, "calling": "Guard" }, "expectedValues": { "name": "Mira", "max_hp": 10, "hp": 10, "calling": "Guard", "guard": 3 },- 10 行目 このシナリオでも、派生値は 3 のままです。
Guard をシートに表示するには、requiredBindings に載せ、それを使うテキストノードを加えます。
layouts/keeper.json{ "binding": "element.attributes.guard", "expectedType": "number" }- 15 行目 新しいフィールドのバインディングです。
layouts/keeper.json{ "kind": "text", "text": "Guard: {{element.attributes.guard}}" },- 32 行目 値を表示するテキストノードです。
「検証してプレビュー」を選びます。次へ進む前に、検証が通るようにしてください。
プロジェクトに、次のファイルができました。上の抜粋を展開すると、ファイル全体を見られます。
manifest/project.json schemas/keeper.json rules/guard.ts rules/heal.ts rules/hurt.ts rules/keeper.ts layouts/keeper.json scenarios/heal.json scenarios/new-keeper.json - 子フレームワークを加える
Keeper は Lamp を持ちます。Lamp は 2 つ目のフレームワークで、独自のスキーマ、コンストラクタ、フレーム、シナリオを持ちます。そのスキーマとシナリオをマニフェストに載せます。
manifest/project.json{ "formatVersion": "fabled.game-project/v1", "projectId": "game-your-game-id", "projectKind": "GAME", "compilerProfile": "native-game-v1", "name": "Lantern Keepers", "entryFramework": "keeper", "frameworks": ["schemas/keeper.json", "schemas/lamp.json"], "scenarios": [ "scenarios/new-keeper.json", "scenarios/heal.json", "scenarios/new-lamp.json" ] }- 8 行目 Lamp のスキーマが、Keeper のスキーマに加わります。
- 9-13 行目 Lamp のシナリオが、Keeper のシナリオに加わります。
Lamp のスキーマを作成します。
schemas/lamp.json{ "id": "lamp", "name": "Lamp", "elementType": "item", "constructor": "rules/lamp.ts", "inputs": { "name": { "label": "Name" }, "oil": { "label": "Oil", "defaultValue": 5 } }, "frames": [{ "id": "card", "source": "layouts/lamp.json", "default": true }] }- 6-9 行目 子の入力です。子にも name が必要です。
- 10 行目 子には独自のフレームがあります。
そのコンストラクタ、フレーム、シナリオを作成します。
rules/lamp.tsexport function construct(name: string, oil: number): { name: string; oil: number } { return { name: name, oil: oil }; }- 1 行目 コンストラクタは、入力を保存値として返します。
layouts/lamp.json{ "version": "1.0", "metadata": { "name": "Lamp card", "bindingMode": "bound", "frameworkRef": "lamp", "context": "BLADE", "template": "standard-blade" }, "dataContract": { "requiredBindings": [ { "binding": "element.attributes.name", "expectedType": "string" }, { "binding": "element.attributes.oil", "expectedType": "number" } ], "optionalBindings": [], "actions": [], "regions": [] }, "layout": { "kind": "container", "children": [ { "kind": "text", "text": "{{element.attributes.name}}" }, { "kind": "text", "text": "Oil: {{element.attributes.oil}}" } ] } }- 11-14 行目 フレームが表示するすべてのフィールドを、バインディングとして載せます。
scenarios/new-lamp.json{ "id": "new-lamp", "framework": "lamp", "parameters": { "name": "Lantern", "oil": 5 }, "expectedValues": { "name": "Lantern", "oil": 5 } }- 4 行目 Lamp を単独で作り、その値を確認します。
Keeper では、非表示の入力がフレームワーク参照を保持し、childFrameworks がスロットとフレームワークを対応付けます。
schemas/keeper.json"lamp": { "label": "Lamp", "defaultValue": "lamp", "hidden": true }- 14 行目 非表示の入力はウィザードに表示されません。
schemas/keeper.json"childFrameworks": { "lamp": "lamp" }- 31 行目 スロット lamp を、id が lamp のフレームワークに対応付けます。
コンストラクタは、自身の値と並べて Lamp の入力を返します。
rules/keeper.tsexport function construct( name: string, max_hp: number, calling: string, lamp: FrameworkRef, ): { values: { name: string; max_hp: number; hp: number; calling: string; guard: number }; children: { lamp: { framework: FrameworkRef; params: { name: string; oil: number } } }; } { return { values: { name: name, max_hp: max_hp, hp: max_hp, calling: calling, guard: 0 }, children: { lamp: { framework: lamp, params: { name: "Lantern", oil: 5 } } }, }; }- 5 行目 lamp は FrameworkRef です。入力のフレームワーク id から、プラットフォームがそれを作ります。
- 6-9 行目 戻り値の型は、保存値と子スロットを並べます。
- 12 行目 子スロットです。どのフレームワークを作るか、渡す入力は何かを示します。
Keeper のシナリオに lamp の入力を渡し、コンストラクタが作る Lamp を期待します。
scenarios/new-keeper.json{ "id": "new-keeper", "framework": "keeper", "parameters": { "name": "Mira", "max_hp": 10, "calling": "Guard", "lamp": "lamp" }, "expectedValues": { "name": "Mira", "max_hp": 10, "hp": 10, "calling": "Guard", "guard": 3 }, "ledger": [{ "function": "guard", "currentValue": 0, "expected": 3 }], "expectedChildren": { "lamp": { "name": "Lantern", "oil": 5 } } }- 8 行目 非表示の入力です。
- 18 行目 コンストラクタが作る子と、その値です。
scenarios/heal.json"parameters": { "name": "Mira", "max_hp": 10, "calling": "Guard", "lamp": "lamp" }, "expectedValues": { "name": "Mira", "max_hp": 10, "hp": 10, "calling": "Guard", "guard": 3 },- 8 行目 heal.json の非表示の入力です。
scenarios/heal.json"expectedChildren": { "lamp": { "name": "Lantern", "oil": 5 } }- 30 行目 heal.json で期待する子です。
「検証してプレビュー」を選びます。次へ進む前に、検証が通るようにしてください。
プロジェクトに、次のファイルができました。上の抜粋を展開すると、ファイル全体を見られます。
manifest/project.json schemas/keeper.json schemas/lamp.json rules/guard.ts rules/heal.ts rules/hurt.ts rules/keeper.ts rules/lamp.ts layouts/keeper.json layouts/lamp.json scenarios/heal.json scenarios/new-keeper.json scenarios/new-lamp.json - リストと拒否されるアクションを加える
リストは、キャラクターがプレイ中に得るコンテンツを保持します。Pack は Trinket を保持します。Trinket は 2 つ目のフレームワークで、そのプリセットがコンテンツです。そのスキーマと 2 つのシナリオをマニフェストに載せます。
manifest/project.json{ "formatVersion": "fabled.game-project/v1", "projectId": "game-your-game-id", "projectKind": "GAME", "compilerProfile": "native-game-v1", "name": "Lantern Keepers", "entryFramework": "keeper", "frameworks": [ "schemas/keeper.json", "schemas/lamp.json", "schemas/trinket.json" ], "scenarios": [ "scenarios/new-keeper.json", "scenarios/heal.json", "scenarios/new-lamp.json", "scenarios/new-trinket.json", "scenarios/pack.json" ] }- 8-12 行目 Trinket のスキーマが、ほかの 2 つに加わります。
- 17-18 行目 Trinket のシナリオと、pack のシナリオです。
Trinket のスキーマを作成します。そのフレームには、ゲーム内のほかのフレームが使っていない id が必要です。
schemas/trinket.json{ "id": "trinket", "name": "Trinket", "elementType": "item", "constructor": "rules/trinket.ts", "inputs": { "name": { "label": "Name" }, "weight": { "label": "Weight", "defaultValue": 1, "min": 0, "max": 5 } }, "frames": [ { "id": "trinket-card", "source": "layouts/trinket.json", "default": true } ], "content": [ { "id": "rope", "name": "Rope", "parameters": { "name": "Rope", "weight": 2 } }, { "id": "anvil", "name": "Anvil", "parameters": { "name": "Anvil", "weight": 5 } } ] }- 8 行目 上限と下限のある weight です。オファーがそれを読みます。
- 10-16 行目 Trinket には独自のフレームがあります。
- 17-28 行目 プレイヤーが詰められるプリセットです。シナリオが選ぶのは、その id です。
そのコンストラクタ、フレーム、シナリオを作成します。
rules/trinket.tsexport function construct( name: string, weight: number, ): { name: string; weight: number } { return { name: name, weight: weight }; }- 5 行目 2 つの保存値を返します。
layouts/trinket.json{ "version": "1.0", "metadata": { "name": "Trinket card", "bindingMode": "bound", "frameworkRef": "trinket", "context": "BLADE", "template": "standard-blade" }, "dataContract": { "requiredBindings": [ { "binding": "element.attributes.name", "expectedType": "string" }, { "binding": "element.attributes.weight", "expectedType": "number" } ], "optionalBindings": [], "actions": [], "regions": [] }, "layout": { "kind": "container", "children": [ { "kind": "text", "text": "{{element.attributes.name}}" }, { "kind": "text", "text": "Weight: {{element.attributes.weight}}" } ] } }- 22-23 行目 name と weight を表示します。
scenarios/new-trinket.json{ "id": "new-trinket", "framework": "trinket", "parameters": { "name": "Rope", "weight": 2 }, "expectedValues": { "name": "Rope", "weight": 2 } }- 4 行目 Trinket を単独で作り、その値を確認します。
Keeper のスキーマで、アクションとリストを宣言します。
schemas/keeper.json"actions": [ { "id": "hurt", "name": "Take damage", "source": "rules/hurt.ts" }, { "id": "heal", "name": "Heal", "source": "rules/heal.ts" }, { "id": "pack_trinket", "name": "Pack a trinket", "source": "rules/pack-trinket.ts" } ],- 19-23 行目 新しいアクションです。
schemas/keeper.json"lists": { "pack": { "label": "Pack", "of": "trinket", "max": 4 } }- 37 行目 Pack のリストです。ラベル、保持するコンテンツのフレームワーク、サイズを示します。
アクションのルールファイルを作成します。
rules/pack-trinket.tsexport function pack_trinket(self: ElementRef, trinket: ContentRef): void { list.add(self, "pack", trinket, offer.where({ weight: { lte: 3 } })); }- 1 行目 選ばれた trinket は ContentRef として届きます。
- 2 行目 オファーが、詰められるものを決めます。重さが 3 以下の trinket だけです。
オファーは拒否できるので、両方の結果をテストします。scenarios/pack.json を作成します。
scenarios/pack.json{ "id": "pack-trinkets", "framework": "keeper", "parameters": { "name": "Mira", "max_hp": 10, "calling": "Guard", "lamp": "lamp" }, "expectedValues": { "name": "Mira", "max_hp": 10, "hp": 10, "calling": "Guard", "guard": 3 }, "expectedChildren": { "lamp": { "name": "Lantern", "oil": 5 } }, "actions": [ { "action": "pack_trinket", "parameters": {}, "choose": { "trinket": "rope" }, "expectedWrites": [{ "op": "list.add", "key": "pack", "value": "rope" }] }, { "action": "pack_trinket", "parameters": {}, "choose": { "trinket": "anvil" }, "expectedRefusal": "NOT_OFFERED", "expectedWrites": [] } ], "expectedLists": { "pack": ["rope"] } }- 19-24 行目 受け入れ: ロープは十分軽いので、追加されます。
- 25-31 行目 拒否: 金床は重すぎます。このステップは、拒否 NOT_OFFERED と書き込みなしを期待します。
- 33 行目 最後のステップのあと、Pack にはロープだけが入っています。
リストを表示するフレームは version 2.0 を使います。version を変更し、dataContract でアクションとリージョンを宣言して、ボタンとリージョンを描画します。
layouts/keeper.json"version": "2.0",- 2 行目 リストのあるフレームは version 2.0 を使います。
layouts/keeper.json"actions": [ { "name": "hurt", "functionRef": "hurt" }, { "name": "heal", "functionRef": "heal" }, { "name": "pack_trinket", "functionRef": "pack_trinket" } ], "regions": [{ "name": "pack", "listBinding": "element.lists.pack" }]- 21 行目 新しいアクションのエイリアスです。
- 23 行目 リストのリージョンを宣言します。
layouts/keeper.json{ "kind": "button", "label": "Pack a trinket", "actionRef": "pack_trinket" }, { "kind": "region", "id": "pack_list", "listBinding": "{{element.lists.pack}}", "emptyText": "Your pack is empty." }- 37-39 行目 アクションを実行するボタンです。
- 42 行目 リージョンはリストを描きます。
- 44 行目 描画するリストです。
- 45 行目 リストが空の間に表示されます。
「検証してプレビュー」を選びます。次へ進む前に、検証が通るようにしてください。
プロジェクトに、次のファイルができました。上の抜粋を展開すると、ファイル全体を見られます。
manifest/project.json schemas/keeper.json schemas/lamp.json schemas/trinket.json rules/guard.ts rules/heal.ts rules/hurt.ts rules/keeper.ts rules/lamp.ts rules/pack-trinket.ts rules/trinket.ts layouts/keeper.json layouts/lamp.json layouts/trinket.json scenarios/heal.json scenarios/new-keeper.json scenarios/new-lamp.json scenarios/new-trinket.json scenarios/pack.json
作ったもの
Keeper に、選択肢、派生値、子フレームワーク、拒否されるアクション付きのリスト、そしてそれぞれを確かめるシナリオができました。レシピでは、より小さな変更を単独で紹介します。

