Zum Hauptinhalt springen

Code-Frameworks

Lantern zu einem echten Spiel ausbauen

Füge dem Schnellstart-Spiel eine Auswahl, einen abgeleiteten Wert, ein untergeordnetes Framework und eine Liste hinzu und beweise jeden Schritt mit einem Szenario.

Bevor du anfängst

Starte mit dem Projekt, das du im Schnellstart gebaut hast. Jeder Schritt nennt jede Datei, die du erstellst oder änderst. Ein Auszug zeigt den Teil, der sich ändert. Du kannst ihn aufklappen, um die ganze Datei zu sehen. Wähle nach jedem Schritt Prüfen und Vorschau.

Vier Schritte

  1. Eine Auswahl hinzufügen

    Füge eine Eingabe Calling mit zwei Optionen hinzu.

    schemas/keeper.json
        "calling": {
          "label": "Calling",
          "options": ["Guard", "Scout"],
          "defaultValue": "Guard"
        }
    1. Zeile 11 Eine feste Liste von Auswahlmöglichkeiten. Der Standardwert muss eine davon sein.

    Der Konstruktor erhält die neue Eingabe und gibt sie als gespeichertes Feld zurück.

    rules/keeper.ts
    export 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 };
    }
    1. Zeile 4 Die neue Eingabe.
    2. Zeilen 5-6 calling ist jetzt ein gespeichertes Feld.

    Beide Szenarien brauchen den neuen Wert, in parameters und in expectedValues. Beginne mit 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"
      }
    }
    1. Zeile 4 Die Eingabe, die du gibst.
    2. Zeile 9 Der gespeicherte Wert, den du erwartest.

    Mache dann dieselben zwei Änderungen in heal.json.

    scenarios/heal.json
      "parameters": { "name": "Mira", "max_hp": 10, "calling": "Guard" },
      "expectedValues": {
        "name": "Mira",
        "max_hp": 10,
        "hp": 10,
        "calling": "Guard"
      },
    1. Zeile 4 Dieselbe Eingabe.
    2. Zeile 9 Derselbe gespeicherte Wert.

    Wähle Prüfen und Vorschau. Die Prüfung sollte bestehen, bevor du weitermachst.

    Das Projekt enthält jetzt diese Dateien. Klappe einen Auszug oben auf, um die ganze Datei zu sehen.

    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
  2. Einen Wert ableiten

    Eine Ledger-Funktion leitet Guard aus dem calling ab. Deklariere sie im Schema.

    schemas/keeper.json
      "ledgerFunctions": [
        {
          "id": "guard",
          "name": "Guard",
          "field": "guard",
          "source": "rules/guard.ts"
        }
      ]
    1. Zeilen 24-27 id und name bezeichnen die Funktion, field ist der gespeicherte Wert, den sie füllt, und source ist ihre Regeldatei.

    Erstelle rules/guard.ts.

    rules/guard.ts
    function 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. Zeilen 1-3 Eine Hilfsfunktion ohne export.
    2. Zeilen 5-7 Die Ledger-Funktion liest das calling und gibt den abgeleiteten Wert zurück.

    Der Konstruktor gibt außerdem ein Feld guard mit dem Startwert 0 zurück. Die Ledger-Funktion trägt den echten Wert ein.

    rules/keeper.ts
    export 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 };
    }
    1. Zeilen 5-6 guard gehört zum Rückgabewert und startet bei 0.

    Ein Szenario prüft die Funktion. Füge guard zu den erwarteten Werten von new-keeper.json hinzu, dazu eine ledger-Zeile, die die Funktion aufruft.

    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 }]
    }
    1. Zeile 10 Der abgeleitete Wert, nachdem die Ledger-Funktion lief.
    2. Zeile 12 Ruft die Funktion guard mit dem aktuellen Wert 0 auf und erwartet 3.

    Füge denselben erwarteten Wert zu heal.json hinzu.

    scenarios/heal.json
      "parameters": { "name": "Mira", "max_hp": 10, "calling": "Guard" },
      "expectedValues": {
        "name": "Mira",
        "max_hp": 10,
        "hp": 10,
        "calling": "Guard",
        "guard": 3
      },
    1. Zeile 10 Der abgeleitete Wert bleibt auch in diesem Szenario 3.

    Um Guard auf dem Bogen zu zeigen, liste es in requiredBindings auf und füge einen Textknoten hinzu, der es nutzt.

    layouts/keeper.json
          { "binding": "element.attributes.guard", "expectedType": "number" }
    1. Zeile 15 Die Bindung für das neue Feld.
    layouts/keeper.json
          { "kind": "text", "text": "Guard: {{element.attributes.guard}}" },
    1. Zeile 32 Ein Textknoten, der den Wert zeigt.

    Wähle Prüfen und Vorschau. Die Prüfung sollte bestehen, bevor du weitermachst.

    Das Projekt enthält jetzt diese Dateien. Klappe einen Auszug oben auf, um die ganze Datei zu sehen.

    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
  3. Ein untergeordnetes Framework hinzufügen

    Ein Keeper trägt eine Lamp. Die Lamp ist ein zweites Framework mit eigenem Schema, Konstruktor, Frame und Szenario. Liste ihr Schema und ihr Szenario im Manifest auf.

    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"
      ]
    }
    1. Zeile 8 Das Lamp-Schema kommt zum Keeper-Schema dazu.
    2. Zeilen 9-13 Das Lamp-Szenario kommt zu den Keeper-Szenarien dazu.

    Erstelle das Schema der 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 }]
    }
    1. Zeilen 6-9 Die Eingaben des untergeordneten Elements. Auch ein untergeordnetes Element braucht einen Namen.
    2. Zeile 10 Das untergeordnete Element hat einen eigenen Frame.

    Erstelle ihren Konstruktor, ihren Frame und ihr Szenario.

    rules/lamp.ts
    export function construct(name: string, oil: number): { name: string; oil: number } {
      return { name: name, oil: oil };
    }
    1. Zeile 1 Der Konstruktor gibt die Eingaben als gespeicherte Werte zurück.
    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}}" }
        ]
      }
    }
    1. Zeilen 11-14 Jedes Feld, das der Frame zeigt, steht als Bindung in der Liste.
    scenarios/new-lamp.json
    {
      "id": "new-lamp",
      "framework": "lamp",
      "parameters": { "name": "Lantern", "oil": 5 },
      "expectedValues": { "name": "Lantern", "oil": 5 }
    }
    1. Zeile 4 Baut eine Lamp für sich allein und prüft ihre Werte.

    Im Keeper hält eine versteckte Eingabe die Framework-Referenz, und childFrameworks ordnet den Platz dem Framework zu.

    schemas/keeper.json
        "lamp": { "label": "Lamp", "defaultValue": "lamp", "hidden": true }
    1. Zeile 14 Eine versteckte Eingabe erscheint nie im Assistenten.
    schemas/keeper.json
      "childFrameworks": { "lamp": "lamp" }
    1. Zeile 31 Ordnet den Platz lamp dem Framework mit der ID lamp zu.

    Der Konstruktor gibt die Eingaben der Lamp neben seinen eigenen Werten zurück.

    rules/keeper.ts
    export 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 } } },
      };
    }
    1. Zeile 5 lamp ist eine FrameworkRef. Die Plattform macht aus der Framework-ID der Eingabe eine solche.
    2. Zeilen 6-9 Der Rückgabetyp listet die gespeicherten Werte und die untergeordneten Plätze auf.
    3. Zeile 12 Der untergeordnete Platz: welches Framework gebaut wird und welche Eingaben es bekommt.

    Gib den Keeper-Szenarien die Eingabe lamp und erwarte die Lamp, die der Konstruktor baut.

    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 } }
    }
    1. Zeile 8 Die versteckte Eingabe.
    2. Zeile 18 Das untergeordnete Element, das der Konstruktor baut, mit seinen Werten.
    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
      },
    1. Zeile 8 Die versteckte Eingabe in heal.json.
    scenarios/heal.json
      "expectedChildren": { "lamp": { "name": "Lantern", "oil": 5 } }
    1. Zeile 30 Das erwartete untergeordnete Element in heal.json.

    Wähle Prüfen und Vorschau. Die Prüfung sollte bestehen, bevor du weitermachst.

    Das Projekt enthält jetzt diese Dateien. Klappe einen Auszug oben auf, um die ganze Datei zu sehen.

    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
  4. Eine Liste und eine abgelehnte Aktion hinzufügen

    Eine Liste hält Inhalte, die ein Charakter im Spiel erhält. Das Pack hält Trinkets, ein zweites Framework, dessen Presets die Inhalte sind. Liste sein Schema und seine zwei Szenarien im Manifest auf.

    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"
      ]
    }
    1. Zeilen 8-12 Das Trinket-Schema kommt zu den anderen beiden dazu.
    2. Zeilen 17-18 Das Trinket-Szenario und das Pack-Szenario.

    Erstelle das Trinket-Schema. Sein Frame braucht eine ID, die kein anderer Frame im Spiel nutzt.

    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 }
        }
      ]
    }
    1. Zeile 8 Ein Gewicht mit Grenzen. Das Angebot liest es.
    2. Zeilen 10-16 Das Trinket hat einen eigenen Frame.
    3. Zeilen 17-28 Die Presets, die ein Spieler einpacken kann. Ein Szenario wählt sie über ihre IDs.

    Erstelle ihren Konstruktor, ihren Frame und ihr Szenario.

    rules/trinket.ts
    export function construct(
      name: string,
      weight: number,
    ): { name: string; weight: number } {
      return { name: name, weight: weight };
    }
    1. Zeile 5 Gibt die zwei gespeicherten Werte zurück.
    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}}" }
        ]
      }
    }
    1. Zeilen 22-23 Zeigt den Namen und das Gewicht.
    scenarios/new-trinket.json
    {
      "id": "new-trinket",
      "framework": "trinket",
      "parameters": { "name": "Rope", "weight": 2 },
      "expectedValues": { "name": "Rope", "weight": 2 }
    }
    1. Zeile 4 Baut ein Trinket für sich allein und prüft seine Werte.

    Deklariere die Aktion und die Liste im Keeper-Schema.

    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"
        }
      ],
    1. Zeilen 19-23 Die neue Aktion.
    schemas/keeper.json
      "lists": { "pack": { "label": "Pack", "of": "trinket", "max": 4 } }
    1. Zeile 37 Die Liste Pack: ihre Beschriftung, das Inhalts-Framework, das sie hält, und ihre Größe.

    Erstelle die Regeldatei der Aktion.

    rules/pack-trinket.ts
    export function pack_trinket(self: ElementRef, trinket: ContentRef): void {
      list.add(self, "pack", trinket, offer.where({ weight: { lte: 3 } }));
    }
    1. Zeile 1 Das gewählte Trinket kommt als ContentRef an.
    2. Zeile 2 Das Angebot entscheidet, was eingepackt werden darf. Nur Trinkets, die 3 oder weniger wiegen.

    Ein Angebot kann ablehnen, also teste beide Ergebnisse. Erstelle 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"] }
    }
    1. Zeilen 19-24 Angenommen: Das Seil ist leicht genug, also wird es hinzugefügt.
    2. Zeilen 25-31 Abgelehnt: Der Amboss ist zu schwer. Der Schritt erwartet die Ablehnung NOT_OFFERED und keine Schreibvorgänge.
    3. Zeile 33 Nach dem letzten Schritt enthält das Pack nur das Seil.

    Ein Frame, der eine Liste zeigt, nutzt version 2.0. Ändere die Version, deklariere die Aktion und eine Region im dataContract und zeichne die Schaltfläche und die Region.

    layouts/keeper.json
      "version": "2.0",
    1. Zeile 2 Frames mit einer Liste nutzen 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" }]
    1. Zeile 21 Der Alias für die neue Aktion.
    2. Zeile 23 Deklariert die Listenregion.
    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."
          }
    1. Zeilen 37-39 Die Schaltfläche, die die Aktion ausführt.
    2. Zeile 42 Eine Region zeichnet eine Liste.
    3. Zeile 44 Die Liste, die gezeichnet wird.
    4. Zeile 45 Wird gezeigt, solange die Liste leer ist.

    Wähle Prüfen und Vorschau. Die Prüfung sollte bestehen, bevor du weitermachst.

    Das Projekt enthält jetzt diese Dateien. Klappe einen Auszug oben auf, um die ganze Datei zu sehen.

    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

Was du gebaut hast

Dein Keeper hat jetzt eine Auswahl, einen abgeleiteten Wert, ein untergeordnetes Framework, eine Liste mit einer abgelehnten Aktion und Szenarien, die jedes davon beweisen. Die Rezepte zeigen kleinere Änderungen einzeln.