メインコンテンツへスキップ

コードフレームワーク

クイックスタート:最初のコードフレームワーク

キャラクター 1 体、アクション 2 つ、シート 1 つの小さなゲームを書きます。その後、検証して The Forge でキャラクターを作成します。

始める前に

  • ゲームを作成するには有料プランが必要です。
  • The Forge で非公開のゲームを作成します。「自分のゲームを作成」を選び、続けて「手動エディター」を選んで、ゲームを作成してください。
  • 好きなエディタでファイルを書いてからソースエディタに貼り付けるか、ソースエディタに直接入力します。

ゲーム

Lantern Keepers には Keeper というキャラクターが 1 体います。Keeper は名前、最大 HP、現在の HP を持ちます。HP を変えるアクションは「ダメージを受ける」と「回復」の 2 つです。回復しても最大値を超えません。

ステップ 1:マニフェスト

マニフェストには、プロジェクト名と、すべての Framework とシナリオのファイルが一覧されています。エディタがゲーム用に作成した projectId はそのまま残してください。この例の値は仮のものです。

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"],
  "scenarios": ["scenarios/new-keeper.json", "scenarios/heal.json"]
}
  1. 3 行目 エディタがゲーム用に作成した projectId はそのまま残してください。この値は仮のものです。
  2. 4-5 行目 GAME は、プレイできるキャラクターを持つ完全なゲームです。プロファイルは native-game-v1 だけです。
  3. 7 行目 プレイヤーが最初に作成するフレームワークです。
  4. 8-9 行目 すべてのフレームワークファイルとシナリオファイルがここに並びます。

ステップ 2:フレームワーク

フレームワークファイルは、プレイヤーが入力する内容(入力)、Keeper ができること(アクション)、どのフレームで描画するかを宣言します。コンストラクタと各アクションは、ルールファイルを指します。

schemas/keeper.json
{
  "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 }
  ]
}
  1. 2-5 行目 フレームワークの正体です。id、表示名、作成するエレメントの種類、それを作るルールファイルを持ちます。
  2. 6-9 行目 プレイヤーが入力する内容です。各キーはコンストラクタの引数に対応します。
  3. 10-13 行目 アクションです。それぞれがルールファイルを指します。
  4. 14-16 行目 Keeper を描画するフレームです。既定のものはちょうど 1 つです。

ステップ 3:コンストラクタ

コンストラクタは、入力を初期値に変換します。戻り値の型には、保存されるすべてのフィールドが並びます。新しい Keeper は HP が満タンの状態で始まります。

rules/keeper.ts
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. 1-4 行目 入力と、コンストラクタが返すフィールドです。
  2. 5 行目 初期値です。新しい Keeper は HP が満タンの状態で始まります。

ステップ 4:アクション

アクションは、最初にエレメントを受け取り、次に自身の入力を受け取ります。値の変更はレジャーを通してのみ行います。「ダメージを受ける」はマイナスの量を加算します。入力を持つアクション(たとえば amount)のボタンを押すと、Fabled はダイアログで入力を尋ねます。

rules/hurt.ts
export function hurt(self: ElementRef, amount: number): void {
  ledger.add(self, "hp", 0 - amount);
}
  1. 2 行目 ledger.add は hp を指定した量だけ変えます。マイナスの数を加えると減ります。

「回復」は 2 つの値を読み取り、新しい HP を設定します。Keeper がすでに HP 満タンの場合は何もしないため、書き込みは発生しません。

rules/heal.ts
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));
  }
}
  1. 2-3 行目 現在の HP と最大 HP を読み取ります。element.var は値を返し、as number で数値として扱います。
  2. 4 行目 Keeper がすでに HP 満タンなら何もしません。その場合は何も書き込みません。
  3. 5 行目 hp には、最大値と回復後の値のうち小さい方を設定します。

ステップ 5:フレーム

フレームはシートを描画します。テキストは {{element.attributes.name}} 形式のバインディングで値を表示します。各ボタンには、データ契約で宣言されたアクションのエイリアスを指定します。

layouts/keeper.json
{
  "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" }
    ]
  }
}
  1. 3-9 行目 metadata はフレームに名前を付け、どのフレームワークのものかを示します。
  2. 10-22 行目 dataContract は、フレームが読むフィールドと、ボタンが呼べるアクションを一覧にします。
  3. 23-34 行目 layout は描画されるツリーです。テキストは二重の波かっこでフィールドを表示し、ボタンはアクションのエイリアスを指定します。

ステップ 6:シナリオ

シナリオは Keeper を作成し、その初期値を確認します。

scenarios/new-keeper.json
{
  "id": "new-keeper",
  "framework": "keeper",
  "parameters": { "name": "Mira", "max_hp": 10 },
  "expectedValues": { "name": "Mira", "max_hp": 10, "hp": 10 }
}
  1. 4 行目 Keeper を作るための入力です。
  2. 5 行目 コンストラクタが返す必要があるすべての値です。

このシナリオでは、ダメージを受け、最大値を超えて回復し、HP 満タンでもう一度回復します。各ステップには、アクションが行うべき書き込みを正確に並べます。最後のステップには何も並べません。

scenarios/heal.json
{
  "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": [] }
  ]
}
  1. 7-11 行目 ステップ 1:6 ダメージを受けます。アクションは、-6 の ledger.add をちょうど 1 回書き込む必要があります。
  2. 12-16 行目 ステップ 2:9 回復します。結果は最大値で止まるため、hp は 10 に設定されます。
  3. 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 に戻すと、検証に通ります。ルールは、コンストラクタが返すフィールドにのみ書き込めます。

次へ