zenJect

Zenject框架(九)

2019-03-13  本文已影响0人  虫小白

一般指南/建议/陷阱/提示和技巧

游戏物体绑定方法(Game Object Bind Methods)

对于创建新游戏对象的绑定(例如FromComponentInNewPrefabFromNewComponentOnNewGameObject等),还有两个额外的绑定方法。

Container.Bind<Foo>().FromComponentInNewPrefabResource("Some/Path/Foo").WithGameObjectName("Foo1");
Container.Bind<Foo>().FromNewComponentOnNewGameObject().WithGameObjectName("Foo1");
Container.BindFactory<Bullet, Bullet.Factory>()
    .FromComponentInNewPrefab(BulletPrefab)
    .UnderTransformGroup("Bullets");
Container.BindFactory<Bullet, Bullet.Factory>()
    .FromComponentInNewPrefab(BulletPrefab)
    .UnderTransform(BulletTransform);
Container.BindFactory<Foo, Foo.Factory>()
    .FromComponentInNewGameObject()
    .UnderTransform(GetParent);

Transform GetParent(InjectContext context)
{
    if (context.ObjectInstance is Component)
    {
        return ((Component)context.ObjectInstance).transform;
    }

    return null;
}

此示例会自动将Foo游戏对象置于其注入的游戏对象下,除非被注入的对象不是MonoBehaviour,在这种情况下,Foo游戏对象会保留在场景层次面板的根部。

可选的绑定(Optional Binding)

您可以将某些依赖项声明为可选,如下所示:

public class Bar
{
    public Bar(
        [InjectOptional]
        IFoo foo)
    {
        ...
    }
}
...

// You can comment this out and it will still work
Container.Bind<IFoo>().AsSingle();

或者加一个标识符:

public class Bar
{
    public Bar(
        [Inject(Optional = true, Id = "foo1")]
        IFoo foo)
    {
        ...
    }
}

如果未在任何安装器中绑定可选依赖项,则它将被注入为null。
如果依赖关系是基本类型(例如int, float,struct),那么它将被注入其默认值(例如,0用于整数)。
您还可以使用标准C#方式分配显式默认值,例如:

public class Bar
{
    public Bar(int foo = 5)
    {
        ...
    }
}
...

// Can comment this out and 5 will be used instead
Container.BindInstance(1);

另请注意,[InjectOptional]在这种情况下不需要的,因为默认值指明了值。
或者,您可以将基本参数定义为可空,并根据是否提供值执行逻辑,例如:

public class Bar
{
    int _foo;

    public Bar(
        [InjectOptional]
        int? foo)
    {
        if (foo == null)
        {
            // 如果没有指定则使用5
            _foo = 5;
        }
        else
        {
            _foo = foo.Value;
        }
    }
}

...

// Can comment this out and it will use 5 instead
Container.BindInstance(1);

条件绑定(Conditional Bindings)

在许多情况下,您需要限制注入给定依赖项的位置。您可以使用以下语法执行此操作:

Container.Bind<IFoo>().To<Foo1>().AsSingle().WhenInjectedInto<Bar1>();
Container.Bind<IFoo>().To<Foo2>().AsSingle().WhenInjectedInto<Bar2>();

请注意,WhenInjectedInto是以下的简写,下面的语法使用了更通用的When()方法:

Container.Bind<IFoo>().To<Foo>().AsSingle().When(context => context.ObjectType == typeof(Bar));

InjectContext类(在上面被作为context参数进行传递)包含您可以作为条件使用信息,如下:

列表绑定(List Bindings)

当Zenject找到同一类型的多个绑定时,它会将其解释为列表。因此,在下面的示例代码中,Bar将获得包含新实例Foo1,Foo2,Foo3的列表:

// In an installer somewhere
Container.Bind<IFoo>().To<Foo1>().AsSingle();
Container.Bind<IFoo>().To<Foo2>().AsSingle();
Container.Bind<IFoo>().To<Foo3>().AsSingle();

...

public class Bar
{
    public Bar(List<IFoo> foos)
    {
    }
}

列表的顺序与使用Bind方法添加顺序的顺序相同。唯一的例外是当您使用子容器时,因为在这种情况下,列表将首先由关联的子容器排序,第一组实例从最底层的子容器中获取,然后是父级,然后是祖父级,等等。

使用工程长下文的全局绑定(Global Bindings Using Project Context)

如果有一些依赖项需要在所有的场景中长久保存,该怎么办?在Zenject中,您可以通过向ProjectContext对象添加安装器来完成此操作。

