Создаем коллекции объектов и настраиваем автоэскпорт проекта в игровой движок

Смотрите наш подробный видеогайд

Тридцать карт — это тридцать отдельных файлов, и ни один из них не показывает колоду целиком. Чтобы ответить даже на простой вопрос «какая карта наносит слишком много урона за свою стоимость», приходится открывать их по очереди, удерживать все значения в голове и сравнивать по памяти. Потом результат нужно переписать в таблицу баланса, а затем ещё и в движок. Получается три копии одних и тех же чисел.

Проблема: у баланса нет источника истины. В предыдущей статье мы сделали дизайн типизированным, связали карты с персонажами и закрепили словарь структурами и перечислениями. Данные стали осмысленными и связанными, но работать с ними по-прежнему приходилось по одному файлу. Сегодня мы устраним этот разрыв: мы собирем колоду в одну таблицу, дадимм каждой задаче свою раскладку, навигация через фильтры и добавим автоэкспорт, чтобы файл с балансом сохранялся после каждого имзенения.

Коллекция: баланс в одной таблице

Вы больше не редактируете одну карту. Вы настраиваете всю колоду, а для этого нужно видеть много объектов одновременно.

Коллекция — это ваши данные геймдизайна, отображаемые в виде таблицы. Одна строка соответствует одной карте, а нужные поля становятся колонками. Главное здесь то, что каждая строка это реальный элемент, тот же самый файл на диске, который раньше вы открывали по одному,е копия и не отдельный список значений. Если открыть строку, вы увидите карту целиком. Если изменить строку, изменится сама карта.

Создать коллекцию можно прямо в дереве проекта тем же способом, что и папку. Выберите в переключателе Коллекция, дайте имя Карты и выберите тип - базовый объект Игровая карта, который мы создали до этого.

Карты, которые уже есть в проекте, просто перетаскиваются в коллекцию в дереве слева. Перемещаются сами элементы, а не их копии.

Колонки приходят из типа. У Игровой карты четыре поля: Имя (Строка), Стоимость (Целое число), Урон (Число) и Легендарная (Флажок). Написать «Стоимость» как-то иначе или забыть про «Урон» здесь нельзя, потому что структуру задаёт сам тип. Благодаря многоуровневому наследованию со второй недели в одной таблице можно держать и карты-предметы (Гантель, Газировка, Аптечка), и карты способностей («Дымовая завеса» Дмитрия). Обе категории наследуют одну базу, поэтому у них одинаковые колонки, и их можно сравнивать между собой.

Правка нескольких карт — одно сохранение

Здесь таблица окончательно перестаёт быть просто картинкой. Вы меняете урон «Банановой кожуры» с 1 на 2, поправляете стоимость «Огнетушителя» и отмечаете «Тележку для покупок» как легендарную, всё прямо в таблице, не открывая ни одного файла.

Таблица помечает изменённые значения, а кнопка Сохранить записывает их на диск одним действием вместо тридцати отдельных редактирований. Поскольку это те же файлы, которые читает движок, таблица баланса и данные игры теперь физически не могут разойтись.

Правило «строка равна элементу» работает и в обратную сторону. Команда Добавить строку создаёт новый настоящий элемент, реальный файл на диске, который остаётся только переименовать и настроить. Команда Удалить строку (нужно выделить её и нажать Delete) удаляет сам элемент, поэтому приложение заранее спросит подтверждение.

Вид под каждую задачу

У каждой задачи своя специфика, и одной и той же таблице иногда нужны совершенно разные раскладки. Для проверки урона нужны Имя, Урон, Стоимость и флаг легендарности, отсортированные по урону. Для анализа стоимости почти никаких данных не требуется, достаточно аккуратной карточки на элемент. Для глубокого анализа нужна каждая колонка.

Перестраивать эти три раскладки руками каждый раз — снова погрязнуть в рутине.

Вид — это сохранённая раскладка данных коллекции, не копия данных, а их компоновка: какой из трёх типов отображения выбран, какие колонки видны, в каком порядке, какой ширины, как отсортировано и до чего отфильтровано. Настроили один раз и потом просто открываете готовый вид. Сами данные при этом никуда не двигаются, меняется только способ их отображения.

Откройте список видов коллекции и выберите Создать вид. Назовите его по задаче, которой он служит: График урона для таблицы, Только стоимость для карточек, Полный аудит снова для таблицы.

Всего есть три типа отображения, и каждый подходит для своей задачи. Таблица показывает все колонки сразу, хорошо подходит для сравнений и глубокого анализа. Список показывает каждый элемент отдельным блоком на всю высоту, поля идут строками, а элементы листаются сверху вниз. Карточки представляют собой компактную сетку, где заголовок и несколько ключевых полей позволяют быстро взглянуть на всю колоду сразу.

Раскладка: сортировка, фильтр, свойства

За настройку вида отвечает панель над коллекцией с тремя раскрывающимися списками. В Сортировке вы выбираете поле и направление (по возрастанию или по убыванию), причём сортировок можно связать несколько сразу. В Фильтре вы задаёте виду запрос, чтобы он открывался сразу на нужном наборе данных. В Свойствах вы решаете, какие поля показывать: отмечаете колонки чекбоксами, скрываете лишние и перетаскиваете их в нужном порядке.

