Реализация Декоратора в Unity с Zenject
Есть интерфейс:
public interface IUpdatable
{
public void Awake();
public void Start();
public void Update();
public void FixedUpdate();
public void LateUpdate();
}
Данный интерфейс создан для того,что бы дергать Юнит-ишные методы у классов, не наследующихся от MonoBehaviour.
У меня есть класс PlayerController, который реализует этот интерфейс.
Вот сам декоратор. Я пытаюсь централизовать все вызовы в одном месте с помощью итерфейса, но при запуске List<IUpdatable> updatable имеет Count = 0;,хотя как минимум один наследник у IUpdatable есть.
public class UpdatableDecorator : MonoBehaviour
{
private List<IUpdatable> _updatables = new();
[Inject]
public void Construct(List<IUpdatable> updatable)
{
_updatables.AddRange(updatable);
Debug.Log(_updatables.Count);
}
private void Awake()
{
foreach (var awake in _updatables)
{
awake.Awake();
}
}
void Start()
{
foreach (var start in _updatables)
{
start.Start();
}
}
void Update()
{
foreach (var update in _updatables)
{
update.Update();
}
}
private void FixedUpdate()
{
foreach (var fixedUpdate in _updatables)
{
fixedUpdate.FixedUpdate();
}
}
private void LateUpdate()
{
foreach (var lateUpdate in _updatables)
{
lateUpdate.LateUpdate();
}
}
}
Вот так выглядит бинд в SceneInstaller:
private void BindDicorator()
{
Container.Bind<List<IUpdatable>>().AsSingle();
Container.BindInterfacesAndSelfTo<UpdatableDecorator>().FromComponentInHierarchy().AsSingle();
}
Не могу понять, что я делаю не так. Буду благодарен за наставление.
Ответы (1 шт):
Вам не нужно вот это:
Container.Bind<List<IUpdatable>>().AsSingle();Вам нужно проинсталлить только сам класс, реализующий
IUpdatable:Container .BindInterfacesTo<ClassThatImplementsIUpdatable>() .AsSingle();Zenject умный и автоматически проинжектит все реализации
IUpdatableв вашList.
Zenject гарантирует, что метод, помеченный
[Inject]будет вызван до вызова методаStart, но не дает такой гарантии относительноAwake. То есть,Awakeможет быть вызван раньше,чемConstruct. Из чего следует, что изAwakeнельзя обращаться к проинжекченным объектам.То есть, в вашем случае, вот это
foreach (var awake in _updatables) { awake.Awake(); }или нужно удалить вообще, или вызывать его из метода
Construct. Я бы рекомендовал вообще удалить, так как см. пункт 4.
Это, в принципе, не нужно делать, поскольку в Zenject уже всё реализовано.
Создаем класс так:
public class MyClass : IInitializable, ITickable, IFixedTickable, ILateTickable, IDisposable { // Implement interfaces }Инсталлим так:
Container .BindInterfacesTo<MyClass>() .AsSingle();Легенда такая:
Start->IInitializableUpdate->ITickableFixedUpdate->IFixedTickableLateUpdate->ILateTickableOnDestroy->IDisposable
Нетрудно заметить, что в предыдущем пункте отсутствует
Awake. Однако, это не совсем так: для обычного класса аналогомAwakeпопросту является его конструктор. Вернее, даже наоборот, это вMonoBehaviourмы вынуждены вместо нормального конструктора использовать связкуAwakeиConstruct. Поэтому, можно считать, что то, что вы пытаетесь сделать уже полностью реализовано в Zenject. Ну, разве что, можно создать интерфейсIUpdatable, который объединит в себе все эти отдельные интерфейсы:public interface IUpdatable : IInitializable, ITickable, IFixedTickable, ILateTickable, IDisposable { }
- Замечание на будущее: будьте внимательны, когда начнете использовать
WithId(),WhenInjectedInto()иWhen()в биндингах. С их помощью вы ограничите, куда Zenject будет пытаться инжектить. Поэтому, чтобы эти интефейсы работали, придется создать связанный биндинг для них, без этих ограничений.