コードフレームワーク
クイックスタート:最初のコードフレームワーク
キャラクター 1 体、アクション 2 つ、シート 1 つの小さなゲームを書きます。その後、検証して The Forge でキャラクターを作成します。
始める前に
- ゲームを作成するには有料プランが必要です。
- The Forge で非公開のゲームを作成します。「自分のゲームを作成」を選び、続けて「手動エディター」を選んで、ゲームを作成してください。
- 好きなエディタでファイルを書いてからソースエディタに貼り付けるか、ソースエディタに直接入力します。
ゲーム
Lantern Keepers には Keeper というキャラクターが 1 体います。Keeper は名前、最大 HP、現在の HP を持ちます。HP を変えるアクションは「ダメージを受ける」と「回復」の 2 つです。回復しても最大値を超えません。
ステップ 1:マニフェスト
マニフェストには、プロジェクト名と、すべての Framework とシナリオのファイルが一覧されています。エディタがゲーム用に作成した projectId はそのまま残してください。この例の値は仮のものです。
{
"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"],
"scenarios": ["scenarios/new-keeper.json", "scenarios/heal.json"]
}- 3 行目 エディタがゲーム用に作成した projectId はそのまま残してください。この値は仮のものです。
- 4-5 行目 GAME は、プレイできるキャラクターを持つ完全なゲームです。プロファイルは native-game-v1 だけです。
- 7 行目 プレイヤーが最初に作成するフレームワークです。
- 8-9 行目 すべてのフレームワークファイルとシナリオファイルがここに並びます。
ステップ 2:フレームワーク
フレームワークファイルは、プレイヤーが入力する内容(入力)、Keeper ができること(アクション)、どのフレームで描画するかを宣言します。コンストラクタと各アクションは、ルールファイルを指します。
{
"id": "keeper",
"name": "Keeper",
"elementType": "character",
"constructor": "rules/keeper.ts",
"inputs": {
"name": { "label": "Name" },
"max_hp": { "label": "Maximum HP", "defaultValue": 10 }
},
"actions": [
{ "id": "hurt", "name": "Take damage", "source": "rules/hurt.ts" },
{ "id": "heal", "name": "Heal", "source": "rules/heal.ts" }
],
"frames": [
{ "id": "sheet", "source": "layouts/keeper.json", "default": true }
]
}- 2-5 行目 フレームワークの正体です。id、表示名、作成するエレメントの種類、それを作るルールファイルを持ちます。
- 6-9 行目 プレイヤーが入力する内容です。各キーはコンストラクタの引数に対応します。
- 10-13 行目 アクションです。それぞれがルールファイルを指します。
- 14-16 行目 Keeper を描画するフレームです。既定のものはちょうど 1 つです。
ステップ 3:コンストラクタ
コンストラクタは、入力を初期値に変換します。戻り値の型には、保存されるすべてのフィールドが並びます。新しい Keeper は HP が満タンの状態で始まります。
export function construct(
name: string,
max_hp: number,
): { name: string; max_hp: number; hp: number } {
return { name: name, max_hp: max_hp, hp: max_hp };
}- 1-4 行目 入力と、コンストラクタが返すフィールドです。
- 5 行目 初期値です。新しい Keeper は HP が満タンの状態で始まります。
ステップ 4:アクション
アクションは、最初にエレメントを受け取り、次に自身の入力を受け取ります。値の変更はレジャーを通してのみ行います。「ダメージを受ける」はマイナスの量を加算します。入力を持つアクション(たとえば amount)のボタンを押すと、Fabled はダイアログで入力を尋ねます。
export function hurt(self: ElementRef, amount: number): void {
ledger.add(self, "hp", 0 - amount);
}- 2 行目 ledger.add は hp を指定した量だけ変えます。マイナスの数を加えると減ります。
「回復」は 2 つの値を読み取り、新しい HP を設定します。Keeper がすでに HP 満タンの場合は何もしないため、書き込みは発生しません。
export function heal(self: ElementRef, amount: number): void {
const hp: number = element.var(self, "hp") as number;
const max_hp: number = element.var(self, "max_hp") as number;
if (hp < max_hp) {
ledger.set(self, "hp", math.min(max_hp, hp + amount));
}
}- 2-3 行目 現在の HP と最大 HP を読み取ります。element.var は値を返し、as number で数値として扱います。
- 4 行目 Keeper がすでに HP 満タンなら何もしません。その場合は何も書き込みません。
- 5 行目 hp には、最大値と回復後の値のうち小さい方を設定します。
ステップ 5:フレーム
フレームはシートを描画します。テキストは {{element.attributes.name}} 形式のバインディングで値を表示します。各ボタンには、データ契約で宣言されたアクションのエイリアスを指定します。
{
"version": "1.0",
"metadata": {
"name": "Keeper sheet",
"bindingMode": "bound",
"frameworkRef": "keeper",
"context": "BLADE",
"template": "standard-blade"
},
"dataContract": {
"requiredBindings": [
{ "binding": "element.attributes.name", "expectedType": "string" },
{ "binding": "element.attributes.hp", "expectedType": "number" },
{ "binding": "element.attributes.max_hp", "expectedType": "number" }
],
"optionalBindings": [],
"actions": [
{ "name": "hurt", "functionRef": "hurt" },
{ "name": "heal", "functionRef": "heal" }
],
"regions": []
},
"layout": {
"kind": "container",
"children": [
{ "kind": "text", "text": "{{element.attributes.name}}" },
{
"kind": "text",
"text": "HP: {{element.attributes.hp}} / {{element.attributes.max_hp}}"
},
{ "kind": "button", "label": "Take damage", "actionRef": "hurt" },
{ "kind": "button", "label": "Heal", "actionRef": "heal" }
]
}
}- 3-9 行目 metadata はフレームに名前を付け、どのフレームワークのものかを示します。
- 10-22 行目 dataContract は、フレームが読むフィールドと、ボタンが呼べるアクションを一覧にします。
- 23-34 行目 layout は描画されるツリーです。テキストは二重の波かっこでフィールドを表示し、ボタンはアクションのエイリアスを指定します。
ステップ 6:シナリオ
シナリオは Keeper を作成し、その初期値を確認します。
{
"id": "new-keeper",
"framework": "keeper",
"parameters": { "name": "Mira", "max_hp": 10 },
"expectedValues": { "name": "Mira", "max_hp": 10, "hp": 10 }
}- 4 行目 Keeper を作るための入力です。
- 5 行目 コンストラクタが返す必要があるすべての値です。
このシナリオでは、ダメージを受け、最大値を超えて回復し、HP 満タンでもう一度回復します。各ステップには、アクションが行うべき書き込みを正確に並べます。最後のステップには何も並べません。
{
"id": "heal-after-damage",
"framework": "keeper",
"parameters": { "name": "Mira", "max_hp": 10 },
"expectedValues": { "name": "Mira", "max_hp": 10, "hp": 10 },
"actions": [
{
"action": "hurt",
"parameters": { "amount": 6 },
"expectedWrites": [{ "op": "ledger.add", "key": "hp", "value": -6 }]
},
{
"action": "heal",
"parameters": { "amount": 9 },
"expectedWrites": [{ "op": "ledger.set", "key": "hp", "value": 10 }]
},
{ "action": "heal", "parameters": { "amount": 3 }, "expectedWrites": [] }
]
}- 7-11 行目 ステップ 1:6 ダメージを受けます。アクションは、-6 の ledger.add をちょうど 1 回書き込む必要があります。
- 12-16 行目 ステップ 2:9 回復します。結果は最大値で止まるため、hp は 10 に設定されます。
- 17 行目 ステップ 3:HP 満タンでもう一度回復します。ガードが書き込みを省くため、書き込みは期待されません。
The Forge に入れる
- The Forge でゲームを開きます。
- 「ゲームプレイ」のステップで「詳細設定:ソースとバージョン」を選ぶと、ソースエディタが開きます。
- ゲームにまだプロジェクトがない場合は、「自分で書く」を選んで編集可能な例から始めます。AI を使う方法の「ゲームの作成を始める」は選ばないでください。例のファイルを上記のファイルに置き換えます。新しいパスには「ファイルを追加」を使い、不要なファイルには「ファイルを削除」を使ってください。エディタがマニフェストに入れた projectId はそのまま残してください。
- 「検証してプレビュー」を選びます。表示された問題をすべて修正してから、もう一度検証します。
- 「プロジェクトを保存」を選びます。
- 「ゲームを準備」を選びます。保存したバージョンから、非公開のフレームワーク、フレーム、コンテンツが作成されます。
- 「このゲームでキャラクターを作成」を選び、シート上で「ダメージを受ける」と「回復」を試します。
わざと壊してみる
「回復」が書き込むフィールドを hp から mana に変更します。コンパイラが停止し、問題を示します。
export function heal(self: ElementRef, amount: number): void {
const hp: number = element.var(self, "hp") as number;
const max_hp: number = element.var(self, "max_hp") as number;
if (hp < max_hp) {
ledger.set(self, "mana", math.min(max_hp, hp + amount));
}
}検証結果は次のとおりです。
Action "keeper.heal" calls ledger.set(self, "mana", ...), and "mana" is not declared on this schema. Declared scalar fields: hp, max_hp, name.名前を hp に戻すと、検証に通ります。ルールは、コンストラクタが返すフィールドにのみ書き込めます。