为此,首先需要为ProjectContext创建一个预制体,然后您可以为其添加安装器。您可以通过选择Edit -> Zenject -> Create Project Context菜单项轻松完成此操作。然后,您应该可以在Assets/Resources看到名为“ProjectContext”的新资产。或者,您可以在工程面板中右击某个位置并选择Create -> Zenject -> ProjectContext

如果单击此项,可以看到它与SceneContext的检视面板几乎完全相同。配置此预制体的最简单方法是暂时将其添加到场景中,向其中添加安装器,然后单击“apply”将其保存回预制体,然后再从场景中删除它。除了安装器,您还可以直接将自己的自定义MonoBehaviour类添加到ProjectContext对象。

然后,当您启动任何包含SceneContext的任何场景时,ProjectContext对象都会首先初始化。您在此处添加的所有安装器都将被执行,在安装器中添加的绑定将可用于项目中的所有场景。该ProjectContext游戏对象被设置为DontDestroyOnLoad因此更改场景时也不会被销毁。

另请注意,这只发生一次。如果从第一个场景加载另一个场景,ProjectContext则不会再次调用,并且之前添加的绑定将保留在新场景中。您可以使用与场景安装器相同的方式在项目上下文安装器中声明ITickable/ IInitializable/ IDisposable对象,其结果是IInitializable.Initialize只在每次运行时执行一次,IDisposable.Dispose只在应用程序完全停止时调用一次。

您添加到全局安装器的所有绑定都可用于每个场景中的所有类的原因是因为每个场景中容器都会使用的ProjectContext的容器作为父对象 。有关嵌套容器的更多信息,请参见后续。

ProjectContext是一个放置跨场景保留的对象的非常方便的地方。但是,它对每个场景都是完全全局的这一事实可能导致一些意想不到的行为。例如,这意味着即使您编写了一个使用Zenject框架的简单测试场景,它也会加载ProjectContext,这可能不是您想要。为了解决这些问题,通常最好使用Scene Parenting,因为这种方法允许您根据哪些场景继承相同的公共绑定来选择。有关该方法的更多详细信息,请参见“Scene Parenting Using Contract Names”章节

另请注意,默认情况下,在ProjectContext中实例化的任何游戏对象都默认成为其子级。如果您希望将每个新实例化的对象放置在场景层次面板的的根目录中(但仍标记为DontDestroyOnLoad),则可以通过在ProjectContext的检视面板中取消选中“Parent New Objects Under Context”。

标识符(Identifiers)

如果您需要为相同类型设置不同的绑定,而不仅仅是将其保存在列表中,你可以为绑定添加标识符。例如:

Container.Bind<IFoo>().WithId("foo").To<Foo1>().AsSingle();
Container.Bind<IFoo>().To<Foo2>().AsSingle();

...

public class Bar1
{
    [Inject(Id = "foo")]
    IFoo _foo;
}

public class Bar2
{
    [Inject]
    IFoo _foo;
}

在该例中,Bar1类将会得到Foo1的实例,而Bar2类会得到IFoo绑定的默认版本也就是Foo2的实例。
另请注意,您也可以对构造函数/注入方法参数执行相同的操作:

public class Bar
{
    Foo _foo;

    public Bar(
        [Inject(Id = "foo")] 
        Foo foo)
    {
    }
}

在很多情况下,标识符是字符类型的,但实际上可以是任意类型的,在下面的例子中使用了枚举作为标识符:

enum Cameras
{
    Main,
    Player,
}

Container.Bind<Camera>().WithId(Cameras.Main).FromInstance(MyMainCamera);
Container.Bind<Camera>().WithId(Cameras.Player).FromInstance(MyPlayerCamera);

你也可以使用自定义的类型,只要它们实现Equals运算符即可。

可编辑对象安装器(Scriptable Object Installer)

自定义的安装器除了可以派生自MonoInstaller或 Installer,也可以派生自ScriptableObjectInstaller。这最常用于存储游戏设置。这种方法具有以下优点:

例子:

public class GameSettings : ScriptableObjectInstaller
{
    public Player.Settings Player;
    public SomethingElse.Settings SomethingElse;
    // ... etc.

    public override void InstallBindings()
    {
        Container.BindInstances(Player, SomethingElse, etc.);
    }
}

public class Player : ITickable
{
    readonly Settings _settings;
    Vector3 _position;

    public Player(Settings settings)
    {
        _settings = settings;
    }

    public void Tick()
    {
        _position += Vector3.forward * _settings.Speed;
    }

    [Serializable]
    public class Settings
    {
        public float Speed;
    }
}
上一篇 下一篇

猜你喜欢

热点阅读