Введение
Разработка игр в Unity, особенно средних и крупных проектов, быстро приводит к сложному переплетению классов и компонентов. Представьте:
PlayerControllerзависит отInventorySystem.InventorySystemзависит отItemDatabaseиSaveLoadManager.SaveLoadManagerзависит отFileSystemServiceиEncryptionService.EncryptionServiceиспользует настройки изGameSettings.
Создавая PlayerController вручную (new PlayerController()), вы вынуждены также создавать InventorySystem, потом создавать ItemDatabase и SaveLoadSystem, потом создавать FileSystemService, EncryptionService и получить доступ к GameSettings... Короче, это превращается в "спагетти-код", где классы жёстко связаны. Изменение одной зависимости вызывает волну правок по всему коду. Тестирование становиться невозможным, так как не возможно изолировать класс, не создавая всю гору его зависимостей.
Но выход есть!
Dependency Injection
Dependency Injection (DI, внедрение зависимостей) - Паттерн проектирования, который решает описанные проблемы.
Он делает:
Инверсия управления (IoC): Класс не создаёт свои зависимости самостоятельно. Вместо этого, он объявляет, что они ему нужны (через конструктор, публичные поля или методы с атрибутами).
Внешнее предоставление: Ответственность за создание экземпляров зависимостей и их "впрыскивание" (injection) в нужные классы берёт на себя внешний компонент - Контейнер.
Слабая связанность: Классы знают только об интерфейсах или абстрактных типах своих зависимостей, а не о конкретных реализациях. Это позволяет легко заменять реализации.
Zenject
Zenject (позже Extenject) - Фреймворк с открытым исходным кодом, специально разработанный для интеграции паттерна DI в среду Unity. Он предоставляет способ управления зависимостями, жизненным циклом объектов и архитектурой приложения.
Ключевые компоненты и концепции:
-
Сердце системы это глобальный или сценарный реестр, который знает:
Что (какой интерфейс, абстрактный класс или конкретный тип) нужно предоставить.
Как создать экземпляр этого "чего-то" (самостоятельно / через фабрику / из префаба).
Кому и когда это нужно предоставить.
-
Привязка (Binding):
Процесс регистрации типов и их зависимостей в контейнере.
-
Основные методы:
Bind<ТипИнтерфейса>().To<КонкретнаяРеализация>().AsSingle(); - Привязка интерфейса к конкретному классу.AsSingle()- "Один экземпляр - один контейнер",To<>()- Привязка интерфейса к реализации. Интерфейс вBind<>()привязывается к реализации вTo<>().Bind<Тип>().FromNew();- Создавать новый экземпляр каждый раз, когда запрашивается.Bind<Тип>().FromInstance(myExistingObject);- Использовать существующий заранее созданный экземпляр.Bind<Тип>().FromComponentInNewPrefab(playerPrefab);- Создавать экземпляр из префаба (для MonoBehaviour).Bind<Тип>().FromComponentInHierarchy();- Найти существующий компонент типаТипна сцене.Bind<Тип>().To<Реализация>().AsTransient();- Создавать новый экземпляр при каждом запросе (по умолчанию).Bind<Тип>().WithId("SpecialID").To ...- Привязка с идентификатором для различения зависимостей одного типа.
Цепочки: Методы можно цепочкой добавлять для настройки (
AsSingle(),NonLaze(),WhenInjectedInto<SomeType>(),WithArguments(42, "hello")и т.д.).
-
Внедрение (Injection):
-
Конструктор: Самый предпочтительный способ. Zenject автоматический разрешает параметры конструктора.

-
Поля (с атрибутом
[Inject]):
-
Методы (с атрибутом
[Inject]): Вызываются после создания / инъекции полей
Свойства (аналогично полям,
[Inject]).
-
-
Установщики (Installers):
Классы, наследующие от
MonoInstaller(для привязок в сцене) илиInstaller(для не-MonoBehaviour привязок).В методе
InstallBindings()описываются все привязки для определённого контекста (проекта, сцены, подсистемы).-
Иерархия и модульность:
Проектные (ProjectContext): Установщик, прикреплённый к префабу
ProjectContext(создаётся автоматический при первом запуске). Привязки здесь доступны во всех сценах. Идеально для глобальных сервисов (сохранение, аудио, настройки, сеть).Сценарные (SceneContext): Установщик, прикреплённый к объекту
SceneContextв сцене. Привязки здесь доступны только в текущей сцене. Обычно ссылается на ProjectContext черезContainer.Inherit = true.Подконтексты (SubContainers): Позволяют создавать изолированные области зависимостей внутри сцены (например, для UI-окна).
-
Пример простого установщика:

