Туториалы для геймдева #0 StaticData
Привет, Пикабу!
Я тут обещал какое-то время назад серию туториалов для геймдева, ну в общем вот первый пост)
Требования для урока:
1)Мозг
2)Умение читать ОЧЕНЬ ОЧЕНЬ ОЧЕНЬ много текста
3)Умение читать доки
4)c#
5)Json
6)Примеры будут на Unity, т.к. мой проект на нём, но про альтернативу для Unreal расскажу
StaticData. Что это? Зачем оно нужно?
В любом проекте чуть больше "Hello world" появляются объекты которые неплохо бы кастомизировать. Будь то параметры юнитов, оружия, да даже просто ящики разной массы для шутерков.
Если вы составляете карту руками, то вам ещё допустимо создать несколько шаблонов с захардкоженными параметрами и расставить их на сцене. (но всё равно не надо) А вот если вы захотите чуть более сложную архитектуру, или процедурную генерацию, то тут без подгрузки данных из кода не обойтись.
Первым порывом будет написать что-то такое:
\\код не мой, я просто разместил объяву
Код взят из проекта одного из школьников, у которых я веду проекты. По ушам за той код уже отхвачено)
obj1 = new Class();
obj.a = 1;
obj.b = 2;
obj2 = new Class();
obj2.a = obj1.a * 1.05;
obj2.b = obj1.b * 4;
Ну вы поняли...
Это пример того как делать НЕ надо. В приличном обществе любую константу которая влияет на геймплей (а особенно на геймдизайновую его часть) надо бы хранить снаружи кода, чтобы просто изменив поле можно было бы подправить баланс игры.
Опционально, если при инициализации ваш клиент пытается скачать таблицу из облака и обновить баланс игры. Т.е. для обновления баланса игроку надо будет выкачать пару МБ, а не новый билд весом пару сотен метров (некоторые движки не позволяют патчить части себя, а только полностью менять билд).
Так что такое StaticData? Это эта самая таблица, которая иницалзирует сама себя и хранит все значения которые не зависят от действий игрока. А главное, к ней можно обращаться в любой момент.
Ну а теперь ОДНА ИЗ ВОЗМОЖНЫХ реализаций этого.
Unreal engine - Класс Data Table. Идите гуглите) Таблички можно создавать прямо в редакторе, а можно подгружать из csv. Очень удобно и всё такое. И мануалы простые легко гуглятся.
Unity - Тут я собственно на примере буду рассказывать как оно устроено и работает. На примере своего проекта.
Сначала немного о базовых механиках построения таблиц:
KeyName - наше основное значение. Это id. Уникальная строковая переменая, по которой всегда можно найти в StaticData объект.
Каждый лист таблицы занимает или список классов (строка - экземпляр\шаблон объекта), Либо один цельный объект. Примером второго является например Settings. Не настройки игрока, а базовые параметры игры. Для кода сверху, это были бы масса и радиус земли.
Имена переменных должны совпдать с аналогичными переменными в вашем коде.
Итак, как же у меня в игре устроен ОТРЯД СОЛДАТ, а вот так:
Лист называется SquadArchtype
keyName - ключевое имя отряда (по нему например более глобальный класс Company (армия) тащит свои составляющие. Не буду показывать, чтобы не перегружать вас) Дальше идут типы отряда, 6 юнитов, флаг может ли этот отряд быть использован игроком и сила для расчёта мощи армии противника.
Давайте внимательнее посмотрим на юнитов (лист UnitArchtype)
Всё ещё куча ключевых слов и никаких параметров. Заметьте по ключевым словам можно догадаться о чём тут вообще идёт речь! Тут можно заметить поля правой и левой руки, доспеха и активируемого предмета. baseUnit это класс базовых характеристик (а-ля раса, но не совсем). Взглянем например на оружие. (Weapon)
Ого чиселки! Но урона у оружия нет) Вы же не думали что всё так просто? В моей архтектуре удар оружием не отличается от фаербола. Это всё активные способности, некоторые могут иметь или не иметь урон. Глубже в таблицу уходить не будем, думаю смысл понятен)
НО:
Тут заметим интересную вещь. Изменилсь цвета. Зелёный - вотчина геймдизайнера (в данном случае меня) это баланс. Синяя - вотчина художника. Вот например в sprite записаны имена спрайтов которые подгружаются в соответсвующее оружие. О том как и зачем - в следующих урока, но поверьте, удобно)
Аналогичная архитектура записанная в коде заняла бы сотни строк, а меня баланс стало бы вообще нереально.
Самые внимательные могли заметить, что табличка хранится в гуглдоке. Итак, как же нам её оттуда достать.
Можно написать свою хорошую систему (так например у меня на работе, очень мощная штука, но такую систему можно и саму по себе продавать, зачем вам тогда игры). Дома я пользуюсь беслпатным плагином
https://assetstore.unity.com/packages/tools/utilities/google...
Что он делает - выкачивает гуглдок в Json и сохраняет в папке Resurces (для тех кто не шарит, в юнити это такая папка специальная, файлы откуда всегда уодят в билд, даже если на ни нет явных ссылок). Каждый листик сохраняет отдельно. Тут есть свои плюсы и минусы, но в большинстве случаев нам пофигу.
Качаем, ставим, читаем доки по заполнению таблиц, пользуемся.
Прочитали? Ну и славно, едем дальше.
Собственно Static Data.
Есть два варианта - простой и сложный (внезапно)
Сложный - создаём класс с namespace которое всем доступно, инициализируем и тягаем оттуда инфу.
Простой - создаём GameObject на сцене, вешаем на него скрипт StaticData, он при запуске делает тоже самое. Если объекту нужны данные он его пинает и тащит данные.
Чтобы объект загружался из Json, он должен быть серриализуем.
Вот примерно так выглядит класс Weapon (табличка сверху).
Теперь как же его собственно выцепить из таблички? Ну вот примерно так:
Тут сейчас будет немного магии, специально для юнити. Кому не интересно, листают до следующего перерыва на кофе.
Json - в данном случае просто строковая переменная.
LoadResourceTextFile - вспомогательная функция для выкачки текста. вот она:
Странное добавление по бокам - Json встроенный в юньку малось туповат, ему надо явно указывать что это список объектов, а не цельный объект.
Weapons - вспомогательный класс для серриализации. Требуется из-за того-же несовершенства Json. Вот он:
weapons - список оружий с которым позже мы будем уже работать. (точнее сам список в Weapons.list, но не суть).
Итак, мы почти у конца! Что же с этим делать? Ну для начала надо бы написать инициализатор который подтащит по ключевым именам (строкам) уже готовые и ранее серрилизованные объекты.
Легчайше!
Ну а вот собственно например инициализатор оружия:
Собсно что тут делать - дело ваше. У меня например только подтаскиваются пассивные и активные убилки из той же самой StaticData.
Итоги:
Мы получили МОЩНЕЙУЮ систему для хранения и доступа к данным. Которую из-за гуглдока также сложно потерять (ну если вы вдруг не умеете пользоваться гитом).
Пусть это не самый эффективный и аккуратный вариант реализации, но даже он сэкономит вам ТОННЫ времени) Удачи, до встречи в следующем туториале!
P.S. Да, наконец то! Думал у меня пальцы отвалятся пока пишу! Фуууух)
А почему бы и не ScriptableObject? Зачем делать богомерзкий GameObject.Find, если можен синглетон? Зачем JSON с кучей лишнего текстах, если можно CSV с меньшим объемом текста?
Как по мне эту задачу можно было решить в разы эффективнее:
1. Использовать ScriptableObject для хранения данных.
2. Использовать синглетон для доступа к данным.
3. Написать скрипт редактора, который по команде из главного меню будет делать WWW запрос к GoogleDocs, получать нужный лист в формате CSV, и потом парсить его в указанный ScriptableObject.
Таким образом мы всю мароку о заполнении данных переносим из Runtime в Editor.
По таким урокам только врагов в лес заводить :) Думаю они будут не очень популярны.
Может кодите вы и хорошо, но учить и кодить это разные вещи.
обещались мего уроки по разработке,
- одну треть говорим о табличках в гугл доке,
- одну треть об элементарной сериализации из джонсона, о которой в интернете 1000 и 1 урок написаны уже.
Обсираем код какого-то школьника. Который возможно писал 3д модель солнечной системы, а его значения массы и радиуса можно считать почти что константами. И их то точно не нужно в отдельный файл пихать.
Ты его столько не пишешь, не льсти себе.
Чтобы так выебываться, твои посты должны быть похожи на это https://habrahabr.ru/post/243471/
Фиг поймешь на кого ориентированы эти "уроки".
"Вот это вы пиздуйте сами прочитайте и вникайте, а сейчас вам элементарное расскажу"
p.s. и да, реально удобно писать с таким стилем скобок? это же пиздец.
Тому кто это придумал в visual studio на с# ставить по умолчанию, нужно руки оторвать.
Имхо, конкретно в юнити совсем не всегда целесообразно создавать внешние таблицы данных.
Сериализация в инспекторе это достаточно мощная вещь.Можно создать "референс класс", который будет содержать описания всех нужных игровых обьектов и данных( в том числе, хранить ссылки на прототипы префабов для спавна), а затем брать все это оттуда как-то так:
GlobalReference.Instance.GetCharacterDesc(CharType _type); //CharType is enum
Из очевидных преимуществ:
1)быстро поменял прямо в редакторе, запустил, работает. Тормознутость сериалайзд классов, в принципе, можно решить путем каста в не сериалайд парент класс.(у меня, правда, не возникало таких проблем)
2) Не нужно ничего читать и загружать - юнити сама подгрузит все объекты указанные где-либо в инспекторе.
3) Защита от школьника. Клиенту будет чуть сложнее править конфиги и читить.