Перейти к основному содержимому

Фреймворки на коде

Вырастим Lantern до настоящей игры

Добавьте в игру из быстрого старта выбор, производное значение, дочерний Framework и список, и проверьте каждый шаг сценарием.

Перед началом

Начните с проекта, который вы собрали в быстром старте. Каждый шаг называет все файлы, которые нужно создать или изменить. Отрывок показывает часть, которая меняется, а целый файл можно раскрыть. После каждого шага выберите «Проверить и показать».

Четыре шага

  1. Добавьте выбор

    Добавьте вход Calling с двумя вариантами.

    schemas/keeper.json
        "calling": {
          "label": "Calling",
          "options": ["Guard", "Scout"],
          "defaultValue": "Guard"
        }
    1. Строка 11 Фиксированный список вариантов. Значение по умолчанию должно быть одним из них.

    Конструктор получает новый вход и возвращает его как хранимое поле.

    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. Строка 4 Новый вход.
    2. Строки 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"
      }
    }
    1. Строка 4 Вход, который вы передаёте.
    2. Строка 9 Хранимое значение, которое вы ожидаете.

    Затем сделайте те же два изменения в heal.json.

    scenarios/heal.json
      "parameters": { "name": "Mira", "max_hp": 10, "calling": "Guard" },
      "expectedValues": {
        "name": "Mira",
        "max_hp": 10,
        "hp": 10,
        "calling": "Guard"
      },
    1. Строка 4 Тот же вход.
    2. Строка 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
  2. Выведите значение

    Функция журнала выводит Guard из calling. Объявите её в схеме.

    schemas/keeper.json
      "ledgerFunctions": [
        {
          "id": "guard",
          "name": "Guard",
          "field": "guard",
          "source": "rules/guard.ts"
        }
      ]
    1. Строки 24-27 id и name определяют функцию, field это хранимое значение, которое она заполняет, а source это файл с её правилами.

    Создайте 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. Строки 1-3 Вспомогательная функция без export.
    2. Строки 5-7 Функция журнала читает calling и возвращает выведенное значение.

    Конструктор также возвращает поле guard с начальным значением 0. Функция журнала записывает настоящее значение.

    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. Строки 5-6 guard входит в результат и начинается с 0.

    Сценарий проверяет функцию. Добавьте guard в ожидаемые значения new-keeper.json и строку 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 }]
    }
    1. Строка 10 Выведенное значение после работы функции журнала.
    2. Строка 12 Вызывает функцию guard с текущим значением 0 и ожидает 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
      },
    1. Строка 10 В этом сценарии выведенное значение тоже остаётся 3.

    Чтобы показать Guard на листе, перечислите его в requiredBindings и добавьте текстовый узел, который его использует.

    layouts/keeper.json
          { "binding": "element.attributes.guard", "expectedType": "number" }
    1. Строка 15 Привязка для нового поля.
    layouts/keeper.json
          { "kind": "text", "text": "Guard: {{element.attributes.guard}}" },
    1. Строка 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
  3. Добавьте дочерний Framework

    Keeper носит Lamp. Lamp это второй Framework со своей схемой, конструктором, Frame и сценарием. Перечислите его схему и его сценарий в манифесте.

    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. Строка 8 Схема Lamp добавляется к схеме Keeper.
    2. Строки 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 }]
    }
    1. Строки 6-9 Входы дочернего Framework. Дочернему тоже нужно имя.
    2. Строка 10 У дочернего Framework свой Frame.

    Создайте его конструктор, его Frame и его сценарий.

    rules/lamp.ts
    export function construct(name: string, oil: number): { name: string; oil: number } {
      return { name: name, oil: oil };
    }
    1. Строка 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}}" }
        ]
      }
    }
    1. Строки 11-14 Каждое поле, которое показывает Frame, перечислено как привязка.
    scenarios/new-lamp.json
    {
      "id": "new-lamp",
      "framework": "lamp",
      "parameters": { "name": "Lantern", "oil": 5 },
      "expectedValues": { "name": "Lantern", "oil": 5 }
    }
    1. Строка 4 Собирает Lamp отдельно и проверяет его значения.

    В Keeper скрытый вход хранит ссылку на Framework, а childFrameworks сопоставляет слот с Framework.

    schemas/keeper.json
        "lamp": { "label": "Lamp", "defaultValue": "lamp", "hidden": true }
    1. Строка 14 Скрытый вход никогда не показывается в мастере.
    schemas/keeper.json
      "childFrameworks": { "lamp": "lamp" }
    1. Строка 31 Сопоставляет слот lamp с Framework с id lamp.

    Конструктор возвращает входы Lamp рядом со своими значениями.

    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. Строка 5 lamp это FrameworkRef. Платформа превращает id Framework из входа в такую ссылку.
    2. Строки 6-9 Тип результата перечисляет хранимые значения и дочерние слоты.
    3. Строка 12 Дочерний слот: какой Framework собрать и какие входы ему передать.

    Передайте сценариям 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 } }
    }
    1. Строка 8 Скрытый вход.
    2. Строка 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
      },
    1. Строка 8 Скрытый вход в heal.json.
    scenarios/heal.json
      "expectedChildren": { "lamp": { "name": "Lantern", "oil": 5 } }
    1. Строка 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
  4. Добавьте список и отклонённое действие

    Список хранит контент, который персонаж получает в игре. Pack хранит Trinkets, второй Framework, чьи пресеты и есть контент. Перечислите его схему и два его сценария в манифесте.

    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. Строки 8-12 Схема Trinket добавляется к двум другим.
    2. Строки 17-18 Сценарий Trinket и сценарий pack.

    Создайте схему Trinket. Её Frame должен иметь id, который не использует ни один другой Frame в игре.

    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. Строка 8 Вес с границами. Предложение читает его.
    2. Строки 10-16 У Trinket свой Frame.
    3. Строки 17-28 Пресеты, которые игрок может положить в рюкзак. Сценарий выбирает их по id.

    Создайте его конструктор, его Frame и его сценарий.

    rules/trinket.ts
    export function construct(
      name: string,
      weight: number,
    ): { name: string; weight: number } {
      return { name: name, weight: weight };
    }
    1. Строка 5 Возвращает два хранимых значения.
    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. Строки 22-23 Показывает имя и вес.
    scenarios/new-trinket.json
    {
      "id": "new-trinket",
      "framework": "trinket",
      "parameters": { "name": "Rope", "weight": 2 },
      "expectedValues": { "name": "Rope", "weight": 2 }
    }
    1. Строка 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"
        }
      ],
    1. Строки 19-23 Новое действие.
    schemas/keeper.json
      "lists": { "pack": { "label": "Pack", "of": "trinket", "max": 4 } }
    1. Строка 37 Список Pack: его подпись, Framework контента, который он хранит, и его размер.

    Создайте файл правил действия.

    rules/pack-trinket.ts
    export function pack_trinket(self: ElementRef, trinket: ContentRef): void {
      list.add(self, "pack", trinket, offer.where({ weight: { lte: 3 } }));
    }
    1. Строка 1 Выбранная безделушка приходит как ContentRef.
    2. Строка 2 Предложение решает, что можно положить в рюкзак. Только безделушки весом 3 или меньше.

    Предложение может отказать, поэтому проверьте оба исхода. Создайте 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. Строки 19-24 Принято: верёвка достаточно лёгкая, поэтому её добавляют.
    2. Строки 25-31 Отказ: наковальня слишком тяжёлая. Шаг ожидает отказ NOT_OFFERED и отсутствие записей.
    3. Строка 33 После последнего шага в Pack лежит только верёвка.

    Frame, который показывает список, использует version 2.0. Измените версию, объявите действие и область в dataContract и нарисуйте кнопку и область.

    layouts/keeper.json
      "version": "2.0",
    1. Строка 2 Frame со списком использует 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. Строка 21 Псевдоним для нового действия.
    2. Строка 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."
          }
    1. Строки 37-39 Кнопка, которая запускает действие.
    2. Строка 42 Область рисует список.
    3. Строка 44 Список для показа.
    4. Строка 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 есть выбор, производное значение, дочерний Framework, список с отклонённым действием и сценарии, которые проверяют каждое из них. В рецептах показаны более мелкие изменения по отдельности.