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

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

Быстрый старт: ваш первый фреймворк на коде

Напишите небольшую игру с одним персонажем, двумя действиями и листом. Затем проверьте её и создайте персонажа в The Forge.

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

  • Чтобы создать игру, нужен платный тариф.
  • Создайте приватную игру в The Forge. Выберите «СОЗДАЙТЕ СВОЮ ИГРУ», затем «Ручной редактор» и создайте игру.
  • Напишите файлы в любом редакторе и вставьте их в редактор исходного кода или набирайте их прямо там.

Игра

В Lantern Keepers один персонаж, Keeper. У Keeper есть имя, максимум HP и текущее HP. HP меняют два действия: Получить урон и Лечение. Лечение никогда не поднимает HP выше максимума.

Шаг 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, который создал редактор для вашей игры. Этот id только для примера.
  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. Ровно один из них стоит по умолчанию.

Шаг 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 на заданную величину. Если добавить отрицательное число, значение уменьшится.

Лечение читает два значения, затем устанавливает новое 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. 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 это дерево, которое рисуется. Text показывает поля в двойных фигурных скобках, а кнопки называют псевдоним действия.

Шаг 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 Шаг первый: получить 6 урона. Действие должно сделать ровно одну запись: ledger.add со значением -6.
  2. Строки 12-16 Шаг второй: вылечить 9. Результат ограничен максимумом, поэтому hp становится 10.
  3. Строка 17 Шаг третий: снова лечить при полном hp. Защита пропускает запись, поэтому записей не ожидается.

Перенесите проект в The Forge

  • Откройте свою игру в The Forge.
  • На шаге «Геймплей» выберите «Дополнительно: исходный код и версии», чтобы открыть редактор исходников.
  • Если в игре ещё нет проекта, выберите «Написать самостоятельно», чтобы начать с редактируемых примеров. Не выбирайте «Начать создание моей игры»: это путь через ИИ. Замените файлы примеров файлами выше: используйте «Добавить файл» для новых путей и «Удалить файл» для ненужных файлов. Оставьте 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, и проверка пройдёт. Правила могут писать только в поля, которые возвращает конструктор.

Дальше