文章目录
Binding
Gameobject绑定(并设置Name和父物体)
针对于新创建的游戏对象的绑定(eg:FromComponentInNewPrefab FromNewComponentOnNewGameObject …),这里还额外提供了两种方法:
WithGameObjectName 完成绑定并对新创建的游戏物体命名。(笔者认为:类似于gameobject.name=“Foo1”)
//根据资源路径完成绑定,场景中新添加的游戏物体被命名为“Foo1”
Container.Bind<Foo>().FromComponentInNewPrefabResource("Some/Path/Foo").WithGameObjectName("Foo1");
Container.Bind<Foo>().FromNewComponentOnNewGameObject().WithGameObjectName("Foo1");
UnderTransformGroup(string) 设置新创建的游戏对象所在的Transform Group,这对于工厂模式很有用。因为工厂可能创建大量的Prefab副本,因此最好在Hierarchy中将它们自动分组在一起。
Container.BindFactory<Bullet, Bullet.Factory>()
.FromComponentInNewPrefab(BulletPrefab)
.UnderTransformGroup("Bullets");
Demo:
using Zenject;
public class FactorDemo : MonoInstaller
{
public Food _food;
public override void InstallBindings()
{
Container.BindFactory<Food, FoodFactory>()
.FromComponentInNewPrefab(_food)
.WithGameObjectName("水饺")
.UnderTransformGroup("中国食品");
}
private void Start()
{
FoodFactory foodFactory = Container.Resolve<FoodFactory>();
foodFactory.Create();
foodFactory.Create();
foodFactory.Create();
}
public class FoodFactory:PlaceholderFactory<Food>
{
}
}
UnderTransform(Transform) 设置父物体
Container.BindFactory<Bullet, Bullet.Factory>()
.FromComponentInNewPrefab(BulletPrefab)
.UnderTransform(BulletTransform);
UnderTransform(Method) 使用一个方法来提供Transform
Container.BindFactory<Foo, Foo.Factory>()
.FromComponentInNewGameObject()
.UnderTransform(GetParent);
Transform GetParent(InjectContext context)
{
if (context.ObjectInstance is Component)
{
return ((Component)context.ObjectInstance).transform;
}
return null;
}
Optional Binding
(笔者观点:一旦声明为 [InjectOptional],就可以认为已经完成绑定,引用类型绑定为Null,值类型绑定为默认值(如int 默认为 0);如果此时通过代码进行人工绑定,则绑定值以代码绑定为准)
可以将一些依赖项声明为可选绑定,如下所示:
public class Bar
{
public Bar( [InjectOptional] IFoo foo)
{
...
}
}
...
// 即使删除掉也不影响使用
Container.Bind<IFoo>().AsSingle();
同样,Identifier也可以使用:
public class Bar
{
public Bar( [Inject(Optional = true, Id = "foo1")] IFoo foo)
{
...
}
}
如果在任何 Installers 中都没有绑定可选的依赖项,那么它将被注入为null。
如果依赖项是原始类型(例如int,float,struct),则将使用其默认值(例如,对于int 为0)进行注入。
您还可以使用标准C#方式分配一个明确的默认值,例如:
public class Bar
{
public Bar(int foo = 5)
{
...
}
}
...
// 删除掉这行代码,5就会被使用
Container.BindInstance(1);
还要注意,以下这种情况下[InjectOptional]是不必要的,因为默认值已经隐含了它。或者,您可以将基本参数定义为可为空,并根据是否提供该参数执行逻辑,例如:
public class Bar
{
int _foo;
//int? 表示int 类型可以置为空
public Bar([InjectOptional] int? foo)
{
if (foo == null)
{
// Use 5 if unspecified
_foo = 5;
}
else
{
_foo = foo.Value;
}
}
}
...
// 如果执行下行代码,那么_foo=1,如果没有执行则_foo=5
Container.BindInstance(1);
条件绑定
在一些情况下,可以使用下面的语法在给不同的类注入依赖时进行限制:
//如果 Bar1 Bar2 中均有声明为IFoo的变量,这是注入的是不一样的
Container.Bind<IFoo>().To<Foo1>().AsSingle().WhenInjectedInto<Bar1>();
Container.Bind<IFoo>().To<Foo2>().AsSingle().WhenInjectedInto<Bar2>();
上述是一种简写形式,使用 “When” 的通用写法如下:
Container.Bind<IFoo>().To<Foo>().AsSingle().When(context => context.ObjectType == typeof(Bar));
InjectContext类(作为上面的context参数传递)包含以下信息,您可以在条件中使用该信息:
- **Type ObjectType **:注入依赖的类(即我们将为该类注入依赖项),可使用这个选项为不同的类注入不同的内容。
- object ObjectInstance
- string Identifier
- object ConcreteIdentifier
- string MemberName
- Type MemberType
- InjectContext ParentContext
- bool Optional 如果在要注入的字段上声明了[InjectOptional],则为True
本节将通过案例进行详细介绍:链接地址
List绑定
当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方法添加它们的顺序相同。(当使用Sub_Container时有所不同,列表将首先由关联的Sub_Container排序,从最底层的Sub_Container中获取,然后是parent,然后是 grandparent)
全局绑定(Project Context)
一个游戏中必然包含多个场景,如何让某些依赖项在所有场景中都起作用呢?在Zenject中可以通过将 installers 添加到ProjectContext 中来实现这个目的。
第一步:创建一个ProjectContext的Prefab
点击 Edit -> Zenject -> Create Project Context 之后,在Resources文件夹中将会出现需要的预制体。
Create -> Zenject -> ProjectContext ,也可创建出Prefab
点击查看Prefab,就会发现其Inspector面板和Scene Context的面板几乎相同
最简单的配置方式就是通过拖拽直接添加 Installers ,除了Installers ,还可以将自己定义的Monobehaviours类直接拖拽到Prefab上。
第二步 运行程序
当启动任何包含了SceneContext的场景时,ProjectContext 会被优先初始化,在此处添加的所有 Installers 将被执行,并且您在其中添加的绑定将可用于项目中的所有场景。 ProjectContext游戏对象设置为DontDestroyOnLoad,因此在更改场景时不会将其销毁。
注:ProjectContext 只有在加载第一个具有SceneContext的场景是初始化一次,当中途切换场景,不会再次调用ProjectContext,但先前的绑定可以保留在新场景中。可以与Scene Installers 相同的方式在ProjectContext的 Installers 中声明继承了ITickable / IInitializable / IDisposable的对象,结果是IInitializable.Initialize在每次运行中仅被调用一次,IDisposable.Dispose仅在应用程序关闭时被调用。
原理剖析:为什么添加到全局 installer 中的所有绑定可注入单独场景中的类?
每个场景中的 Container 是 ProjectContext Container的 ”子级“。
ProjectContext是一个非常好的保存跨场景对象的位置。但是这也可能带来一些不利影响,例如,即使使用Zenject编写一些简单的测试场景,也会加载ProjectContext,这并不是我们想要的。为了解决这个问题,最好使用 Scene Parenting 功能,使用这个方法可以选择哪些场景可以继承公共绑定。
通过ProjectContext实例化的gameobject都默认位于其下方,如果希望希望实例化的对象出在hierarchy 根目录(仍标记为DontDestroyOnLoad),可取消ProjectContext 的Inspector面板中’Parent New Objects Under Context’ 的选定。
Identifiers(同一类型完成多个绑定)
在前面讲到的技术大多都是为一个类型绑定一个实例,绑定多个实例的情况被绑定成了List<>,那么我们可不可以一个类型具有多个不同的绑定,还不是List呢? 答案是肯定的,这就是使用Identifiers技术,下面将通过例子做详细讲解。
在下面的例子中,Bar1 类 中的 _foo字段 会被实例化为 Foo1 类型,而 Bar2 中的 _foo 字段会被会被实例化为 Foo2 类型。
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;
}
这项技术也可以通过构造函数注入和方法注入被使用:
public class Bar
{
Foo _foo;
public Bar(
[Inject(Id = "foo")]
Foo foo)
{
}
}
在很多实例中(可以参考上边的例子),ID都被设置为了string 类型,实际上你可以使用任意你喜欢的类型,有时候使用枚举类型可能会有意想不到的优势。(只要实现了Equals运算符,自定义类型也允许被使用)
enum Cameras
{
Main,
Player,
}
Container.Bind<Camera>().WithId(Cameras.Main).FromInstance(MyMainCamera);
Container.Bind<Camera>().WithId(Cameras.Player).FromInstance(MyPlayerCamera);
非泛型绑定(Non Generic bindings)
在一些案例中,编译时您可能不知道要绑定的具体类型,在这些情况下,可以使用Bind方法的重载,该方法采用System.Type值而不是确切的参数。
// These two lines will result in the same behaviour
Container.Bind(typeof(Foo));
Container.Bind<Foo>();
还可以多个参数同时传递:
Container.Bind(typeof(Foo), typeof(Bar), typeof(Qux)).AsSingle();
// The above line is equivalent to these three:
Container.Bind<Foo>().AsSingle();
Container.Bind<Bar>().AsSingle();
Container.Bind<Qux>().AsSingle();
To方法也同理:
Container.Bind<IFoo>().To(typeof(Foo), typeof(Bar)).AsSingle();
// The above line is equivalent to these two:
Container.Bind<IFoo>().To<Foo>().AsSingle();
Container.Bind<IFoo>().To<Bar>().AsSingle();
还可以两个一起搞一个自动组合:
Container.Bind(typeof(IFoo), typeof(IBar)).To(typeof(Foo), typeof(Bar)).AsSingle();
// The above line is equivalent to these:
Container.Bind<IFoo>().To<Foo>().AsSingle();
Container.Bind<IFoo>().To<Bar>().AsSingle();
Container.Bind<IBar>().To<Foo>().AsSingle();
Container.Bind<IBar>().To<Bar>().AsSingle();
当一个类实现多个接口时:
Container.Bind(typeof(ITickable), typeof(IInitializable), typeof(IDisposable)).To<Foo>().AsSingle();
实现的接口太多不想一个一个写时:
Container.BindInterfacesTo<Foo>().AsSingle();
这里提供的API还是很充足的,使用的时候怎么舒服怎么来,看大家的使用习惯。
批量绑定(Convention Based Binding)
灵魂发问:在什么情景下使用这个技术呢?答案就是你需要大量绑定而自己又懒得敲代码的时候。
- 如果想通过一个命名约定来决定如何绑定到Container。(如根据前缀、后缀或者更复杂的正则表达式来确定绑定规则)。
- 如果你想通过自定义attributes 来确定绑定规则。
- 如果你想自动绑定一个程序集/命名空间中实现了某个接口的所有类。
使用 “convention over configuration” 可以定义一个绑定框架,其他人也可以复用你的框架,快速帅气的完成任务而不是苦逼的一个个添加绑定。这是Ruby on Rails,ASP.NET MVC等框架所遵循的哲学。当然有利有弊,需要你权衡使用。
这和上面的 Non Generic bindings 绑定有些类似,区别在于不需要使用Bind()和To() 方法并提供绑定列表。而是使用Fluent API。举个栗子:要将IFoo绑定到在整个代码库中实现它的所有类
Container.Bind<IFoo>().To(x => x.AllTypes().DerivingFrom<IFoo>());
同样Band方法中也可以使用Fluent API,来描述你要绑定的类。也可以Bind()和To() 都用,随意组合。
先写一个 Fluent API 的完整形式在这,后边对其进行一一解释,最后通过例子教你怎么用。
x.InitialList().Conditional().AssemblySources()
InitialList :这是你需要绑定的初始列表,通过下列枚举的指令过滤出你想绑定的类或接口。这些指令看名字都可以才出来进行了怎样的过滤。
- AllTypes 所有的类型
- AllNonAbstractClasses 所有的非Abstract类
- AllAbstractClasses 所有的Abstract类
- AllInterfaces 所有的接口
- AllClasses 所有的Class
**Conditional ** :过滤 InitialList 中类和接口的过滤器(也可以理解为筛选器,根据匹配条件筛选出希望完成绑定的类)。
- DerivingFrom 保留从T派生的匹配类型
- DerivingFromOrEqual 保留从T派生的匹配类型或等于T的类型
- WithPrefix(value) 保留以value开头的类型,前缀匹配
- WithSuffix(value) 保留以value结尾的类型,后缀匹配
- WithAttribute 保留被[T]特性标明的类型
- WithoutAttribute 保留没有被[T]特性标明的类型
- WithAttributeWhere (predicate) 保留被[T]特性标注且通过自定义predicate匹配的类型,这很有用,因此可以使用赋予attribute 的数据来创建绑定
- InNamespace(value) 保留命名空间等于value的类型
- InNamespaces(value1, value2, etc.) 保留命名空间等于value1, value2, etc.的类型
- MatchingRegex(pattern) 保留名称匹配正则表达式pattern的类型
- Where(predicate) 通过predicate进行匹配
**AssemblySources ** :在程序集层面予以限定
- FromAllAssemblies 在所有已加载的程序集中查找类型,默认值。
- FromAssemblyContaining 在类型T所在的所有程序集中查找
- FromAssembliesContaining(type1, type2, …) 在包含给定类型的所有程序集中查找
- FromThisAssembly 在调用此方法的程序集中查找
- FromAssembly(assembly) 在给定的程序集中查找类型
- FromAssemblies(assembly1, assembly2, …) 在给定的程序集中查找类型
- FromAssembliesWhere(predicate) 在所有与给定predicate匹配的程序集中查找
Demos:
可以在绑定中将下面的条件任意组合进行匹配查找。下面的示例中没有指定程序集,因此默认使用 **FromAllAssemblies ** 在所有已加载的程序集中查找类型。 (绑定的前提要满足,两个毫无关系的类肯定无法绑定)
- 将IFoo绑定到在整个代码库中实现它的所有类:
Container.Bind<IFoo>().To(x => x.AllTypes().DerivingFrom<IFoo>());
//等同于
Container.Bind<IFoo>().To(x => x.AllNonAbstractTypes());
//使用AllNonAbstractTypes而不是AllTypes 是为了避免将 IFoo 绑定 到自身
- 绑定”MyGame.Foos“命名空间内,所有的是实现了”IFoo“接口的类
Container.Bind<IFoo>().To(x => x.AllTypes().DerivingFrom<IFoo>().InNamespace("MyGame.Foos"));
- 绑定具有"Controller" 前缀的类
Container.Bind<IController>().To(x => x.AllNonAbstractTypes().WithSuffix("Controller"));
//等价于
Container.Bind<IController>().To(x => x.AllNonAbstractTypes().MatchingRegex("Controller$"));
- 绑定所有具有"Widget后缀的类到Foo
Container.Bind<object>().To(x => x.AllNonAbstractTypes().WithPrefix("Widget")).WhenInjectedInto<Foo>();
- 将所有接口绑定到实现了它们的非抽象类上,并且这些类都在"MyGame.Things"命名空间
Container.Bind(x => x.AllInterfaces())
.To(x => x.AllNonAbstractClasses().InNamespace("MyGame.Things"));