Ширину колонок задаёт перетаскивание: возьмитесь за боковой край заголовка и тяните до нужного размера, вид запомнит ширину вместе с остальными настройками. Клик по шапке колонки циклически переключает сортировку (возрастание → убывание → выключено), поэтому График урона сохраняется уже отсортированным по урону от большего к меньшему.

Фильтры: вопрос вместо прокрутки

Виды запоминают колонки и сортировку. Но когда колода растёт, одной сортировки уже недостаточно. В балансе вообще редко нужен просто самый большой показатель. Гораздо чаще требуются выборки по условию: все карты с уроном выше 20, все нелегендарные карты стоимостью больше 3 или урон в диапазоне от 10 до 30.

В интерфейсе коллекции есть строка фильтра. Кликните по ней, начните вводить запрос, и таблица будет отбирать подходящие данные прямо по мере набора.

Самая простая конструкция: имя поля и значение.
damage:20 — карты, у которых урон ровно 20.
type\:gamecard — элементы с сервисным именем gamecard.
inside\:Cards — элементы внутри рабочего пространства Cards.
name\:Fire — ровно одна карта, потому что сервисные имена уникальны.

Операторы сравнения пишутся сразу после двоеточия:
damage:>20 — урон больше 20
damage:<3 — меньше трёх
damage:>=10 и damage:<=30 — границы включительно
damage:<>3 — всё, кроме выбросов

Поскольку значения типизированы, они сравниваются именно как числа, а не как текст: 20 воспринимается как двадцать, а не как последовательность символов «2» и «0».

Диапазон тоже помещается в одно выражение: damage:[10..30] — урон от 10 до 30 включительно. Один диапазон заменяет сразу два сравнения.

Условия комбинируются через AND, OR и скобки.
type\:gamecard AND damage:>20 AND islegendary\:false находит нелегендарные карты с завышенным уроном — потенциальный перекос силы.
damage:<5 OR damage:>30, или эквивалентно NOT (damage:[10..30]) — карты за пределами играбельного диапазона.

Язык не предназначен для описания произвольного текста, и в этом его плюс: правильно составленное выражение однозначно описывает нужную выборку без догадок и двусмысленности.

Сохраняем фильтры

Введённый фильтр, это разовый запрос. Сохранённый фильтр уже становится регулярным отчётом. Поэтому запрос Завышенные превращается в отдельный именованный вид: вы вводите выражение, проверяете результат, сохраняете как вид и идёте дальше. При каждом открытии вида фильтр заново проверяет актуальные данные. Если урон какой-нибудь карты станет больше 20, она сама появится в Завышенные, и обновлять вручную ничего не нужно.

Экспорт: CSV для команды, JSON для движка

Теперь вы можете отслеживать коллекцию Карты как угодно. Движок ждёт конкретную JSON-структуру, не весь объект карты, как он лежит на диске, а только те поля, которые ему нужны, и под теми именами, которые он ожидает. Команда хочет CSV-строки, которые можно открыть в Excel, переставить и прокомментировать. Составлять нужные форматы руками после каждой правки баланса, значит вернуться к переписыванию, от которого мы так хотели избавиться.

Для этого есть пользовательский формат экспорта, инструкция, которая определяет, как набор элементов превращается в файлы: какие элементы экспортировать (опционально ограничив типом), в каком формате, с какими полями.

Создаём формат для движка: Заголовок — Карты для движка, Тип — Игровая карта, Формат — JSON. У JSON три шаблона формы: Полный отдаёт все данные элемента, Только значения - структурированные значения, сгруппированные по блокам, а Выбранные поля — те поля, которые вы отметили, ничего лишнего. Для движка подходит третий: Имя, Стоимость, Урон и Легендарная. Для каждого выбранного поля можно задать своё имя вывода — они станут ключами объектов в экспортированных файлах.

Второй формат — CSV, уже для команды: выбираете нужные колонки, задаёте разделитель (запятая или точка с запятой) и при необходимости добавляете строку заголовков. В итоге каждая карта становится отдельной строкой. Два разных формата, но источник у них один и тот же - коллекция Карты.

Скрипт постобработки и живой предпросмотр

Иногда движку нужны данные, пересобранные иначе, чем их хранит редактор, решаем через скрипт постобработки, прикреплённый к формату.

При экспорте каждый элемент проходит через небольшую JavaScript-функцию. Подготовленный элемент передаётся в переменную asset_data, а всё, что функция вернёт, будет записано для этого элемента. По умолчанию скрипт ничего не меняет: return asset_data;.

Допустим, движок загружает карты как словарь по коротким именам, использует другие названия полей и ожидает несколько производных значений, которые сам считать не хочет. Тогда скрипт пересобирает каждый элемент и сразу вычисляет их:

Ключи name, damage, cost и legendary приходят из имён вывода, заданных в Выбранных полях. Остальное — обычный JavaScript: power_budget складывает два поля, а overtuned прогоняет те же значения через сравнение и определяет соотношение урона и стоимости. Движок получает уже готовую сумму и готовый флаг, и ему не нужно каждый раз пересчитывать исходные значения.

