Как записать весь игровой контент в формате, который примет движок, — рассказал Константин Сахнов, основатель студии Vengeance Games и научный руководитель программы «Менеджмент игровых проектов» в ВШЭ.
Константин Сахнов
Одна из самых распространенных и сложных задач, с которой сталкиваются как начинающие, так и опытные геймдизайнеры — описание большого объема контента для передачи его параметров в движок.
В этом материале я рассмотрю весь процесс на примере игры, над которой сейчас работаю. Это симулятор терраформирования планет с последовательным набором миссий и упором на сюжет.
Ключевые механики будущей игры:
- строительство зданий на планете;
- терраформирование планеты;
- развитие древа талантов.
В игре будет большое количество построек. У каждой — множество условий, функций и параметров.
Для понимания: для возведения любого здания нужны ресурсы. Требуемое количество и тип ресурсов для каждого строения свое. Функции у строений также разные. Какие-то производят ресурсы, какие-то увеличивают эффективность других построек, какие-то вовсе влияют на совершенно другие игровые сущности.
И задача гейм-дизайнера — так выстроить работу с данными, чтобы они были понятны коллегам, подтягивались движком и были изменяемы.
Как это сделать?
Поэтапно.
1. Утверждение формата данных
Первый шаг — договориться с программистами о формате передачи информации о контенте в движок.
Решить этот вопрос можно множеством вариантов.
Например, мы могли бы воспользоваться механизмом ScriptableObject или прописать данные в скриптах непосредственно в Unity / UE. Однако такой вариант подошел бы только в том случае, если количество контента определенного типа было бы небольшим (например, если бы речь шла о трех-пяти строениях) или вовсе мы имели бы дело с уникальным объектом на сцене.
Так что, если в вашей игре десять строчек данных, вам не нужны никакие «Google Таблицы», JSON и прочие решения.
В нашем же случае построек может быть до ста штук. Каждое накладывает тот или иной эффект на планету, территорию вокруг, соседние постройки, сказывается на производстве ресурсов. Также у каждого множество уровней развития.
Все это в коде не пропишешь. Плюс в этом случае не будет необходимой нам гибкости. Удалять, добавлять, балансировать контент будет не просто неудобно, а проблематично.
И это справедливо для любой игры, где для описания контента необходимо заполнить сотни конфигурационных файлов, а правка в балансе одного юнита игры затрагивает десятки разных таблиц.
Поэтому я рекомендую в качестве формата передачи информации использовать скриптовые языки разметки данных JSON или XML (также годится старенький Lua и более совершенный YAML).
Для конкретного проекта я остановился на JSON.
Кто-то отмечает его преимущества в привычном синтаксисе, пришедшем из JavaScript. Я же считаю главным плюсом — наличие готовых бесплатных модулей в ассет-сторах для сериализации данных из JSON / XML.
Плюс опыт моих студентов показывает, что научиться писать на JSON — вопрос нескольких дней. Это не то же самое, что изучить движок или язык программирования.
Итак, с программистами о формате передачи информаци договорились. После этого гейм-дизайнер должен сформировать полный список параметров каждого типа объектов с указанием типа данных и возможных лимитов.
Рис 1. Фрагмент страницы Confluence со списком параметров JSON-скрипта, описывающего сценарии
Получившийся список необходимо согласовать с программистами. Созданный гейм-дизайнером документ они превратят в описание структуры данных в коде игры.
Рис. 2. Скриншот описания структуры данных JSON-конфига стоимости улучшения построек в Unity
2. Связи и структура данных
Как правило, изолированных объектов в играх нет. Каждый объект взаимосвязан с какими-либо данными.
Для фиксации этих взаимосвязей и формирования структуры данных удобно использовать терминологию SQL, а также рассматривать каждый список контента определенного типа (в нашем случае — построек) в качестве таблицы.
Ключи и типы связи
При формировании структуры данных важно помнить о следующих понятиях:
Внешние ключи
Идентификаторы (обычно в виде натуральных чисел), по которым объекты из одних таблиц могут получать информацию из других.
Типы связей
Логика использования внутренних и внешних ключей. Обычно выделяют:
- Один к одному: связь между информацией из двух таблиц, когда каждая запись используется в каждой таблице только один раз. Например, идентификатор постройки в одной таблице полностью соответствует такому же идентификатору в другой.
- Один ко многим: в такого рода связях строка в таблице А может иметь много строк в таблице Б. Но строка в таблице Б может иметь только одну строку в таблице А. Например, одной постройке соответствует много уровней апгрейда, на каждом из которых она может иметь разные свойства.
- Многие ко многим: связь, при которой множественным записям из одной таблицы (A) могут соответствовать множественные записи из другой (Б). Например, здание «Четырехмерный завод» на пятом уровне открывает дополнительный слот под строительство в зоне, где он расположен. И свойство «Небесные филатуры» также накладывают эффект на здание, добавляющий в зону дополнительный слот для строительства.
Если есть техническая возможность, старайтесь создавать эффекты и другие поля с уникальными id, отдавая предпочтение связи один ко многим.
Нормальная форма базы данных — это набор правил, предполагающий недопущение избыточности данных. Их существует несколько, информации об этом довольно много в сети.
Формирование структуры данных конфигурационных файлов проекта проще показать на примере. Давайте кратко рассмотрим механику строительства зданий.
- Планета игрока разделена на зоны, каждая из которых состоит из гексов.
- В одном гексе может располагаться одна постройка.
- Помимо строений в зоне могут быть источники и аномалии.
Источники — это заготовка для здания. К примеру, месторождение кварца позволит построить в данной клетке только одну конкретную постройку, которая будет добывать камень.
Аномалия же — это виртуальный контейнер, содержащий в себе эффекты, которые начинают работать после ее исследования, и/или ресурсы, которые можно забрать, исследовав аномалию.
Постройки могут иметь различное число уровней. У каждого уровня есть:
- цена, которую нужно заплатить для улучшения постройки до этого уровня;
- набор эффектов, которые дает постройка на этом уровне;
- ресурсы, которые на этом уровне производятся.
Одна постройка может иметь несколько вторых уровней. Это значит, что при улучшении здания до второго уровня игрок выбирает, как именно улучшить строение.
Рис. 3. Фрагмент страницы Confluence с описанием развития зданий
Как видно на скриншоте, здание «Планетарный потрошитель» (да, я фанат Dead Space) требует металл для строительства. Будучи построенным, оно начинает производить 1 камень в ход. У игрока есть возможность дважды улучшить его. При улучшении до второго уровня игрок выбирает один из двух вариантов: увеличить добычу камня еще на 1 или сразу на 2.
Второй вариант обойдется игроку дороже на 100% + 30% (коэффициент инфляции апгрейдов). При улучшении здания до третьего уровня постройка не только производит камень, но и получает особый эффект на выбор: дополнительно 1 металла или 1 камня за ход с 50% вероятностью.
Давайте спроектируем модель данных, которая позволила бы нам описать такие возможности развития зданий.
Рис. 4. Взаимосвязи между списками контента (таблицами) в конфигах построек
Главная таблица текущей модели — Buildings. В ней описаны все постройки, доступные в игре. Каждой из них присвоен уникальный численный номер — id (натуральное число). По этому номеру мы будем идентифицировать постройку во всех таблицах.
Допустим, нам нужно получить текстовое описание или ассет (например 3D-модельку) конкретного здания. В этом случае программный код пробегается по таблице Buildings и находит в ней вхождение с заданным уникальным id. Далее он забирает нужную информацию из этого вхождения.
Рис 5. Скриншот Google Таблиц с фрагментом контента таблицы Buildings
Помимо таблицы Buildings, есть таблица Building_Levels. В ней прописан список всех уровней всех зданий. В частности, для здания «Планетарный потрошитель» будет пять вхождений: одно для первого уровня постройки и по два для второго и третьего уровня. У всех этих вхождений будет разный id, но одинаковый building_id. Он является внешним ключом, по которому можно однозначно найти постройку из списка Buildings.
Такая структура данных позволяет продукту иметь разное число уровней с разными бонусами для каждой постройки. Гейм-дизайнеры смогут спроектировать постройки-источники, которые будут просто добывать ресурсы и улучшаться по десять раз. Или сложные космические порты, которые можно улучшить всего пару раз, но пятью разными способами. Это демонстрация работы связи «один ко многим».
Рис. 6. Скриншот Google Таблиц с фрагментом контента таблицы Building_Levels
Улучшение здания до нового уровня и выбор одного из вариантов таких улучшений могут иметь разную стоимость. Логично, что расширение производства на +3 должно стоить дороже, чем расширение на +1. Иначе мы допустим ошибку, называемую дисбалансом «бесполезности или гиперполезности» по Гарфилду: зачем покупать за дорого то, что хуже, чем более дешевый аналог.
Чтобы спроектировать разную стоимость улучшения до каждого из вариантов уровней, удобно ввести дополнительную таблицу Building_Upgrade_Costs. Ее структура похожа на таблицу уровней. Однако теперь внешним ключом, на который ссылаются вхождения в эту таблицу, теперь являются не здания, а конкретные уровни. Для каждого такого уровня прописан список ресурсов, который необходимо отдать для апгрейда.
Рис. 7. Скриншот Google Таблиц с фрагментом контента таблицы Building_Upgrade_Costs
Аналогично описываются таблицы Building_Effects и Building_Production, отвечающие за особые эффекты типа «С вероятностью 50% добывает 1 металл или 1 камень» и за количество производимых ресурсов соответственно.
3. Пайплайн генерации контента
Основная сцена игры — это планета, на гексах которой игрок размещает постройки, перетаскивая туда соответствующие карточки. Каждый ход он получает одну карту (заготовку для постройки или заклинание). Из этого краткого описания уже формируются первые шаги к пониманию процесса:
- Разработка дизайн-документации на игровую механику (строительство и апгрейд зданий);
- Создание списка возможностей построек:
а) Производство ресурсов. Например, +1 энергии / ход;
б) Случайный дроп. Например, с вероятностью 75% приносит единицу случайного ресурса каждый ход;
в) Модификаторы (коэффициенты) экономики. Например, +5% скорость роста населения;
г) Сложные эффекты с селекторами и условиями. Например, все источники камня в зоне потребляют на 1 меньше энергии за каждый уровень улучшения. - Создание списка контента (построек);
- Формирование структуры Excel / Google (или иной) таблицы, в которую будут записываться данные;
- Наполнение таблицы контентом, и его балансировка;
- Формирование и проверка данных в собственном формате проекта или в одном из общепринятых форматов передачи данных. Например, JSON, YAML, LUA…
- Перенос данных в движок и сборка билда;
- Тестирование валидности данных и игрового баланса в игре.
Рис. 8. Скриншот фрагмента страницы Confluence с описанием планеты и строительства зданий
На скриншоте выше видно, что для описания одной только механики строительства мы подготовили сразу несколько документов, рассказывающих, что такое планета, зоны и гексы на ней, как разыгрываются карточки, с помощью которых создаются здания, как они размещаются на сцене и так далее.
Удобнее разделить огромный документ на небольшие статьи, раскрывающие отдельные элементы механики, соединенные в единую структуру системой ссылок, пронизывающих все повествование. Благо Confluence, Notion и аналогичные сервисы для ведения проектной и продуктовой документации отлично для этого приспособлены.
Также хорошим тоном можно считать разделение контента и описания механики. Не стоит объединять в одну большую статью то, как работают постройки, какая за ними стоит экономика и сам список этих построек. Однако это правило не универсально. Оно является следствием простого удобства потребления информации. Если ваша механика ограничена и не требует сотен единиц юнитов, заклинаний, уровней, то бывает удобно расписать все на одной странице.
4. Формирование и наполнение таблицы контента
Имея утвержденную продуктовую документацию и контент, гейм-дизайнер вплотную подходит к вопросу, как теперь описать все это техническим языком.
Чтобы начать работать с постройками, необходимо предварительно описать все, что с ними связано: ресурсы, карточки, экономику.
Рис. 9. Скриншот таблицы ресурсов в Google Таблицах
Обратим внимание, что в таблице построек нет никакой информации о том, что делает та или иная постройка, сколько стоит ее возведение и повышение уровня.
Рис. 10. Скриншот таблицы построек в Google Таблицах
Отмечу, что текст в поле «Название» используется исключительно для удобства гейм-дизайнеров, а настоящие тексты художественного описания, названия и даже описания работы эффектов вынесены в файл локализации. Логика его работы основана на тех же принципах, что и у других таблиц.
Рис. 11. Скриншот файла локализации
На его примере, к слову, удобно остановится на преимуществах автоматизации.
1. Автоматические проверки
«Google Таблицы» защищают гейм-дизайнера от неверного ввода контента. По крайней мере предупреждают, если введены некорректные данные. Так, например, при выборе группы, к которой относится локализуемый текст, в поле подгруппы автоматически подгружаются варианты, подходящие для данной группы.
К примеру, если мы выбрали группу «Планета», то невозможно будет указать подгруппу ключей, не относящихся к планете. То же самое во всех других таблицах: указывая диапазон чисел, которые может принять значение ресурса в таблице ресурсов (min_value, max_value), можно ввести только рациональное число от минус maxint до maxint. Ввод иного текста вызовет ошибку, предупреждая гейм-дизайнера, что вводимые данные могут быть только числом. Как итог, программисты получат от гейм-дизайнеров гарантированно проверенные и валидные конфиги.
2. Подсветка ячеек
Это помогает понять, какие данные еще не заполнены. К примеру, на скриншоте с файлом локализации мы видим, что во второй строке введена заготовка для очередного ключа локализации. Мы выбрали группу «Планета», подгруппу «Здание», но пока еще не описали, какой именно текст будет записан в данном ключе. Поэтому файл подсвечивает поле «Item» красным. В одной из строчек ниже текст в поле «Item» заполнен, но не введен перевод, поэтому подсвечено поле «ru», стоящее после «key» (ключа локализации). Если язык оригинала в игре русский, то можно настроить таблицу так, чтобы она подсвечивала все поля на английском, испанском и других языках, для которых гейм-дизайнеры поменяли что-то в русской локализации. Так мы никогда не забудем отправить на повторный перевод текст, оригинал которого изменился.
Не менее важная часть автоматизации ввода контента — это расчет баланса.
Нормальной является ситуация, когда гейм-дизайнерам приходится постоянно добавлять новый контент (в нашем случае постройки) и считать с нуля производство, стоимость покупки, улучшения и многие другие параметры. Это не только трата времени, но и еще одно место, где можно допустить ошибку.
Общий принцип во всех игровых проектах, над которыми я работал: автоматизируй все и никогда не пиши код сам. Любая ручная работа — шанс ошибиться.
Рис. 12. Скриншот таблицы стоимости апгрейда построек до заданного уровня
На скриншоте выше видно, что часть параметров рассчитывается автоматически.
К примеру, мы знаем, что постройка не имеет никаких эффектов, кроме простого производства ресурсов. Тогда, допустим, «Планетарный потрошитель» на первом уровне производит 1 камня за ход.
Наша задача — рассчитать стоимость постройки этого здания.
Гейм-дизайнеры решили, что на первом уровне оно будет строиться только из металла. Значит, нам нужно посчитать, сколько металла будет стоить здание, производящее 1 камень. Для этого нам нужно знать две вещи:
- как соотносятся между собой ценность металла и камня;
- за сколько ходов должно окупиться здание.
Первый параметр задается коэффициентом «weight» в таблице ресурсов (см. рис. 9). Второй — константой, зафиксированной на отдельной странице.
Зная все это, таблица считает стоимость постройки в энергии (самом дешевом ресурсе), а затем переводит ее в заданный ресурс — металл.
Таблица также учитывает массу других нюансов и коэффициентов. К примеру, инфляцию с повышением уровня постройки или числа построек в зоне.
Я специально описываю процесс баланса поверхностно, так как рассказ о нем — это материал на отдельную маленькую трехчасовую лекцию. Наша текущая задача — понять и убедиться, что баланс и математика также могут и должны быть автоматизированы.
5. Валидация и перенос данных в движок
Следующий этап — закодировать информацию из таблиц, чтобы ее можно было передать в движок.
Как я уже писал, мы используем для этого формат JSON, однако ничто не мешает вам выбрать любой другой способ представления информации.
Обратите внимание на любой скриншот из Google Таблиц с конфигами (рис. 9, , 10, 12). Последняя колонка всегда содержит ячейку для JSON скрипта.
Рис. 13. Скриншот формулы генерации JSON скрипта
Рис. 14. Скриншот итогового JSON скрипта, сгенерированного формулой
Каждая строка с данными в итоге заканчивается ячейкой со скриптом, содержащим записанную в строке информацию.
Следующий шаг — проверить, что генерируемый формулой скрипт содержит рабочий код без ошибок. Для этого удобно использовать различные онлайн валидаторы. Их легко найти в поисковике по запросу «JSON online validator».
Рис. 15. Скриншот JSON кода в онлайн-парсере, проверяющем его на ошибки
После проверки данных на отсутствие ошибок следует объединением скриптом данных из всех строк в одну ячейку, из которой удобно будет копировать финальный текст в редактор.
Если вы производите сборку билда не вручную, а используете автоматические сборщики, можно также добавить возможность автоматически подтягивать текст из заданной ячейки, перезаписывая тот или иной конфигурационный файл.
Итоговый алгоритм автоматизации работы с контентом можно кратко изобразить на схеме:
Рис. 16. Схема работы с контентом
6. Вместо выводов
Спасибо, что дочитали материал до конца. Надеюсь, он был полезен.
Знаю, что читать технические тексты с большим количеством таблиц бывает довольно скучно, однако это очень важно для развития культуры производства в команде.
Непринципиально, является ли ваша игра большим проектом с миллионными бюджетами или небольшой адвенчурой от независимой команды, если вы нацелены на коммерческий успех, автоматизация технической стороны процесса разработки — один из важнейших элементов создания игры, без которой гораздо выше риск производственного ада.
Комментарии
Кирилл Федосенко 2022-12-02 20:43:05
Очень крутая статья! Побольше бы таких на сайте.
Константин Сахнов 2022-12-03 00:07:30
Кирилл Федосенко, спасибо!
Ответить