Данная статья является попыткой переварить и упростить 2-часовое видео, материала созданного ребятами из “k-syndicate.school” и предоставленного (за что им огромное спасибо) в свободном доступе: K-Syndicate.
ZENJECT (позже EXTENJECT) – Фреймворк с открытым исходным кодом, специально разработанный для интеграции паттерна Dependency Injection в среду Unity. Он предоставляет способ управления зависимостями, жизненным циклом объектов и архитектурой приложения.
Установка ZENJECT (EXTENJECT)
Создадим новый тестовый проект Unity. Для установки ZENJECT можно использовать один из двух методов:
- Качаем с гита и устанавливаем в проект последнюю версию плагина github Zenject На данный момент текущая версия 9.2.0.
- Устанавливаем ZENJECT из Asset Store AssetStore EXTENJECT
Настройка ZENJECT (EXTENJECT)
- Создание ProjectContext:
- Создайте префаб
ProjectContext(обычно в папкеResources). - Добавьте компонент
ProjectContext. - Глобальные установщики (менеджеры аналитики, рекламы)будем добавлять в список
InstallersнаProjectContext.
- Создайте префаб
- Создание SceneContext:
- На каждую сцену из контекстного меню добавим объект
SceneContext. - В его списке
Installersбудем добавлять установщики специфичные для этой сцены.
- На каждую сцену из контекстного меню добавим объект
По сути, SceneContext нужен, чтобы инициализировать ( забутстрапить ) весь ZENJECT и происходит это следующим образом:
когда стартует SceneContext он ищет ProjectContext, если его нет, он создаётся и помечается как DontDestroyOnLoad; затем выполняются все регистрации в ProjectContext и лишь потом делает свои регистрации. (работает это благодаря “Script Execution Order”)
Созданный ProjectContext имеет несколько инсталлеров.

Инсталлер может регистрировать зависимости в контейнере.
Лучше убрать галочку с “Parent New Object Under Context”, так как мы не хотим чтоб все объекты которые мы будем инстанцировать через контейнер, были дочерними для ProjectContext.
Добавление инсталеров
Установщики (Installers):
- Классы, наследующие от
MonoInstaller(для привязок в сцене) илиInstaller(для не-MonoBehaviour привязок). - В методе
InstallBindings()описываются все привязки для определённого контекста (проекта, сцены, подсистемы). - Иерархия и модульность:
- Проектные (ProjectContex): Установщик, прикреплённый к префабу
ProjectContext(создаётся автоматический при первом запуске). Привязки здесь доступны во всех сценах. Идеально для глобальных сервисов (сохранение, аудио, настройки, сеть). - Сценарные (SceneContext): Установщик, прикреплённый к объекту
SceneContextв сцене. Привязки здесь доступны только в текущей сцене. Обычно ссылается на ProjectContext черезContainer.Inherit = true. - Подконтексты (SubContainers): Позволяют создавать изолированные области зависимостей внутри сцены (например, для UI-окна).
- Проектные (ProjectContex): Установщик, прикреплённый к префабу
В “Assets” создадим папку “Infrastructure” в которой создадим класс BootstrapInstaller он будет наследоваться от MonoInstaller. Внутри переопределим InstallBindings() в котором будем регистрировать сервисы игры.
using Zenject;
public class BootstrapInstaller: MonoInstaller
{
public override void InstallBindings()
{
}
}BootstrapInstaller добавляем как компонент в ProjectContext и уже созданный компонент
перетягиваем в список “Mono Installers”.

Аналогично создаём LocationInstaller и вешаем его на SceneContext.
Итого мы получили два инсталера, один работает локально в сцене, другой действует во всем проекте.
Эксперимент-1. Создание героя из префаба и изменение героя врагом
Подготовка героя и врага
Создадим префаб кубика, который будет нашим условным героем и назовем его “Hero” Добавим кубику класс HeroController и добавим метод который будет увеличивать размер кубика в два раза.
public class HeroController : MonoBehaviour
{
public void ChangeLocalScale()
{
gameObject.transform.localScale = new Vector3(2, 2, 2);
}
}
В сцене создадим пустой объект “StartPoint”, это будет точка, в которой мы будем инстанцировать героя. В LocationInstaller создадим два поля “public Transform StartPoint;” и public “GameObject HeroPrefab;” и перетянем на них “Hero” и “StartPoint”,