Скрипт пишется прямо в приложении, а рядом редактор показывает предпросмотр на реальном элементе. Ещё до экспорта первого файла видно, как будет выглядеть карта после обработки. Это снимает главную проблему экспорта, когда неправильную структуру обнаруживают уже после записи файлов.

Поскольку каждый элемент проходит через один и тот же скрипт, вся колода получает одинаковую структуру: первая карта и тридцатая будут обработаны по одним и тем же правилам, т.к. это обычный текст, экспорт можно хранить прямо в git: каждое изменение баланса будет видно в истории как обычный читаемый дифф.

Автоэкспорт: IMS Creators и Godot

Экспорт в один клик — удобно. А что делать, если вы просто забыли его запустить? Баланс — это постоянный поток небольших изменений, и многие откладывают его на потом. В итоге игра продолжает работать на вчерашних числах, и вы снова разгадываете ребус.

Автоэкспорт связывает коллекцию с папкой назначения и поддерживает файлы там в актуальном состоянии. Настраивается он один раз в проекте, а дальше вы просто сохраняете элемент.

Откройте меню рядом с именем проекта → Экспортировать → Настроить автоэкспорт и нажмите «Добавить конфигурацию». Конфигурация определяет три вещи: какой набор данных экспортировать, куда его отправлять и в каком формате.

Папка — источник: коллекция Карты.

Тип — Игровая карта: конфигурация использует тип нашего набора.

Выберите формат — созданный в четверг формат Карты для движка.

Сохранить результат как — cards, при объединении JSON в один файл на выходе получается cards.json.

Можно добавить вторую конфигурацию, например, CSV в отдельную папку для команды. Одна коллекция, разные потребители, и всё экспортируется автоматически.

Дальше — корневой каталог. Это то место, которое соединяет IMS Creators и проект в игровом движке: возьмите папку проекта (наш пример на движке Godot) и укажите её здесь как место для экспорта. Файлы будут попадать прямо туда, откуда их сможет прочитать игра.

Перед нами две настройки. Экспорт запускает экспорт прямо сейчас — разовая ручная синхронизация, особенно полезная при первой настройке. А Экспортировать автоматически включает постоянную синхронизацию: с этого момента каждое сохранение элемента заново создаёт экспортируемые файлы. Важно, что система работает не только с Godot, но и с Unity и Unreal Engine.

Цикл от сохранения до игры

Вот весь цикл целиком. Ваш Godot-проект читает файл, который создаёт автоэкспорт:

cards — это массив, и каждая запись в нём — отдельная экспортированная карта в той форме, которую сформировали Выбранные поля и скрипт формата. Именно поэтому имена вывода стоит задавать так, чтобы они сразу совпадали с тем, что ожидает движок:

Теперь сам цикл. Открываем коллекцию Карты, поднимаем Урон Огнетушителя с 4 до 6, нажимаем Сохранить, и через секунду cards.json уже содержит "damage": 6. Перезагружаете сцену, и движок получает новое значение. Всё это происходит без ручного экспорта: редактор и запущенная игра читают одну и ту же версию данных, а держит их небольшая сохранённая настройка.

Если хочется видеть изменения вообще без ручной перезагрузки, добавьте чтение в реальном времени — небольшой запрос или reload_data(), который заново читает cards.json, и привяжите его к горячей клавише. Меняете число, сохраняете и сразу видите, как это влияет на игру.

Итоги

Две проблемы большой таблицы — параллельная копия данных и ручной перенос в движок исчезли. Типизированные коллекции превратили баланс в работу с одной таблицей: каждая строка — реальная карта, а каждая правка сохраняется в самом элементе. Виды дали каждой задаче свою раскладку: тип отображения, нужные колонки, сортировку и фильтр, так что переключение между задачами занимает один клик. Фильтры заменили пролистывание запросами: damage:>20, диапазоны и комбинации условий находят нужные данные сразу, а сохранённый фильтр остаётся постоянным отчётом, который сам переспрашивает сегодняшние карты. Пользовательский экспорт превратил коллекцию в нужный формат: JSON для движка через Выбранные поля, CSV для команды. Автоэкспорт напрямую связал коллекцию с проектом в игровом движке, так что файлы перезаписываются сами.

Одна строка, чтобы прочитать данные. Одна конфигурация, чтобы их записать. Дизайн и игровая сборка больше не смогут разойтись.

Дальше: ИИ-агент

Следующим шагом станет добавление ИИ-агента, которыйы заберёт вашу рутину с поиском и правками в ГДД.

Попробуйте сами. Скачайте IMS Creators, откройте ims.cr5.space/desktop и пройдите недельный путь на своём проекте: соберите коллекцию Карты с типом Игровая карта, настройте виды под свои задачи, сохраните фильтр завышенных карт как отдельный вид, создайте JSON-формат для движка и CSV для команды, а затем включите автоэкспорт прямо в папку проекта. Две минуты настройки? и баланс перестанет расходиться с движком навсегда.