Ten-Hut. Про архитектуру кода #4

Гит проекта. Что планирую доделать и добавить. А также немного про сериализацию того, что в Юнити по умолчанию не сериализуется. В частности, сцен.

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

Перед началом работы над проектом я, помимо того, что успел реализовать, планировал сделать мультиплатформенность (ПК и андроид), второй язык (чтобы можно было переключать тексты между русским и английским) и лидерборды (через социальные сети).

В общем, буду постепенно это всё внедрять. Начну с языка, потом - управление под андроид. Ну и соцсети, как минимум, попробую.

И ещё вещь, которая осталась недоделанной - сериализация сцен.

Сцены в проекте загружаются аддитивно. Т.е. не как на начальных курсах по Юнити обычно рассказывают, что, мол, сцена - ну это типа один уровень в игре. Нет. Аудиоплеер лежит на своей отдельной сцене, панель здоровья - на своей отдельной сцене, панель, которая вылезает при проигрыше, - отдельная сцена. Сцены загружаются/выгружаются по необходимости, через методы вроде SceneManager.LoadSceneAsync. Штука удобная, позволяет отслеживать степень загрузки, выкидывает событие, когда загрузка закончилась, и ещё много всего полезного.

Встаёт вопрос, как задать те сцены, которые нужно загружать/выгружать в отдельных сегментах игры. На первый взгляд кажется, что всё просто. Создаём какой-нибудь GameSegmentsEnum, где перечисляем все глобальные состояния игры, т.е. TitleScreen, MainMenu, Gameplay, GameoverScreen и т.д. Делаем скриптабл-обжект с настройками загрузки сцен и кладём туда [SerializeField] Dictionary<GameSegmentsEnum, Scene[]>, где и отмечаем, какому сегменту какие сцены нужны.

Однако не тут-то было. Проблема А - Dictionary в Юнити по умолчанию не сериализуется. Проблема Б - сцены в редакторе и в билде загружаются по-разному. При попытке сбилдить проект, в котором вы через [SerializeField] положили куда-либо тип Scene, вы можете словить ошибку вида "Простите, но Юнити в душе не знает, что это за Scene такой, и вообще видит его впервые, так что хрен вам с маслом, а не билд". А чтобы всё заработало и сцена загрузилась, методу SceneManager.LoadSceneAsync нужно передать либо номер сцены в билде из Build Settings, либо string с путём к файлу сцены в папке.

Решается это всё, как обычно, либо собственными руками, либо поиском сторонних ассетов.

Для тренировки мне хочется закодить это собственноручно. Тем более что с Editor-кодом у меня всё довольно плохо, ну вот как раз и повод подучиться.

Итого задача номер раз - сделать Scriptable Object с сериализуемым Dictionary. Задача номер два - сделать так, чтобы соответствующий скриптабл-обжект-ассет выглядел в редакторе нормально. Задача номер три - сделать так, чтобы в этот Dictionary можно было в редакторе закинуть ассет сцены, и при этом чтобы в билде всё правильно определилось и работало.

Но пока решена только задача номер раз, т.е. готов Scriptable Object, выстраивающий Dictionary из двух массивов с SerializeField, и основную часть кода я для этого просто взял с сайта документации Юнити. Причём он написан так, что в процессе заполнения полей может выдавать ошибки, поэтому и его тоже стоит поправить:

public abstract class DictionarySO_Base<TK,TV> : ScriptableObject, ISerializationCallbackReceiver { private Dictionary<TK, TV> _dictionary = new Dictionary<TK, TV>();//дикшнари [SerializeField] private TK[] _keys;//массив ключей [SerializeField] private TV[] _values;//массив значений public void OnBeforeSerialize() { //очищаем дикшнари _dictionary.Clear(); } public void OnAfterDeserialize() { //заполняем дикшнари так, //чтобы элементы массива ключей и элементы массива значений //стали в дикшнари ключами и значениями, соответственно for (int i = 0; i != Math.Min(_keys.Length, _values.Length); i++) { _dictionary.Add(_keys[i], _values[i]); } } //вернуть значение по ключу public TV GetValueByKey(TK key) { return _dictionary[key]; } }

Для решения задачи номер два нужно покопаться во всяких CustomPropertyDrawer-ах и т.п. А для решения задачи номер три нужно сделать так, чтобы во время сериализации-десериализации ассета типа Scene в скриптабл-обжект-ассете там сцена сохранялась не в виде того же ассета Scene, а в виде string с путём к сцене и её названием, т.е. тем, что потребуется в итоге в билде.

Пока же в проекте сцены в Dictionary обозначаются просто как их индексы из списка сцен в Build Settings.

Ну... на самом деле это всё, о чём я хотел рассказать. Заходите в гит, смотрите проект, кому интересно. Я открыт для отзывов, предложений и советов. А так - наверно всем пока, до следующего фестиваля.

55
Начать дискуссию