[Unity, C#, Разработка игр] Как мы переосмыслили работу со сценами в Unity

Автор Сообщение
news_bot ®

Стаж: 6 лет 9 месяцев
Сообщений: 27286

Создавать темы news_bot ® написал(а)
17-Окт-2020 16:30

Unity, как движок, имеет ряд недостатков, но которые благодаря возможностям для кастомизации и инструментам для кодогенерации, можно решить. Сейчас я вам расскажу о том, как мы написали плагин для Unity на основе пост-процессинга проектов и кодогенератора CodeDom.ПроблемаВ Unity загрузка сцен происходит через строковой идентификатор. Он не стабильный, а это означает, что он легко изменяем без явных последствий. Например, при переименовании сцены всё полетит, а выяснится это только в самом конце на этапе выполнения. Проблема обнаруживается быстро на часто используемых сценах, но может быть трудно обнаруживаемая, если речь идёт о небольших additive сценах или сценах, которые используются редко. РешениеПри добавлении сцены в проект, генерируется одноимённый класс с методом Load. Если мы добавим сцену Menu, то в проекте сгенерируется класс Menu и в дальнейшем мы можем запустить сцену следующим образом:
Menu.Load();
Да, статический метод - это не лучшая компоновка. Но мне показалось это лаконичным и удобным дизайном. Генерация происходит автоматически, исходный код такого класса:
//------------------------------------------------------------------------------
// <auto-generated>
//     This code was generated by a tool.
//     Runtime Version:4.0.30319.42000
//
//     Changes to this file may cause incorrect behavior and will be lost if
//     the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace IJunior.TypedScenes
{
    public class Menu : TypedScene
    {
        private const string GUID = "a3ac3ba38209c7744b9e05301cbfa453";
        public static void Load()
        {
            LoadScene(GUID);
        }
    }
}
По-хорошему, класс должен быть статическим, так как не предполагается создание экземпляра из него. Это ошибка, которую мы исправим. Как видно уже из этого фрагмента, кода мы цепляемся не к имени, а к GUID сцены, что является более надежным. Сам базовый класс выглядит вот так:
namespace IJunior.TypedScenes
{
    public abstract class TypedScene
    {
        protected static void LoadScene(string guid)
        {
            var path = AssetDatabase.GUIDToAssetPath(guid);
            SceneManager.LoadScene(path);
        }
        protected static void LoadScene<T>(string guid, T argument)
        {
            var path = AssetDatabase.GUIDToAssetPath(guid);
            UnityAction<Scene, Scene> handler = null;
            handler = (from, to) =>
            {
                if (to.name == Path.GetFileNameWithoutExtension(path))
                {
                    SceneManager.activeSceneChanged -= handler;
                    HandleSceneLoaders(argument);
                }
            };
            SceneManager.activeSceneChanged += handler;
            SceneManager.LoadScene(path);
        }
        private static void HandleSceneLoaders<T>(T loadingModel)
        {
            foreach (var rootObjects in SceneManager.GetActiveScene().GetRootGameObjects())
            {
                foreach (var handler in rootObjects.GetComponentsInChildren<ISceneLoadHandler<T>>())
                {
                    handler.OnSceneLoaded(loadingModel);
                }
            }
        }
    }
}
В этой реализации видна ещё одна фишка - передача параметров сценам. Параллельно с решением первой проблемы - избавить код от строковых идентификаторов сцены (в том числе и от констант, которые нужно обновлять вручную), мы добавили еще и параметризацию сцен. Теперь сцена может задекларировать список параметров, а вызывающий код должен передать для них аргументы. Например, сцена Game хочет получить перечисление, содержащее всех игроков и при инициализации добавить для них аватаров. В таком случае мы можем сами создать такой компонент.
using IJunior.TypedScenes;
using System.Collections.Generic;
using UnityEngine;
public class GameLoadHandler : MonoBehaviour, ISceneLoadHandler<IEnumerable<Player>>
{
    public void OnSceneLoaded(IEnumerable<Player> players)
    {
        foreach (var player in players)
        {
            //make avatars
        }
    }
}
После размещения его на сцене и последующим её сохранением, генератор добавит в класс сцены метода запуска сцены с обязательной передачей перечисления игроков.
//------------------------------------------------------------------------------
// <auto-generated>
//     This code was generated by a tool.
//     Runtime Version:4.0.30319.42000
//
//     Changes to this file may cause incorrect behavior and will be lost if
//     the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace IJunior.TypedScenes
{
    public class Game : TypedScene
    {
        private const string GUID = "976661b7057d74e41abb6eb799024ada";
        public static void Load(System.Collections.Generic.IEnumerable<Player> argument)
        {
            LoadScene(GUID, argument);
        }
    }
}
В данный момент реализована возможность перегрузки обработчиков. Т.е. если на сцене будет N обработчиков с разными параметрами, под них создастся N методов запуска. Также не запрещается наличие нескольких компонентов-обработчиков с одинаковыми параметрами. Это не фишка, а скорее недоработка, так как такой функционал быстрее создаст путаницу, нежели будет полезен.А почему не сделать через N? Первую версию плагина я осветил на своём YouTube канале в этом видео.Извините, данный ресурс не поддреживается. :( Там задали ряд вопросов и предложили альтернативные решения. Есть интересные подходы, которые имеют право на жизнь, а есть откровенно идиотские, которые я хочу разобрать. Чем плох статический класс с полями, через которые передаются данные для сцены?Нередко встречаю и такое. Речь идёт о классе по типу этого:
public class GameArguments
{
    public IEnumerable<Player> Players { get; set; }
}
А уже внутри сцены, вероятно, будет группа компонентов, которая достаёт данные из свойств. Основная проблема в том, что нет формализации: при загрузки сцены не совсем ясно, что нам нужно предоставить для корректной работы. Получателей в целом определить будет проще, так как мы можем воспользоваться поиском по ссылке. Ну и опять же сцену придётся запускать по ID или имени. Чем плох PlayerPerfsПредлагали и такой экзотический вариант. Можно было бы начать с того, что PlayerPrefs вообще не предназначен для передачи значений внутри одного инстанса. Этим можно было бы и закончить, но продолжим критику тем, что вам также придётся работать с неформальными строковыми идентификаторами параметров. Параллель с ASPNetМне хотелось получить что-то схожее с строго типизированными View из ASPNet Core. Мы считаем плохим тоном использовать ViewData и стараемся определять ViewModel. В Unity хочется что-то такого же толка с теми же преимуществами.Отличие Unity в первую очередь в том, что сцена - это обычно более громоздкое предприятие, нежели View в ASPNet. Это решается разбивкой одной сцены на несколько подсцен с режимом загрузки Additive (наш плагин, к слову, его поддерживает), что позволяет скомпоновать сцену из сцен поменьше со своими более атомарными моделями. Но такой подход не очень распространён, к сожалению, и на это, я думаю, есть свои причины. Где скачать Плагин мы сделали в паре с Владиславом Койдо в рамках Proof-of-concept. Он ещё не стабилен и не обкатан как следует, но с ним уже можно поиграться. Репозиторий на GitHub - https://github.com/HolyMonkey/unity-typed-scenesЕсли вам интересно, я попрошу Владислава в следующей статье рассказать, как он работал с Code Dom в Unity и как работать с пост-процессингом на примере того, что мы сегодня обсуждали.
===========
Источник:
habr.com
===========

Похожие новости: Теги для поиска: #_unity, #_c#, #_razrabotka_igr (Разработка игр), #_unity, #_unity3d, #_csharp, #_gamedev, #_unity, #_c#, #_razrabotka_igr (
Разработка игр
)
Профиль  ЛС 
Показать сообщения:     

Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы

Текущее время: 26-Ноя 14:00
Часовой пояс: UTC + 5