Создадим Врага. Чтоб не усложнять статью создадим на сцене простой шар назовем его “enemy” и создадим для него класс EnemyController (пока пустой).
Создание обьекта из префаба с помощью ZENJECT
Теперь у нас все готово, чтоб инстанцировать героя через контейнер.
В класс LocationInstaller добавляем строчку
Container.InstantiatePrefabForComponent<HeroController>(HeroPrefab, StartPoint.position, Quaternion.identity, null);
using Zenject;
public class LocationInstaller : MonoInstaller
{
public Transform StartPoint;
public GameObject HeroPrefab;
public override void InstallBindings()
{
HeroController heroController = Container
.InstantiatePrefabForComponent<HeroController>(HeroPrefab, StartPoint.position, Quaternion.identity, null);
}Сейчас при запуске сцены наш кубик будет создаваться на сцене из префаба.
HeroController heroController = Container
.InstantiatePrefabForComponent(HeroPrefab, StartPoint.position, Quaternion.identity, null);
означает, что мы создаем объект из префаба и берем его компонент “HeroController“.
Binding
Далее будем “биндить” компонент куба “HeroController“.
Привязка (Binding): Процесс регистрации типов и их зависимостей в контейнере.
После регистрации контейнер может отдать “по типу” эту реализацию сущностям которым она понадобится.
Для сервисов типичной является ситуация когда регистрация осуществляется по интерфейсу, ибо интерфейсы позволяют создавать нужные реализации под конкретные задачи ( например инпут под различные платформы)
Полный формат команды bind выглядит следующим образом. Обратите внимание, что в большинстве случаев вы не будете использовать все эти методы, и что у всех них есть логические значения по умолчанию, если они не указаны.
Container.Bind<ContractType>()
.WithId(Identifier)
.To<ResultType>()
.FromConstructionMethod()
.AsScope()
.WithArguments(Arguments)
.OnInstantiated(InstantiatedCallback)
.When(Condition)
.(Copy|Move)Into(All|Direct)SubContainers()
.NonLazy()
.IfNotBound();- ContractType = Тип, для которого вы создаете привязку.
- Это значение будет соответствовать типу вводимого поля/параметра.
- ResultType = Тип для привязки.
- По умолчанию: ContractType
- Этот тип должен быть равен ContractType или быть производным от ContractType . Если не указано иное, предполагается ToSelf(), что означает, что ResultType будет таким же, как ContractType . Это значение будет использоваться любым значением, указанным в качестве ConstructionMethod , для получения экземпляра этого типа
- Identifier = значение, используемое для уникальной идентификации привязки. В большинстве случаев это можно игнорировать, но может быть весьма полезно, когда нужно различать несколько привязок с одним и тем же типом контракта. Подробнее см. здесь .
- ConstructionMethod = Метод создания/извлечения экземпляра ResultType . Подробнее о различных методах построения см. в этом разделе .
Bind<ContractType>().FromNew — создание с помощью оператора new в C#. Создает новый экземпляр каждый раз, когда запрашивается.Bind<ContractType>().FromGetterBind<ContractType>().FromMethod — создать с помощью пользовательского методаBind<ContractType>().FromResolve — получение экземпляра путём повторного поиска по контейнеру (то есть вызова DiContainer.Resolve()). Обратите внимание, что для этого ResultType должен быть привязан в отдельном операторе привязки.Bind<ContractType>().FromComponentInNewPrefab — Создать экземпляр из префабаBind<ContractType>().FromSubContainerResolve — Получить экземпляр, выполнив поиск по подконтейнеру. Обратите внимание, что для этого у подконтейнера должна быть привязка к ResultType.Bind<ContractType>().FromInstance — Использовать существующий заранее созданный экземпляр.Bind<FromComponentInHierarchy(); – Найти существующий компонент типаContractType>().на сцене.ContractType- etc.
- Scope (Область действия) = Это значение определяет, как часто (и используется ли вообще) сгенерированный экземпляр повторно в нескольких инъекциях.
- Значение по умолчанию: AsTransient. Обратите внимание, что не все привязки имеют значение по умолчанию, поэтому, если оно не указано, будет сгенерировано исключение. Привязки, не требующие явного указания области действия, — это любые привязки с методом конструирования, который представляет собой поиск, а не создание нового объекта с нуля (например, FromMethod, FromComponentX, FromResolve и т. д.).
- Это может быть одно из следующего::
- AsTransient – экземпляр не будет использоваться повторно. При каждом запросе ContractType DiContainer будет повторно выполнять заданный метод конструирования.
- AsCached – будет повторно использовать один и тот же экземпляр ResultType каждый раз при запросе ContractType , который будет лениво сгенерирован при первом использовании.
- AsSingle – то же самое, что и AsCached, за исключением того, что иногда возникают исключения, если привязка для ResultType уже существует . Это просто способ гарантировать уникальность данного ResultType в контейнере. Однако следует отметить, что это гарантирует только наличие одного экземпляра в данном контейнере, а это означает, что использование AsSingle с той же привязкой в подконтейнере может сгенерировать второй экземпляр.
- В большинстве случаев вы, скорее всего, захотите использовать AsSingle, однако AsTransient и AsCached также имеют свое применение.
- Arguments = Список объектов, используемых при создании нового экземпляра типа ResultType . Это может быть полезно в качестве альтернативы добавлению других привязок для аргументов в форме
Container.BindInstance(arg).WhenInjectedInto<ResultType>() - InstantiatedCallback = В некоторых случаях полезно иметь возможность настраивать объект после его создания. В частности, при использовании сторонней библиотеки может потребоваться изменить несколько полей одного из его типов. В таких случаях можно передать метод OnInstantiated, который настроит вновь созданный экземпляр.
В функцию InstallBindings() добавляем строку:
Container.Bind<HeroController>().FromInstance(heroController).AsSingle();
using UnityEngine;
using Zenject;
namespace Infrastructure
{
public class LocationInstaller : MonoInstaller
{
public GameObject HeroPrefab;
public override void InstallBindings()
{
HeroController heroController = Container
.InstantiatePrefabForComponent<HeroController>(HeroPrefab,
StartPoint.position, Quaternion.identity,
null);
Container
.Bind<HeroController>()
.FromInstance(heroController)
.AsSingle();
}
}
}тут возможны варианты
.AsSingle() – как и .AsCached() но как и синглтон бросает экзепшен если будет попытка создать инстанс по такому же типу.
.AsCached() – сохраняет до уничтожения контейнера.
.AsTransient() – по умолчанию. при каждом запросе будет вызываться код, создающий экземпляр (запросили – создали – использовали – уничтожили).
.NonLazy()– заставляет контейнер создать экземпляр немедленно при старте, а не при первом запросе. это полезно для менеджеров типа аналитики, рекламы и другим, которые начинают работать до основного старта геймплея.
( Container.Bind().FromInstance(heroController).AsSingle() .NonLazy() )
Внедрение (Injection):
На данный момент мы зарегистрировали HeroController в контейнере. теперь мы можем запросить его из кода врага.
Лучше всего инжектить в конструктор. Если инжектим в MonoBehaviour то
создаем метод с атрибутом [Inject]
Создаем инжекцию в классе EnemyController
using UnityEngine;
using Zenject;
public class EnemyController : MonoBehaviour
{
[Inject]
private void Construct(HeroController heroController)
{
heroController.ChangeLocalScale();
}
} теперь при запуске сцены враг будет увеличивать размер героя в два раза, используя метод самого героя.
Когда [Inject] сработает у MonoBehaviour
- Если объект был заранее в сцене (не префаб, а прямо на сцене).
- Zenject при старте сцены «пробегается» по всем объектам, находит компоненты и делает им
Inject(). - Поэтому
Construct()вызовется, даже если ты этот объект не создавал черезContainer.Instantiate().
- Zenject при старте сцены «пробегается» по всем объектам, находит компоненты и делает им
- Если объект появился из
Context(SceneContext, ProjectContext, GameObjectContext).- В этом случае контейнер тоже «пройдётся» и внедрит зависимости.
Когда [Inject] НЕ сработает
- Если ты сам сделал
new SomeClass()→ Zenject про него не знает. - Если сделал
GameObject.Instantiate(prefab)и не вызвал вручную_container.InjectGameObject(go). - Если
MonoBehaviourдобавлен в рантайме черезgameObject.AddComponent<MyComponent>()(тоже нуженInject).
Эксперимент-2. Binding объекта сцены и Injection в создаваемый из префаба объект
Предположим на сцене есть объект который отображает данные героя (например инвентарь) и созданному из префаба герою нужно что-то туда записывать
Создадим на сцене UI панель и добавим на неё три текстовых поля. UI панели добавим класс InventoryDisplay
using UnityEngine;
using TMPro;
public class InventoryDisplay : MonoBehaviour
{
public TMP_Text[] HotbarSlots;
}В редакторе перетянем на TMP_Text[] три текстовых поля.
биндим InventoryDisplay в LocationInstaller
первым делом в LocationInstaller создадим переменную которой назначим InventoryDisplay UI панели
public InventoryDisplay inventoryDisplay; Container
.Bind<InventoryDisplay>()
.FromInstance(inventoryDisplay)
.AsSingle();полный LocationInstaller имеет вид
using UnityEngine;
using Zenject;
namespace Infrastructure
{
public class LocationInstaller : MonoInstaller
{
public Transform StartPoint;
public GameObject HeroPrefab;
public InventoryDisplay inventoryDisplay;
public override void InstallBindings()
{
BindInventoryDisplay();
BindHero();
}
private void BindInventoryDisplay()
{
Container
.Bind<InventoryDisplay>()
.FromInstance(inventoryDisplay)
.AsSingle();
}
private void BindHero()
{
HeroController heroController = Container
.InstantiatePrefabForComponent<HeroController>(HeroPrefab, StartPoint.position, Quaternion.identity,
null);
Container
.Bind<HeroController>()
.FromInstance(heroController)
.AsSingle();
}
}
}теперь мы можем инжектить в класс героя HeroController, но мы не будем создавать там кашу и для героя создадим специальный класс который будет отвечать за инвентарь CharacterInventory
using Zenject;
using UnityEngine;
using UnityEngine.UI;
using TMPro;
namespace Infrastructure
{
public class CharacterInventory : MonoBehaviour
{
TMP_Text[] HotbarDisplayHolders = new TMP_Text[3];
[Inject]
public void Construct(InventoryDisplay inventoryDisplay)
{
HotbarDisplayHolders = inventoryDisplay.HotbarSlots;
}
void Start()
{
HotbarDisplayHolders[1].text = "герой изменил текст";
}
}
}Запустим проект и наблюдаем как герой внес изменение в текстовое поле.

Эксперимент-3. Создание сервиса в ProjectContext
Пришло время сотворить что-нибудь используя ProjectContext.
Как уже говорилось ранее ProjectContext нужен для создания сервисов которые будут работать во всем приложении. Первое что приходит на ум, так это какой-нибудь InputService отвечающий за ввод. Построим сервис на интерфейсе чтоб иметь возможность переключать ввод с клавиатуры на что-то другое. Сервис будет содержать пару event Action на которые будет подписываться герой. Усложним себе задачу тем что сделаем сервис НЕ MonoBehaviour
Первым делом создадим интерфейс IInputService с двумя event Action ClickOne и ClickTwo
using System;
public interface IInputService
{
event Action ClickOne;
event Action ClickTwo;
}Создадим класс наследник KeyboardInput. Поскольку мы усложнили себе жизнь, решив сделать все без MonoBehaviour, возникает большая проблема. В Unity событие нажатия клавиатуры (Input.GetKey, Input.GetKeyDown, Input.GetKeyUp) обрабатывается только в игровом цикле — то есть внутри методов, которые Unity сама вызывает (Update, LateUpdate, OnGUI и т.д.) в MonoBehaviour.
Чтоб решить эту проблему, пойдем на хитрость. В классе KeyboardInput мы реализуем метод Tick() интерфейса ITickable. Tick() – аналог Update(). Благодаря этому мы сможем вызывать Action ClickOne и ClickTwo при нажатии клавиш “А” и “B”
using System;
using System.Linq;
using UnityEngine;
using UnityEngine.LowLevel;
public class KeyboardInput : IInputServiсe, ITickable
{
public event Action ClickOne;
public event Action ClickTwo;
public void Tick()
{
if (Input.GetKey(KeyCode.A))
{
ClickOne?.Invoke();
}
if (Input.GetKey(KeyCode.B))
{
ClickTwo?.Invoke();
}
}
}Биндим KeyboardInput в BootstrapInstaller предварительно создав обьект класса KeyboardInput биндим с .NonLazy(), так как хотим чтоб контейнер создался немедленно. Не забываем забиндить интерфейс ITickable в keyboardInput , если этого не сделать Tick() работать не будет. Нажатие клавиш при отсутствии подписантов не вызовет проблем так как в KeyboardInput используется конструция вида хххххх?.Invoke().
using Zenject;
public class BootstrapInstaller : MonoInstaller
{
KeyboardInput keyboardInput;
public override void InstallBindings()
{
keyboardInput = new KeyboardInput();
Container
.Bind<IInputServiсe>()
.FromInstance(keyboardInput)
.AsSingle()
.NonLazy();
Container
.Bind<ITickable>()
.FromInstance(keyboardInput)
.AsSingle()
.NonLazy();
}
}Инжектим контейнер в HeroController и подписываемся на события.
При событии ClickOne будем уменьшать героя. При ClickTwo – увеличиваем героя,
using UnityEngine;
using Zenject;
public class HeroController : MonoBehaviour
{
IInputServiсe _inputServiсe;
[Inject]
public void Construct(IInputServiсe inputServiсe)
{
_inputServiсe = inputServiсe;
_inputServiсe.ClickOne += ClickOne;
_inputServiсe.ClickTwo += ClickTwo;
}
private void ClickOne()
{
gameObject.transform.localScale = new Vector3(1, 1, 1);
}
private void ClickTwo()
{
gameObject.transform.localScale = new Vector3(3, 3, 3);
}
public void ChangeLocalScale()
{
gameObject.transform.localScale = new Vector3(2, 2, 2);
}
void OnDestroy()
{
_inputServiсe.ClickOne -= ClickOne;
_inputServiсe.ClickTwo -= ClickTwo;
}
}
запускаем проект и наблюдаем уменьшение и увеличивание героя при нажатии “А” и “В”.


Благодаря использованию интерфейса мы можем менять тип ввода внося изменения лиш в один класс – BootstrapInstaller.
Эксперимент-4. Фабрика
Переименуем сферу-врага в EnemyMelee. Создадим копию переместим в другую случайную позицию и назовем EnemyRange.
Создадим enum c врагами.
public enum EnemyType
{
Melee = 0,
ranged = 1
}Создадим из врагов префабы и удалим их со сцены. На сцене будут располагаться специальные маркеры врагов.
Создадим два объекта-пустышки EnemyMarker и EnemyMarker1. Расположим их в местах появления врагов и повесим на них новый класс EnemyMarker. Он будет отрисовывать в Gizmos красную сферу обозначающую врага.
using UnityEngine;
public class EnemyMarker : MonoBehaviour
{
public EnemyType enemyType;
private void OnDrawGizmos()
{
Gizmos.color = Color.red;
Gizmos.DrawSphere(transform.position, 1);
Gizmos.color = Color.white;
}
}Создадим фабрику Enemys
В ZENJECT есть свои фабрики, но мы создадим свою.
создаем клас EmenyFactory Он должен уметь загружать префабы врагов и создавать врагов из префабов. EmenyFactory будет наследоватся от интерфейса IEmenyFactory.
using UnityEngine;
public interface IEnemyFactory
{
void Create(EnemyType enemyType, Vector3 at);
void Load();
}
public class EnemyFactory : IEnemyFactory
{
public Object _meleeEnemyPrefab;
public Object _rangeEnemyPrefab;
public EnemyFactory(DiContainer diContainer)
{
_diContainer = diContainer;
}
public void Load() {}
public void Create(EnemyType enemyType, Vector3 at) { }
}Сейчас отложим фабрику в сторону и вернемся к LocationInstaller.
Дело в том, что фабрику нужно запустить в какой-то момент времени. На первый взгляд кажется, что сделать это можно в InstallBindings(), но это будет ошибкой.
InstallBindings() — это момент конфигурации контейнера, а не выполнения логики игры.
В нём:
- Регистрируются зависимости
(Bind, FromComponentInHierarchy, To<>()и т.д.) - Но игровой мир ещё не загружен до конца: сцена может не иметь всех объектов, некоторые
MonoBehaviourещё не проинициализированы.
рискуем:
- Создать врагов до того, как сцена полностью загрузилась
- Столкнуться с тем, что фабрика или её зависимости ещё не собраны
- Поймать
NullReferenceException
Чтобы решить эту проблему, в LocationInstaller добавим интерфуйс IInitializable.
IInitializable — это интерфейс, у которого один метод, void Initialize().
Zenject автоматически вызывает Initialize() у всех зарегистрированных IInitializable после того, как:
- Все
InstallBindings()во всех установщиках завершены - Все зависимости внедрены
- Сцена и контейнер полностью собраны
В момент Initialize():
- Контейнер уже готов, все
EnemyMarkerв сцене уже есть. - Фабрика
enemyFactoryуже полностью сконфигурирована. - Можно безопасно пройтись по всем
EnemyMarkerи создать врагов.
По сути, это инициализация сцены после загрузки.
Теперь зная немного теории, забиндим фабрику в методе InstallBindings()
Container.Bind<IEnemyFactory>().To<EnemyFactory>().AsSingle();как говорилось ранее:
В момент калбека Initialize() контейнер уже готов, все EnemyMarker в сцене уже есть. Фабрика enemyFactory уже полностью сконфигурирована.
В Initialize() заресолвим фабрику. Загрузим префабы врагов в фабрику. Пройдемся по Маркерам создавая Enemy.
public void Initialize()
{
// DiContainer.Resolve — возвращает экземпляр, соответствующий заданному типу
// этот кодвыполнится так как в конструкторе забиндили фабрику
var enemyFactory = Container.Resolve<IEnemyFactory>();
enemyFactory.Load();
foreach (EnemyMarker marker in EnemyMarkers)
{
enemyFactory.Create(marker.enemyType, marker.transform.position);
}
}В метод InstallBindings() класса LocationInstaller добавим код, в котором забиндим сам LocationInstaller (это понадобится в фабрике)
Container.BindInterfacesTo<LocationInstaller>().FromInstance(this).AsSingle();Теперь, когда мы все подготовили в LocationInstaller возвращаемся в EnemyFactory.
добавим загрузку префабов.
public void Load()
{
_meleeEnemyPrefab = Resources.Load(PathEnemyMelee);
_rangeEnemyPrefab = Resources.Load(PathEnemyRange);
}Инстанцировать Enemy из префаба будем с помощью ранее забинденого LocationInstaller.
Для этого в конструкторе EnemyFactory получаем зависимостью сам контейнер
public EnemyFactory(DiContainer diContainer)
{
_diContainer = diContainer;
}Благодаря этому ХАКу мы можем инстанцировать врагов в фабрике с помощъю diContainer.
public void Create(EnemyType enemyType, Vector3 at)
{
switch (enemyType)
{
case EnemyType.Melee:
_diContainer.InstantiatePrefab(_meleeEnemyPrefab, at, Quaternion.identity, null);
break;
case EnemyType.Ranged:
_diContainer.InstantiatePrefab(_rangeEnemyPrefab, at, Quaternion.identity, null);
break;
}
}Полный код класса IEnemyFactory:
using UnityEngine;
using Zenject;
public class EnemyFactory : IEnemyFactory
{
private const string PathEnemyMelee = "EnemyMelee";
private const string PathEnemyRange = "EnemyRange";
private readonly DiContainer _diContainer;
public Object _meleeEnemyPrefab;
public Object _rangeEnemyPrefab;
public EnemyFactory(DiContainer diContainer)
{
_diContainer = diContainer;
}
public void Load()
{
_meleeEnemyPrefab = Resources.Load(PathEnemyMelee);
_rangeEnemyPrefab = Resources.Load(PathEnemyRange);
}
public void Create(EnemyType enemyType, Vector3 at)
{
switch (enemyType)
{
case EnemyType.Melee:
_diContainer.InstantiatePrefab(_meleeEnemyPrefab, at, Quaternion.identity, null);
break;
case EnemyType.Ranged:
_diContainer.InstantiatePrefab(_rangeEnemyPrefab, at, Quaternion.identity, null);
break;
}
}
}Полный код класса LocationInstaller
using UnityEngine;
using Zenject;
public class LocationInstaller : MonoInstaller, IInitializable
{
public Transform StartPoint;
public GameObject HeroPrefab;
public EnemyMarker[] EnemyMarkers;
public InventoryDisplay inventoryDisplay;
public override void InstallBindings()
{
BindInstallerInterfaces(); // LocationInstaller биндит сам себя
BindInventoryDisplay();
BindHero();
BindEmenyFactory();
}
private void BindEmenyFactory()
{
Container
.Bind<IEnemyFactory>()
.To<EnemyFactory>()
.AsSingle();
}
private void BindInstallerInterfaces()
{
Container.BindInterfacesTo<LocationInstaller>().FromInstance(this).AsSingle();
}
private void BindInventoryDisplay()
{
Container
.Bind<InventoryDisplay>()
.FromInstance(inventoryDisplay)
.AsSingle();
}
private void BindHero()
{
HeroController heroController = Container
.InstantiatePrefabForComponent<HeroController>(HeroPrefab, StartPoint.position, Quaternion.identity,
null);
Container
.Bind<HeroController>()
.FromInstance(heroController)
.AsSingle();
}
// инициализацию объекта вне конструктора можем делать тут, удобно для фабрик и биндинга после резолва
public void Initialize()
{
// DiContainer.Resolve — возвращает экземпляр, соответствующий заданному типу
// этот кодвыполнится так как в конструкторе забиндили фабрику
var enemyFactory = Container.Resolve<IEnemyFactory>();
enemyFactory.Load();
foreach (EnemyMarker marker in EnemyMarkers)
{
enemyFactory.Create(marker.enemyType, marker.transform.position);
}
}
}
После того как в редакторе в поля LocationInstaller перетянем маркеры врагов,можно запустить игру и убедится враги (шары) появились в местах расположения маркеров

Шпаргалка
public override void InstallBindings()
{
// Создавать новый экземпляр Foo для каждого класса, который его запрашивает
Container.Bind<Foo>().AsTransient();
// Создавать новый экземпляр Foo для каждого класса, который запрашивает IFoo
Container.Bind<IFoo>().To<Foo>().AsTransient();
// Негeneric-версия приведённого выше
Container.Bind(typeof(IFoo)).To(typeof(Foo)).AsTransient();
///////////// AsSingle
// Создать один общий экземпляр Foo и переиспользовать его для всех классов, которые его запрашивают
Container.Bind<Foo>().AsSingle();
// Создать один общий экземпляр Foo и переиспользовать его для всех классов, которые запрашивают IFoo
Container.Bind<IFoo>().To<Foo>().AsSingle();
// Привязать один и тот же экземпляр к нескольким типам
// В этом примере один и тот же экземпляр Foo будет использоваться для всех трёх типов
// (для связывания с несколькими типами нужно использовать негeneric-версию Bind)
Container.Bind(typeof(Foo), typeof(IFoo), typeof(IFoo2)).To<Foo>().AsSingle();
///////////// BindInterfaces
// Это будет иметь тот же эффект, что и строка выше
// Привязать все интерфейсы, которые реализует Foo, а также сам Foo к одному синглтону типа Foo
Container.BindInterfacesAndSelfTo<Foo>().AsSingle();
// Привязать только интерфейсы, которые реализует Foo, к экземпляру Foo
// Полезно, если не хотите, чтобы другие классы напрямую ссылались на конкретный тип
Container.BindInterfacesTo<Foo>().AsSingle();
///////////// FromInstance
// Использовать переданный экземпляр везде, где запрашивается Foo
// Заметьте, что в данном случае особого смысла в FromInstance нет
Container.Bind<Foo>().FromInstance(new Foo());
// Это просто сокращённый вариант предыдущей привязки
// Немного удобнее, так как тип определяется из переданного объекта
Container.BindInstance(new Foo());
// Привязать сразу несколько экземпляров
Container.BindInstances(new Foo(), new Bar());
///////////// Привязка примитивных типов
// BindInstance чаще используется с примитивными типами
// Использовать число 10 каждый раз, когда запрашивается int
Container.Bind<int>().FromInstance(10);
Container.Bind<bool>().FromInstance(false);
// Эквивалентная запись:
Container.BindInstance(10);
Container.BindInstance(false);
// Но обычно так делать не стоит — почти всегда лучше использовать условие When для примитивных значений
Container.BindInstance(10).WhenInjectedInto<Foo>();
///////////// FromMethod
// Создавать экземпляр Foo при запросе, используя указанную функцию
// Для более сложных сценариев лучше использовать фабрику (FromFactory)
Container.Bind<Foo>().FromMethod(GetFoo);
// Случайным образом возвращать одну из нескольких реализаций IFoo
// Используем Instantiate вместо new, чтобы Foo1 получил свои зависимости через инъекцию
Container.Bind<IFoo>().FromMethod(GetRandomFoo);
// Можно также использовать анонимный делегат напрямую
Container.Bind<Foo>().FromMethod(ctx => new Foo());
// Это эквивалентно AsTransient
Container.Bind<Foo>().FromMethod(ctx => ctx.Container.Instantiate<Foo>());
InstallMore();
}
void InstallMore()
{
///////////// FromResolveGetter
// Привязать к свойству другого зависимого объекта
// Это помогает уменьшить связанность между классами
Container.Bind<Foo>().AsSingle();
Container.Bind<Bar>().FromResolveGetter<Foo>(foo => foo.GetBar());
// Другой пример с возвращением значений
Container.Bind<string>().FromResolveGetter<Foo>(foo => foo.GetTitle());
///////////// FromNewComponentOnNewGameObject
// Создать новый GameObject в корне сцены и добавить к нему MonoBehaviour Foo
Container.Bind<Foo>().FromNewComponentOnNewGameObject().AsSingle();
// Можно указать имя создаваемого объекта с помощью WithGameObjectName
Container.Bind<Foo>().FromNewComponentOnNewGameObject().WithGameObjectName("Foo1").AsSingle();
// Привязать к интерфейсу вместо конкретного класса
Container.Bind<IFoo>().To<Foo>().FromNewComponentOnNewGameObject().AsSingle();
///////////// FromComponentInNewPrefab (singleton)
// Создать новый GameObject в корне сцены, используя данный префаб
// После того как Zenject создаст объект из префаба, он найдёт в нём компонент Foo и вернёт его
GameObject prefab = null;
Container.Bind<Foo>().FromComponentInNewPrefab(prefab).AsSingle();
// Привязать к интерфейсу вместо конкретного класса
Container.Bind<IFoo>().To<Foo>().FromComponentInNewPrefab(prefab).AsSingle();
// Можно добавить несколько компонентов
// Важно: будет создан только один экземпляр указанного префаба
// Для этого на префабе должны быть компоненты Foo и Bar
Container.Bind(typeof(Foo), typeof(Bar)).FromComponentInNewPrefab(prefab).AsSingle();
///////////// FromComponentInNewPrefab (Transient)
// Создавать новый экземпляр префаба каждый раз, когда нужен Foo
Container.Bind<Foo>().FromComponentInNewPrefab(prefab).AsTransient();
// Привязать к интерфейсу вместо конкретного класса
Container.Bind<IFoo>().To<Foo>().FromComponentInNewPrefab(prefab);
///////////// Идентификаторы
// Привязать глобально доступную строку с именем "PlayerName"
// Хотя часто лучше создать отдельный объект настроек и привязать его
Container.Bind<string>().WithId("PlayerName").FromInstance("имя игрока");
// Эквивалент предыдущей строки, но читается понятнее
Container.BindInstance("имя игрока").WithId("PlayerName");
// С помощью ID можно привязать несколько экземпляров одного типа:
Container.BindInstance("foo").WithId("FooA");
Container.BindInstance("asdf").WithId("FooB");
InstallMore2();
}
// При внедрении зависимостей нужно использовать тот же ID:
public class Norf
{
[Inject(Id = "FooA")]
string _foo;
}
public class Qux
{
[Inject(Id = "FooB")]
string _foo;
}
public void InstallMore2()
{
///////////// AsCached
// Здесь мы привязываем три экземпляра Foo, включая один без ID
// Используем AsCached, потому что Foo не singleton, но и новый экземпляр при каждом запросе нам не нужен
// В итоге будет создано максимум 3 экземпляра Foo
Container.Bind<Foo>().AsCached();
Container.Bind<Foo>().WithId("FooA").AsCached();
Container.Bind<Foo>().WithId("FooA").AsCached();
InstallMore3();
}
// Если ID не указан в [Inject], используется первый подходящий экземпляр
// Привязки без ID могут использоваться как значение по умолчанию, а ID — для конкретных версий
public class Norf2
{
[Inject]
Foo _foo;
}
// Qux2._foo будет тем же экземпляром, что и Norf2._foo
// Это возможно, потому что мы используем AsCached, а не AsTransient
public class Qux2
{
[Inject]
Foo _foo;
[Inject(Id = "FooA")]
Foo _foo2;
}
public void InstallMore3()
{
///////////// Conditions (условия)
// Сделать Foo видимым только для Bar
// Если добавить Foo в конструктор любого другого класса — он не будет найден
Container.Bind<Foo>().AsSingle().WhenInjectedInto<Bar>();
// Использовать разные реализации IFoo в зависимости от того, в какой класс внедряется зависимость
Container.Bind<IFoo>().To<Foo1>().AsSingle().WhenInjectedInto<Bar>();
Container.Bind<IFoo>().To<Foo2>().AsSingle().WhenInjectedInto<Qux>();
// Использовать Foo1 по умолчанию, кроме случаев внедрения в Qux — там Foo2
// Если условие совпадает, оно имеет приоритет
Container.Bind<IFoo>().To<Foo1>().AsSingle();
Container.Bind<IFoo>().To<Foo2>().AsSingle().WhenInjectedInto<Qux>();
// Разрешить использовать Foo только в указанных классах
Container.Bind<Foo>().AsSingle().WhenInjectedInto(typeof(Bar), typeof(Qux), typeof(Baz));
// Передавать строку "my game" для всех строк, внедряемых в класс Gui с идентификатором "Title"
Container.BindInstance("my game").WithId("Title").WhenInjectedInto<Gui>();
// Передавать число 5 для всех int, внедряемых в класс Gui
Container.BindInstance(5).WhenInjectedInto<Gui>();
// Передавать 5 для всех значений float, внедряемых в параметр или поле внутри Gui с именем "width"
// Обычно так лучше не делать, т.к. имя поля может измениться и сломать привязку
Container.BindInstance(5.0f).When(ctx =>
ctx.ObjectType == typeof(Gui) && ctx.MemberName == "width");
// Создавать новый Foo для каждого класса, который создаётся как часть создания Bar
// Например, если Bar зависит от Qux, а Qux зависит от IFoo — то для этого IFoo будет создан новый Foo
Container.Bind<IFoo>().To<Foo>().AsTransient().When(
ctx => ctx.AllObjectTypes.Contains(typeof(Bar)));
///////////// Пример сложных условий
var foo1 = new Foo();
var foo2 = new Foo();
Container.Bind<Bar>().WithId("Bar1").AsCached();
Container.Bind<Bar>().WithId("Bar2").AsCached();
// Здесь используем ParentContexts, чтобы сопоставить несколько идентификаторов
Container.BindInstance(foo1).When(c => c.ParentContexts.Where(x => x.MemberType == typeof(Bar) && x.Identifier == "Bar1").Any());
Container.BindInstance(foo2).When(c => c.ParentContexts.Where(x => x.MemberType == typeof(Bar) && x.Identifier == "Bar2").Any());
// В результате получаем:
Assert.That(Container.ResolveId<Bar>("Bar1").Foo == foo1);
Assert.That(Container.ResolveId<Bar>("Bar2").Foo == foo2);
///////////// FromResolve
// FromResolve делает повторный поиск в контейнере
// В результате IBar, IFoo и Foo будут ссылаться на один и тот же экземпляр Foo,
// который, предполагается, уже есть на указанном префабе
GameObject fooPrefab = null;
Container.Bind<Foo>().FromComponentInNewPrefab(fooPrefab).AsSingle();
Container.Bind<IBar>().To<Foo>().FromResolve();
Container.Bind<IFoo>().To<IBar>().FromResolve();
// То же самое, но короче записано
Container.Bind(typeof(Foo), typeof(IBar), typeof(IFoo)).To<Foo>().FromComponentInNewPrefab(fooPrefab).AsSingle();
InstallMore4();
}
void InstallMore4()
{
///////////// Installing Other Installers
// Немедленно вызвать InstallBindings() у FooInstaller
FooInstaller.Install(Container);
// Перед вызовом FooInstaller сконфигурировать его свойство
Container.BindInstance("foo").WhenInjectedInto<FooInstaller>();
FooInstaller.Install(Container);
// Аргументы можно добавить в дженерик Installer<>, чтобы сделать их строго типизированными
FooInstallerWithArgs.Install(Container, "foo");
///////////// Ручное использование Container
// Заполнить все поля/методы с [Inject] и вызвать методы с [Inject]
var foo = new Foo();
Container.Inject(foo);
// Вернуть экземпляр для IFoo, используя уже добавленные привязки
// Внутренне используется при внедрении в конструкторы
// Если не найдёт подходящий тип — выбросит исключение
Container.Resolve<IFoo>();
// То же самое, но вернёт null, если не найдёт
Container.TryResolve<IFoo>();
// Вернуть список из 2 экземпляров Foo
// Просто Resolve<IFoo> здесь вызовет исключение
Container.BindInstance(new Foo());
Container.BindInstance(new Foo());
var foos = Container.ResolveAll<IFoo>();
// Создать новый экземпляр Foo и внедрить зависимости в его поля/конструктор
Container.Instantiate<Foo>();
GameObject prefab1 = null;
GameObject prefab2 = null;
// Создать новый объект из префаба и внедрить зависимости в его компоненты
GameObject go = Container.InstantiatePrefab(prefab1);
// Создать новый объект из префаба и вернуть конкретный MonoBehaviour
Foo foo2 = Container.InstantiatePrefabForComponent<Foo>(prefab2);
// Добавить новый компонент к существующему GameObject
Foo foo3 = Container.InstantiateComponent<Foo>(go);
}