Andrey Dyakov, Associate Technical Director, Sperasoft
This presentation contains information about benefits of Data-Driven Gameplay architecture in games development, how to organize data and work with it in Unreal Engine 4 on the example of various game projects to achieve a variety of goals. You will learn about the ways to build a dynamic data-driven gameplay, DataAsset usage examples, remote synchronization of data and many other nuances of working with data in UE4.
2. ABOUT ME
I AM ANDREY DYAKOV
Associate Technical Director at Sperasoft with 8 years of game
development experience. During my career I worked with many
game engines including Unity, UDK, Unreal Engine 4 and other
proprietary game engines from well known studios.
email: Andrey.Dyakov@sperasoft.com
skype: a__dyakov
twitter: acrossfy
5. Применение Data-Driven подхода в играх
◦ Декларативный мир
(постройки, биомы, зоны добычи ресурсов и т.д.)
◦ Декларативное описание персонажа
(характеристики, скилы, инвентарь и т.д.)
◦ Управление процедурной генерацией
(чего угодно)
◦ Бизнес логика
(прогресс, требования/награды/достижения,
внутриигровые покупки и т.д.)
7. Плюсы Data-Driven дизайна
◦ Переиспользование кода
◦ Код проще в обслуживании
◦ Ускоренная разработка функционала
◦ Удобная работа с данными
◦ Синхронизация данных в рантайме
8. Composition over Inheritance
◦ Сущность является контейнером компонентов
◦ Компоненты определяют поведение объекта
◦ Данные описывает компоненты объекта
◦ Данные определяют иерархию сущностей
9. Classic OOP: Inheritance
◦ Поведение расширяется
наследниками
◦ Объект управляет
данными
◦ Данные могут
передаваться по
иерархии
10. Composition & Inheritance in UE4
◦ Система компонентов
◦ Фабрики
◦ Gameplay тэги
◦ DataTables
◦ DataCurves
◦ еtc
18. Data Table/Curve
◦ Source: csv, xls
◦ Doesn’t require C++
◦ Binary object
◦ Flat structure
◦ Can’t be synced or reimported
at Runtime
◦ Special Getters for data
◦ Two sources of truth
Data Asset
◦ Any type of data
◦ Hierarchical structure
◦ Can be synced at runtime
◦ It’s object with properties
◦ Binary object
◦ Requires C++
UE4 Data Containers: Pros/Cons
19. Simple game design case
◦ Набор уровней разделенный на главы
◦ Можно заработать три звезды за уровень
◦ У каждой звезды свои требование
◦ Главы или уровни могут быть
разблокированы если выполнены их
требования
◦ Уровень может содержать
коллекционные предметы, секреты и т.д.
29. UE4 C++ Ограничения
Для классов потомков UObject:
◦ Множественное наследование запрещено
◦ Нельзя передавать данные в конструктор
◦ Классы и интерфейсы не могут быть шаблонными
◦ Blueprint Callable методы не могут быть шаблонными
Data-Driven Gameplay – термин применяемый в отношении разработки игр в которых входные данные определяют поведение игровых объектов.
Этот термин происходит от парадигмы Data-Driven программирования, которая ведет к созданию Data-Driven дизайну архитектуры ПО.
Этот термин происходит от парадигмы Data-Driven программирования, которая ведет к созданию Data-Driven дизайну архитектуры ПО.
Построение подобной архитектуры дает множество плюсов, каждый из которых проявляется проявляется в той или иной степени в зависимости от жанра игры.
В идеале это позволяет повысить переиспользование кода, ускорить процесс создания новых фич, обеспечить максимальным контроль над игрой сразу после релиза, позволить балансировать игру в ограниченных условиях, когда нет возможности быстро выпустить хотфикс, как например в случае с публикацией в App Store:+ Code reuse (with declarative game programming); + Разработка новых фич идет эффективнее – каждый участник команды делает свою работу – программист пишет код, дизайнер настраивает игровые объекты, художник предоставляет контент, технический художник его вставляет и т.д. и до бесконечности. Программист не должен заниматься магией, хотя иногда очень хочется)
+ Что в свою очередь позволяет быстрее наращивать игровой контент.+ Данные можно вынести из игры на сервер и обновлять по мере поддержки продукта.
Наиболее часто встречаемые подходы к организации игровой логики это композиция или наследование.Композиционная архитектура сводится к тому, что особенности объекта должны выделяться в объект/компонент и по возможности быть независимыми от объекта носителя.
В случае с классическим ООП в большом проекте очень легко достичь сложной иерархии классов и хрупкой архитектуры всей игры. На слайде представлена схема шутер игры в начале разработке. Выглядит просто и лаконично, но в процессе разработки она может разростить до неприличных размеров и будет иметь ряд проблем. Необходим наиболее гибкий подход.
Нечто подобное мы видим в архитектуре движка Unreal Engine 4 и в Gameplay фреймворке. Это дает нам свободу выбора и не навязывает какую-либо одну из парадигм программирования. В нашем распоряжении есть встроенная система компонентов, gameplay тэги, контейнеры данных, фабрики и многое другое.
Одним из важных аспектов Data-Driven программирования является выделения особенностей объекта в компоненты. Объект в таком случае может быть описан на уровне работы с данными. В таком случае мы приходим от к абстракции данных от абстракции классов. В случае с такими простыми объектами, как оружие предпочтительнее пользоватьсся заготовленными классами объектов.
При этом для более сложных объектов, таких как игровой персонаж в MOBA игре, лучше использовать динамическую композицию через данные. На слайде вы видите самый простой пример такой композиции.
Примерно разобравшись в сути термина и в подходах к работе, самое время разобраться в организации и работе данных в UE4.
Сейчас на слайде представлен скриншот оффициальной документации, в которой присутствует статья Data-Driven Gameplay Elements in UE4. Но по сути эта статья лишь в двух словах описывает контейнеры данных движка – DataTables и DataCurves, что по сути одно и то же, с той лишь разницей, что кривые используются только для хранения интерполированных цифровых значений.
В целом это удобные контейнеры данных которые создаются на основе любой указанной структуры и заполняются с помощью ипортируемых данных из таблиц Excel.
Впрочем заполнять можно и просто из редактора не прибегая к таблицам. На выходе мы получаем словарь заполненных структур с доступом по ключу.
На деле же в UE4 есть еще один контейнер данных, перешедший в движок из UDK. Я говорю о DataAsset’ах. Наверняка многие не раз видели в ContentBrowser’e возможность создать DataAsset, а возможно кто-то даже и использует их в своих проектах. Есть здесь такие? А кто пользуется DataTables? Ок. Давайте рассмотрим и сравним все подробнее.
DataTables отличаются плоской структурой данных. Это не лучший контейнер данных для иерархичных структур, т.к. многие используют именно таблицу в качестве исходника. Несмотря на возможность заполнений дочерних структур, массивов, указателей на объекты из редактора, последующее редактирование после экспорта в Excel становится не самым удобным. Более того становится актуальной так называемая проблема двух источников правды для одного объекта. DataTable не могут быть использованы по ссылке, одна таблица не может напрямую ссылаться на другую, а для получения данных приходится пользоваться специальными статичными методами. Использование же DataTable в плюсах и вовсе жуткое (как по мне).DataAsset представляет объект. Это буквально класс унаследованный от UObject’a и скрывающий все лишнее от него. Для создания своего DataAsset’а вы создаете новый класс унаследованный от UDataAsset и добавляете в него свои поля, которые могут быть массивами, мапами, ссылками, структурами, вложенными DataAsset’ами. После этого через Content Browser мы создаем экземпляр, который тут же можно начать заполнять через редактор. С этим экземпляром можно работать напрямую по ссылке и из него можно не только брать данные, но и заполнять их, в зависимости от того, какой аксессор был установлен у поля. Epic Games хоть и не упоминали этот тип в своей документации, тем не менее используют его в своих проектах, в том числе для синхронизации онлайн через JSON.
Тем не менее каждый из контейнеров хорош по своему и там где хватает DataTables, абсолютно бессмысленно использовать DataAsset’s. Давайте попробуем рассмотреть тестовый кейс абстрактной казуальной игры. Это некий собирательный образ казуальных игр последних лет, в прочем мое мнение здесь не авторитетно, я не геймдизайнер, но допустим..
+ В игре есть набор уровней поделенных на главы;
+ При прохождении каждого из уровней игрок может зарабатывать звезды за прохождение;
+ Требования к получению звезд могут различаться, как по типу, так и по значению;
+ Уровни и Главы могут открываться при выполнении требований;
+ Уровни могут содержать коллекционные предметы и секреты, которые могут находиться в разных позициях при перепрохождениях;
Для наглядности я изобразил простую структуру данных. Самый первый объект на графике это владелец данных. В зависимости от того, как мы их храним, это может быть абсолютно любой объект – GameInstance, LocalPlayer, DataTable, DataAsset, даже просто любой блупринт класс. В случае если в игре будут достижения, сменные персонажи и прочие структуры данных – они также будут исходить из объекта на графике в отношении N к одному.
Первым делаем пытаемся использовать для этого кейса DataTable. Отталкиваемся собственно от описания уровня.АнимацияЕсли кто-то пытался успеть прочитать все названия переменных – вот они – в импортированном DataTable. В чем плюс такой таблицы для нас – она в общем-то работает. Правда приходится вручную указывать Id главы, которая описана в другой таблице. Приходится перечилять все возможные требования для открытия уровня и подразумевать, что если значения равно 0, то оно выключено, вместо того, чтобы указать класс одного конкретного требования и тут же задать его значение, которое не обязательно может быть числовым. Со звездами проще – все требования числовые, поэтому тип можено определять перечислением, а значение установить одного типа флоатом или интом. Но в это в этой версии дизайна игры. В проекте, разработка которого только началась.. При этом мы не указали вложенную иерархию – коллекционных предметов. Как я уже говорил, делать иерархию структур в DataTable можно, но это ломает юзабилити для тех, кто хочет редактировать их из экселя, и в итоге на эксель все равно забивают и продолжают работать только через редактор. Так или иначе DataTable не дает достаточно гибкости для организации сложных данных. Все дополнительные значения, ссылки на другие значения в других таблицах и прочее сказываются и на коде, который работает с ними.
Поэтому в подобных случаях я всячески рекомендую пользоваться DataAsset’ами. На экране вы видите пример иерархичной структуры данных. Здесь я уже могу использовать TМap для вложенных типов, что также придает удобство в работе с данными.
Поэтому в подобных случаях я всячески рекомендую пользоваться DataAsset’ами. На экране вы видите пример иерархичной структуры данных. Здесь я уже могу использовать TМap для вложенных типов, что также придает удобство в работе с данными. При желании можно сделать каждый из вложенных элементов DataAsset’ом и вместо работы с одной иерархией в одном окне, просто передавать ссылки на вложенные объекты. Что позволит работать одновременно с несколькими объектами из одной иерархии.
Это пример развернутой структуры первого уровня из первой главы. Здесь мне уже не приходится заводить с десяток переменных для описания одного возможного требования на открытие уровня.
Все требования наследуются от общего типа, поэтому один селектор предлагает на выбор все возможные варианты. Есть возможность установки композитного требования, которое может включать в себя несколько других и выдавать тру только в случае если каждое из требований выполненно.
Здесь мы видим подиерархию коллекционных предметы – имя, иконка, возможные позиции. Сейчас это обычная структура, но по мере развития игры ее можно заменить на конкретный класс описывающий CollectableItem от которого будет наследовано множество коллекционных предметов и мы сможем выбирать их по типу с помощью селектора, также как это происходит с требованиями.
На данном слайде представлен пример работы с датаассетами из блупринтов. Как видите мы просто получаем объект по ссылке и работаем с ним, как с любым другим объектам, что значительно повышает удобство работы с данными, особенно в плюсах.Можно даже отойти от правила чистых данных и реализовать бизнеслогику в этих классах. При этом для геймдизайнера это по прежнему будет объект данных и ничем не усложнит ему работу с ними. Требования как раз пример такого класса, у которого можно вызвать функцию Check, которая сама обратится к текущей игровой сессии и проверит, было ли выполненно условие или нет.
Примерно разобравшись со стандартными контейнерами движка, самое время разобрать примеры организации Data Driven геймплея для которого характерна динамиская генерация. Для подобного разработчиками принято использовать различные паттерны и подходы, которые на полную мощь используют возможности языка C++. Вот только к сожалению в C++ библиотеке UE4 есть определенные ограничения.
Это тот неполный список ограничений с которыми я сталкивался, когда начал работать с UE4 в попытке применить свои привычные паттерны, такие как Фабричный метод и Абстрактная фабрика
И на последок я хочу рассказать о синхронизации данных с помощью DataAsset: В отличии от DataTable, значения описанные в DataAsset можно менять в рантайме, что позволяет использовать их даже для системы сейвов, но также их можно полностью обновлять например с помощью полученного с сервера Json объекта. DataAsset можно, как сериализовать, так и десериализовывать с помощью встроенной в движок Json библиотеки. Это как раз и повляет осуществлять удаленный баланс вашей игры сразу после релиза в сторе, где следующий апдейт сможет выйти в лучшем случае через пару дней.
Таким же образом работает синхронизация пользовательских профилей в новом Unreal Tournament. Правда их код является частью проприетарной MasterControlProgram и довольно редко обрывками попадает в master ветку на GitHub’e.