-
Время жизни (Scope) и синглтоны:
AsTransient(): Новый экземпляр при каждом запросе (по умолчанию).AsSingle(): Один экземпляр на контейнер (и его дочерние контейнеры, если не указано иное).AsCached(): АналогAsSingle(), но для фабрик и пулов (один экземпляр на вызов фабрики / пула).FromResolve()/FromResolveGetter(): Связывает зависимость с другой зависимостью, уже зарегистрированной в контейнере.NonLazy(): Заставляет контейнер создать экземпляр немедленно при старте, а не при первом запросе.
-
Фабрики (Factories):
Проблема: Создание объектов, требующих параметров при создании (например, враг с определённым уровнем и позицией).
-
Решение: Zenject генерирует классы фабрик автоматический.

Memory Pools (
FromPoolableMemoryPool): Расширение фабрик для пулинга объектов (переиспользования), критически важное для производительности.
-
Сигналы (Signals):
Проблема: Глобальные события через
Actionили UnityEvents приводят к жёстким связям и сложности откладки.-
Решение: Система событий, основанная на DI. Публикуются и подписываются через контейнер.

Преимущества: Типобезопасность, централизация, возможность передачи данных в сигнале, автоматическая отписка при уничтожении подписчика.
Практическая интеграция в Unity проект
Установка: Через Package Manager (Git URL: https://github.com/modesttree/Zenject.git?path=UnityProject/Assets/Plugins/Zenject) или через Asset Store (Extenject).
-
Создание ProjectContext:
Создайте префаб
ProjectContext(обычно в папкеResources).Добавьте компонент
ProjectContext.Добавьте ваши глобальные установщики в список
InstallersнаProjectContext.
-
Создание SceneContext:
На каждую сцену добавьте объект
SceneContext.В его списке
Installersдобавьте установщики, специфичные для этой сцены.Убедитесь, что
Parent Containerссылается наProjectContext(обычно настроено по умолчанию).
-
Написание установщиков (
Installers):Создайте классы-установщики для разных областей ответственности.
В
InstallBindings()используйте методыBind<>().To<>().As...()для регистрации зависимостей.
-
Рефакторинг классов:
Уберите ручное создание зависимостей (
new,GetComponent,FindObjectOfType).Объявите зависимости через конструктор, поля или методы с
[Inject].Опирайтесь на интерфейсы!
Zenject vs Ручное управление
Ручное управление:

Жёсткая связь.
Сложно тестировать.
Изменение
SaveLoadManagerтребует правкиPlayerController.Код создания размазан по логике.
Zenject:

PlayerControllerничего не знает о созданииInventorySystemили его внутренних зависимостях.Зависимость предоставляется контейнером автоматически.
Легко заменить
FileSystemServiceнаCloudeSaveServiceтолько в установщике.Легко протестировать
PlayerController, подсунув мокIInventorySystem.
Словарик терминов
Внедрение зависимостей (Dependency Injection - DI) - Паттерн проектирования, при котором объект получает свои зависимости извне (через конструктор, поля, методы), а не создаёт их сам или ищет явно.
Инверсия управления (Inversion of Control - IoC) - Принцип, при котором управление созданием объектов и потоком выполнения передаётся внешнему фреймворку (контейнеру DI), а не остаётся внутри самих объектов.
Контейнер (DiContainer) - Объект, который знает какие зависимости зарегистрированы (
Bind); как их создавать (из префаба / черезnew/ из существующего экземпляра и т.д); их время жизни (Scope); и автоматически разрешает зависимости при создании объектов, которыми управляет.Привязка (Binding) - Процесс регистрации типа (интерфейса / класса) и его конкретной реализации или способа создания в контейнере. Делается в установщиках (
Installers).Установщик (Installer): Класс (обычно наследующий от
MonoInstaller), в котором выполняется конфигурация контейнера через методInstallBindings(). Содержит вызовBind(). Могут быть проектные (ProjectContext) и сценарные (SceneContext).ProjectContext - Специальный префаб (обычно в папке
Resources), создаваемый при старте игры. Содержит глобальные контейнеры и установщики, чьи привязки доступны во всех сценах. Хранит глобальные синглтоны (AsSingle()).SceneContext - Компонент, добавляемый в каждую сцену. Содержит сценарный контейнер и установщики, специфичные для данной сцены. Обычно наследует привязки от
ProjectContext(Container.Inherit = true).Внедрение (Injection) - Процесс, при котором контейнер автоматически передаёт экземпляры зависимостей в объект.
Время жизни / Скоуп (Scope) - Определяет, как долго живёт экземпляр, созданный контейнером, и как часто он создаётся заново.
AsTransient(): Создается новый экземпляр каждый раз, когда запрашивается зависимость (по умолчанию). Короткая жизнь.AsSingle(): Создается один экземпляр на контейнер (и его дочерние контейнеры). Классический "синглтон" в рамках своего скоупа (проект, сцена, подконтейнер). Долгая жизнь.AsCached(): Создается один экземпляр, но привязанный к конкретному "источнику" (например, к конкретному вызову фабрики). Используется в фабриках и пулах.Фабрика (Factory) - Объект, отвечающий за создание других объектов, особенно когда для создания нужны параметры. Zenject автоматически генерирует фабрики на основе интерфейса
PlaceholderFactory<Т, Парам1, Парам2, ...>. Позволяет создавать объекты с зависимостями, передавая параметры.Пул (Pool) - Механизм для переиспользования объектов (часто дорогих в создании). Расширяет концепцию фабрики (
FromPoolableMemoryPool). Уменьшает нагрузку на сборщик мусора (GC). Критично для производительности.Сигналы (Signals) - Система событий, основанная на DI. Альтернативна глобальным
ActionилиUnityEvent. Позволяет объявлять типизированные сообщения (struct PlayerDiedSignal), публиковать их черезSignalBus.Fire()и подписываться на них черезSignalBus.Subscribe(). Типобезопасна, централизована, упрощает отписку.SignalBus - Центральный объект, через который происходит публикация (
Fire) и подписка на сигналы.Подконтекст (Sub-Container) - Изолированный "дочерний" контейнер внутри основного сценарного контейнера. Позволяет создавать области с собственным набором зависимостей. Управляется через
SubContainerCreator.Резолюция (Resolving) - Процесс, в ходе которого контейнер находит или создаёт экземпляр зависимости, запрошенной классом.
Граф объектов (Object Graph): Иерархия объектов, созданных контейнером, где каждый объект получает свои зависимости, разрешенные контейнером. Zenject автоматически строит этот граф при старте сцены или при создании корневого объекта.
Lazy опция привязки - (По умолчанию) Объект создаётся только при первом запросе его как зависимости.
NonLaze опция привязки - Объект создаётся немедленно при инициализации контейнера (старте сцены/проекта), даже если на него ещё не ссылок. Полезно для сервисов, которые должны быть готовы сразу.
Id (Идентификатор) - Позволяет различать несколько привязок одного и того же типа. Используется с
Bind<Тип>().WithId("имя").To...и[Inject(Id = "имя")].Интерфейс (Interface) - Ключевая абстракция в DI. Классы должны зависеть от интерфейсов (
IInventory, IAudioService), а не от конкретных реализаций (InventorySystem, UnityAudioService). Это основа слабой связанности и возможности замены реализаций.
Если понравилась статья - рекомендую подписаться на телеграм‑канал NetIntel. Там вы сможете найти множество полезных материалов по IT и разработке!
Jijiki
спасибо, теперь понял зачем это нужно, тоесть цепочка связи по-сути иерархия 1 настройкой цепочки, посмотрел он и в java есть как раз то что нужно )