Skip to content

High-level architectural framework for Unity Engine

Notifications You must be signed in to change notification settings

artUSUN/ModuleFramework

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

24 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

ModuleFramework

High-level architectural framework for Unity Engine

Длинное описание

Фреймворк, который я сделал для личного пользования. Используется на проекте Dungeon Hero

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

Также облегчает работу с ecs-фреймворком Morpeh, поскольку инкапсулирует в себе логику регистрации и порядка вызова всех ecs-систем.

При этом не диктует определенного подхода по работе с самим собой. Модули могут быть любого размера. Можно выстраивать иерархии из модулей. Нет привязки к сценам в unity, соответственно, можно выстраивать работу со сценами исходя из надобностей конкретного проекта.

Зависимости, необходимые для работы фреймворка

  • ECS-фреймворк Morpeh

  • Unity Addressables (добавятся автоматически)

  • UniTask

  • DI-контейнер VContainer

Ссылки для быстрого добавления через Package Manager:

Как установить?

  1. Установить все необходимые зависимости

  2. Добавить ModuleFramework как git-зависимость через Package Manager в Unity:

https://github.com/artUSUN/ModuleFramework.git

Начало работы

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

Чтобы все заработало, нужно сделать два важных пункта:

  1. Написать реализацию для интерфейса IEcsSystemsOrderRepository
  2. Зарегистрировать в DI-контейнере класс EcsSystemsOrderResolver и реализацию интерфейса IEcsSystemsOrderRepository из первого пункта

В текущем репозитории в папке Framework.Examples можно найти два класса: EcsSystemsOrderRepository и ModuleFrameworkInstaller.

Первый класс - это предлагаемая мною реализация для интерфейса IEcsSystemsOrderRepository. Можно скопировать ее в свой код и использовать, добавляя в словарь Order системы из всей игры, которым необходим переопределенный порядок выполнения.

Второй класс - это пример Installer’a для регистрации обоих вышеупомянутых классов. Можно скопировать себе весь класс и вызвать метод Install из корневого модуля. При желании можно сделать собственную реализацию IEcsSystemsOrderRepository и/или регистрировать его и EcsSystemsOrderRepository в разных модулях. Это может быть полезно, например, если вы хотите разбить каждый модуль на отдельную asmdef сборку. Или если хотите держать порядок выполнения системы поближе к каждому отдельному модулю.

Я обычно держу этот список в одном месте, поскольку:

  1. Удобно видеть и менять в одном месте порядок выполнения для всех ecs-систем в игре в одном месте.
  2. Систем, для которых действительно будет важен порядок выполнения, скорее всего будет не более 10-20% от общего кол-ва, а значит словарь не разрастется до совсем космических размеров.

Warning

Для каждой системы с заданным порядком выполнения должен быть свой уникальный порядок. Одинаковый порядок для двух разных систем использовать нельзя, ровно как использовать индексы с 0 до IEcsSystemsOrderRepository.GetLastDefaultOrder();. Если реализуете множество IEcsSystemsOrderRepository, то возвращайте в методе GetLastDefaultOrder() общую для всех константу.

Создание модуля

Модуль создается путем наследования от класса Module или ModuleWithSettings<TSettings>. Разница заключается в том, что необходимы ли какие-либо зависимости в виде ассетов (настройки) для этого модуля или нет.

  • Наследование от Module, когда настройки не нужны.
  • От ModuleWithSettings<TSettings>, когда нужны. По-сути ModuleWithSettings<TSettings> сам наследуется от Module и инкапсулирует в себе работу с абстрактной SO-шкой TSettings, которая будет грузиться из Addressable.

Обязательных методов для реализации у обоих вариантов нет, переопредляйте только то, что будет нужно:

  • OnLoad(), OnActivate(), OnDeactivate(), OnUnload() - вызываются на соответствующее действие с модулем. Возвращаемый тип во всех случаях UniTask, что позволяет выполнять внутри асинхронную логику и не блокировать основной поток долгой операцией.

  • UniTask BeforeScopeCreate() - асинхронный метод, который вызывается перед сборкой DI-контейнера. Нужен, если хочется выполнить какую-то логику и затем зарегистрировать результат в контейнере. По такому принципу и работает ModuleWithSettings<TSettings>. В случае, если метод вызывается при наследовании от ModuleWithSettings<TSettings> - необходимо обязательно вызвать базовый класс.

  • void InstallDependenciesToModule(IContainerBuilder builder) - метод для регистрации классов и инстансов в DI-контейнере. При наследовании от ModuleWithSettings<TSettings> обязательно вызывать базовый метод. Либо можно переопределить метод InstallDependencies(IContainerBuilder builder), который работает абсолютно аналогично, но без необходимости вызывать базовый метод.

Основные способы регистрации в DI-контейнере:

builder.Register<T>(Lifetime.Singleton);
builder.Register<ISystem, TSystem>(Lifetime.Singleton);
builder.RegisterInstance(TInstance);

Другие способы и прочие подробности см. в документации VContainer

Работа с модулями

  • Для загрузки модуля необходимо вызвать статический метод:
UniTask<TModule> Module.Load<TModule>(Module parent, bool activateAfterLoad = false)

Параметр parent может быть null. Тогда модуль будет являться корневым. На загрузке модуля собирается DI-контейнер и загружаются необходимые ресурсы.

  • Активация, деактивация и выгрузка модуля происходит путем вызова соответствующих методов у конкретного экземпляра класса Module. При вызове деактивации/выгрузке родителей - все активные/загруженные модули-наследники будут выключены и/или выгружены.

При включении модуля начинают работать ecs системы. При выключении, соответственно, перестают.

Для всех классов, зарегистрированных в модуле, можно определить дополнительную логику путем реализации соответствующего интерфейса (IModuleLoadListener, IModuleUnloadListener, IModuleActivationListener, IModuleDeactivationListener)

Как использовать фреймворк?

Здесь нет четких рамок или правил. Опишу, как я обычно его использую:

  1. Создаю один корневой General-модуль. Он загружается и запускается один раз на старте и никогда не выключается/выгружается. Такой global-контекст на всю игру.
  2. Далее выделяю два модуля: Core и Meta. В них при необходимости будут запускаться другие модули.
  3. Пишу игровую логику, деля ее по фичам. Модуль инпута, модуль нанесения урона, модуль подключения к серверу, модуль боевого пропуска, модуль инвентаря и т.д.
  4. Модули фичей не обязательно запускать на загрузке модуля-родителя. Например можно не грузить боевой пропуск, пока игрок не нажал на кнопку боевого пропуска. Или не грузить модуль езды на драконе, если игрок еще не получил дракона.
  5. Сцена у меня обычно вообще является пустой, все необходимое динамически создается. При этом смена сцены присутствует, чтобы не менеджерить удаление объектов. Например по окончанию сессии в core и переходу в meta. То же самое касается и World’а в морпехе. Его обычно тоже пересоздаю при необходимости.

About

High-level architectural framework for Unity Engine

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages