Saltar al contenido principal

Code frameworks

Haz crecer Lantern hasta un juego de verdad

Añade una elección, un valor derivado, un Framework hijo y una lista al juego del inicio rápido, y prueba cada paso con un escenario.

Antes de empezar

Empieza con el proyecto que creaste en el inicio rápido. Cada paso nombra cada archivo que debes crear o cambiar. Un extracto muestra la parte que cambia, y puedes expandirlo para ver el archivo completo. Después de cada paso, elige Comprobar y previsualizar.

Cuatro pasos

  1. Añade una elección

    Añade una entrada Calling con dos opciones.

    schemas/keeper.json
        "calling": {
          "label": "Calling",
          "options": ["Guard", "Scout"],
          "defaultValue": "Guard"
        }
    1. Línea 11 Una lista fija de opciones. El valor por defecto debe ser una de ellas.

    El constructor recibe la entrada nueva y la devuelve como un campo guardado.

    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. Línea 4 La nueva entrada.
    2. Líneas 5-6 calling ahora es un campo guardado.

    Los dos escenarios necesitan el valor nuevo, en parameters y en expectedValues. Empieza con 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. Línea 4 La entrada que das.
    2. Línea 9 El valor guardado que esperas.

    Luego haz los mismos dos cambios en heal.json.

    scenarios/heal.json
      "parameters": { "name": "Mira", "max_hp": 10, "calling": "Guard" },
      "expectedValues": {
        "name": "Mira",
        "max_hp": 10,
        "hp": 10,
        "calling": "Guard"
      },
    1. Línea 4 La misma entrada.
    2. Línea 9 El mismo valor guardado.

    Elige Comprobar y previsualizar. Debe pasar antes de seguir.

    Ahora el proyecto tiene estos archivos. Expande cualquier extracto de arriba para ver su archivo completo.

    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. Deriva un valor

    Una función del ledger deriva Guard a partir de calling. Decláralo en el esquema.

    schemas/keeper.json
      "ledgerFunctions": [
        {
          "id": "guard",
          "name": "Guard",
          "field": "guard",
          "source": "rules/guard.ts"
        }
      ]
    1. Líneas 24-27 id y name identifican la función, field es el valor guardado que rellena y source es su archivo de reglas.

    Crea 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. Líneas 1-3 Una función auxiliar sin export.
    2. Líneas 5-7 La función del ledger lee calling y devuelve el valor derivado.

    El constructor también devuelve un campo guard, con un valor inicial de 0. La función del ledger rellena el valor real.

    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. Líneas 5-6 guard es parte del valor de retorno y empieza en 0.

    Un escenario comprueba la función. Añade guard a los valores esperados de new-keeper.json, y una línea de ledger que llame a la función.

    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. Línea 10 El valor derivado, después de que se ejecutó la función del ledger.
    2. Línea 12 Llama a la función guard con el valor actual 0 y espera 3.

    Añade el mismo valor esperado a 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
      },
    1. Línea 10 El valor derivado también se queda en 3 en este escenario.

    Para mostrar Guard en la hoja, añádelo a requiredBindings y agrega un nodo de texto que lo use.

    layouts/keeper.json
          { "binding": "element.attributes.guard", "expectedType": "number" }
    1. Línea 15 El enlace del campo nuevo.
    layouts/keeper.json
          { "kind": "text", "text": "Guard: {{element.attributes.guard}}" },
    1. Línea 32 Un nodo de texto que muestra el valor.

    Elige Comprobar y previsualizar. Debe pasar antes de seguir.

    Ahora el proyecto tiene estos archivos. Expande cualquier extracto de arriba para ver su archivo completo.

    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. Añade un Framework hijo

    Un Keeper lleva una Lamp. La Lamp es un segundo Framework con su propio esquema, constructor, Frame y escenario. Enumera su esquema y su escenario en el manifiesto.

    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. Línea 8 El esquema de la Lamp se suma al esquema del Keeper.
    2. Líneas 9-13 El escenario de la Lamp se suma a los escenarios del Keeper.

    Crea el esquema de la 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. Líneas 6-9 Las entradas del hijo. Un hijo también necesita un nombre.
    2. Línea 10 El hijo tiene su propio Frame.

    Crea su constructor, su Frame y su escenario.

    rules/lamp.ts
    export function construct(name: string, oil: number): { name: string; oil: number } {
      return { name: name, oil: oil };
    }
    1. Línea 1 El constructor devuelve las entradas como valores guardados.
    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. Líneas 11-14 Cada campo que muestra el Frame se enumera como un enlace.
    scenarios/new-lamp.json
    {
      "id": "new-lamp",
      "framework": "lamp",
      "parameters": { "name": "Lantern", "oil": 5 },
      "expectedValues": { "name": "Lantern", "oil": 5 }
    }
    1. Línea 4 Crea una Lamp por sí sola y comprueba sus valores.

    En el Keeper, una entrada oculta guarda la referencia al Framework y childFrameworks asigna la ranura al Framework.

    schemas/keeper.json
        "lamp": { "label": "Lamp", "defaultValue": "lamp", "hidden": true }
    1. Línea 14 Una entrada oculta nunca aparece en el asistente.
    schemas/keeper.json
      "childFrameworks": { "lamp": "lamp" }
    1. Línea 31 Asigna la ranura lamp al Framework con el id lamp.

    El constructor devuelve las entradas de la Lamp junto a sus propios valores.

    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. Línea 5 lamp es un FrameworkRef. La plataforma convierte en uno el id del Framework de la entrada.
    2. Líneas 6-9 El tipo de retorno enumera los valores guardados y las ranuras hijas.
    3. Línea 12 La ranura hija: qué Framework crear y las entradas que se le dan.

    Da a los escenarios del Keeper la entrada lamp, y espera la Lamp que crea el constructor.

    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. Línea 8 La entrada oculta.
    2. Línea 18 El hijo que crea el constructor, con sus valores.
    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. Línea 8 La entrada oculta en heal.json.
    scenarios/heal.json
      "expectedChildren": { "lamp": { "name": "Lantern", "oil": 5 } }
    1. Línea 30 El hijo esperado en heal.json.

    Elige Comprobar y previsualizar. Debe pasar antes de seguir.

    Ahora el proyecto tiene estos archivos. Expande cualquier extracto de arriba para ver su archivo completo.

    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. Añade una lista y una acción rechazada

    Una lista guarda contenido que un personaje gana al jugar. El Pack guarda Trinkets, un segundo Framework cuyos presets son el contenido. Enumera su esquema y sus dos escenarios en el manifiesto.

    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. Líneas 8-12 El esquema del Trinket se suma a los otros dos.
    2. Líneas 17-18 El escenario del Trinket y el escenario de pack.

    Crea el esquema del Trinket. Su Frame necesita un id que ningún otro Frame del juego use.

    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. Línea 8 Un peso con límites. La oferta lo lee.
    2. Líneas 10-16 El Trinket tiene su propio Frame.
    3. Líneas 17-28 Los presets que un jugador puede guardar. Sus ids son lo que elige un escenario.

    Crea su constructor, su Frame y su escenario.

    rules/trinket.ts
    export function construct(
      name: string,
      weight: number,
    ): { name: string; weight: number } {
      return { name: name, weight: weight };
    }
    1. Línea 5 Devuelve los dos valores guardados.
    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. Líneas 22-23 Muestra el nombre y el peso.
    scenarios/new-trinket.json
    {
      "id": "new-trinket",
      "framework": "trinket",
      "parameters": { "name": "Rope", "weight": 2 },
      "expectedValues": { "name": "Rope", "weight": 2 }
    }
    1. Línea 4 Crea un Trinket por sí solo y comprueba sus valores.

    Declara la acción y la lista en el esquema del 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"
        }
      ],
    1. Líneas 19-23 La acción nueva.
    schemas/keeper.json
      "lists": { "pack": { "label": "Pack", "of": "trinket", "max": 4 } }
    1. Línea 37 La lista del Pack: su etiqueta, el Framework de contenido que guarda y su tamaño.

    Crea el archivo de reglas de la acción.

    rules/pack-trinket.ts
    export function pack_trinket(self: ElementRef, trinket: ContentRef): void {
      list.add(self, "pack", trinket, offer.where({ weight: { lte: 3 } }));
    }
    1. Línea 1 El trinket elegido llega como un ContentRef.
    2. Línea 2 La oferta decide qué se puede guardar. Solo los trinkets que pesan 3 o menos.

    Una oferta puede rechazar, así que prueba los dos resultados. Crea 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. Líneas 19-24 Aceptado: la cuerda es lo bastante ligera, así que se añade.
    2. Líneas 25-31 Rechazado: el yunque pesa demasiado. El paso espera el rechazo NOT_OFFERED y ninguna escritura.
    3. Línea 33 Después del último paso, el Pack solo tiene la cuerda.

    Un Frame que muestra una lista usa la version 2.0. Cambia la versión, declara la acción y una región en dataContract, y dibuja el botón y la región.

    layouts/keeper.json
      "version": "2.0",
    1. Línea 2 Los Frames con una lista usan la 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. Línea 21 El alias de la acción nueva.
    2. Línea 23 Declara la región de la lista.
    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. Líneas 37-39 El botón que ejecuta la acción.
    2. Línea 42 Una región dibuja una lista.
    3. Línea 44 La lista que se dibuja.
    4. Línea 45 Se muestra mientras la lista está vacía.

    Elige Comprobar y previsualizar. Debe pasar antes de seguir.

    Ahora el proyecto tiene estos archivos. Expande cualquier extracto de arriba para ver su archivo completo.

    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

Lo que construiste

Tu Keeper ahora tiene una elección, un valor derivado, un Framework hijo, una lista con una acción rechazada y escenarios que prueban cada uno. Las recetas muestran cambios más pequeños por separado.