文章目录[x]
- 0.1:01.Zenject框架简介
- 0.2:02.在Unity中安装Zenject
- 0.3:03.测试环境是否搭建好
- 0.4:04.Zenject注入方式
- 0.5:05. Zenject Installer(安装器)与Container(容器)
- 0.6:06.Bind(绑定)
- 0.7:07.SignalBus通信
- 0.8:08.使用Non-MonoBehaviour
01.Zenject框架简介
Zenject是一个轻量级的高性能依赖注入框架,专门针对Unity 3D而构建(但它也可以在Unity之外使用)。它可用于将应用程序转换为具有高度分段职责的松散耦合部分的集合。然后,Zenject 可以将这些部件以许多不同的配置粘合在一起,让您能够以可扩展且极其灵活的方式轻松编写、重用、重构和测试代码。
Github地址:https://github.com/modesttree/Zenject
02.在Unity中安装Zenject
环境要求: Unity2018LTS及以上(Zenject版本9.1)
下载导入: 可以使用以下任一方法安装 Zenject
1. 从发布页面下载,您可以选择以下选项:
- Zenject-WithAsteroidsDemo.vX.X.unitypackage - 这相当于您在资源商店中找到的内容,并且包含示例游戏“Asteroids”和“SpaceFighter”作为软件包的一部分。Zenject的所有源代码都包含在这里。
- Zenject.vX.X.unitypackage - 与上述相同,但没有示例项目。
- Zenject-NonUnity.vX.X.zip - 如果您想在 Unity 之外使用 Zenject(例如,就像普通的 C# 项目一样),请使用此选项
2. 从 Unity 资源商店
- 因为一些原因,作者已经从Unity资源商店下架该插件。
3. UPM 分支
- 此选项是功能请求。该包将在 Unity 准备就绪时发布。Unity 没有提供有关开发状态的任何见解。但期望在2020年的第一个或第二个版本中。
4. 从Github下载源码
- 同步 git 存储库后,请注意,您必须通过在vs上构建解决方案来构建 。或者,如果您愿意,可以从“发布”部分获取Zenject-Usage.dllAssemblyBuild\Zenject-usage\Zenject-usage.slnZenject-Usage.dll
- 然后,您可以将该目录复制到您自己的 Unity3D 项目中。UnityProject/Assets/Plugins/Zenject
推荐使用第一种方式,从Github页面的release页面下载UnityPackage包,然后导入Unity项目中。
请注意,将 Zenject 导入 Unity 项目时,您可以取消选中 OptionalExtras 文件夹下的任何文件夹,以防您不想包含它,或者如果您只想要核心 zenject 功能,您可以取消选中整个 OptionalExtras 目录。
03.测试环境是否搭建好
1.新建脚本DataBinder.cs
using UnityEngine; using Zenject; public class DataBinder : MonoInstaller { public override void InstallBindings() { //项目运行时会首先执行,Zenject把Data01作为单例类(只是Zenject中的单例) Container.Bind<Data01>().AsSingle(); } } public class Data01 { public void LogData(string inData) { Debug.Log(inData); } }
2.新建脚本ZenjectTest01.cs
using UnityEngine; using Zenject; public class ZenjectTest01:MonoBehaviour { //项目运行时Zenject根据‘Inject’特征为其赋值,data01为Installer中Data01的单例实体。 [Inject]private Data01 data01; private void Awake() { if(data01 != null) { data01.LogData("Hello zenject"); } } }
3.返回Unity编辑器在Hierarchy窗口中右键创建Zenject->Scene Context,挂载‘DataBinder’到该游戏物体。为Scene Context指定MonoInstallers。
4.新建一个空物体,然后挂载‘ZenjectTest01’到该物体。
5. Edit->Zenject->Validate Current Scenes. 让zenject检测是否有误。
6. 运行后控制台会输出“Hello zenject”。
7. 个人理解 SceneContext MonoBehavior是应用程序的入口点,Zenject在启动场景之前设置了所有各种依赖项。要向 Zenject 场景添加内容,您需要编写 Zenject 中称为“Installer”的内容,它声明所有依赖项及其相互关系。所有标记为“NonLazy”的依赖项都是在安装程序运行后自动创建的,这就是我们在上面添加的 Data01 类在启动时创建的原因。项目运行时Scene Installer会创建出我们需要注入的实体单例,然后再根据[Inject]特征为需要的地方自动进行赋值。
6. 运行后控制台会输出“Hello zenject”。
7. 个人理解 SceneContext MonoBehavior是应用程序的入口点,Zenject在启动场景之前设置了所有各种依赖项。要向 Zenject 场景添加内容,您需要编写 Zenject 中称为“Installer”的内容,它声明所有依赖项及其相互关系。所有标记为“NonLazy”的依赖项都是在安装程序运行后自动创建的,这就是我们在上面添加的 Data01 类在启动时创建的原因。项目运行时Scene Installer会创建出我们需要注入的实体单例,然后再根据[Inject]特征为需要的地方自动进行赋值。
04.Zenject注入方式
上面的例子中我们使用了成员变量注入的方式(在字段前添加[Inject]特征),Zenject为我们提供4中注入方式构造函数注入、成员变量注入、属性注入和方法注入。
1. 构造注入
public class Foo { IBar _bar; public Foo(IBar bar) { _bar = bar; } }
2.成员变量注入
字段注入在调用构造函数后立即发生。所有标有该属性的字段都将在容器中查找并赋予一个值。请注意,这些字段可以是私有的,也可以是公共的,并且仍会被注入。
public class Foo { [Inject] IBar _bar; }
3.属性注入
属性注入的工作方式与字段注入相同,但应用于 C# 属性。就像字段一样,在这种情况下,set可以是私有的,也可以是公共的。
public class Foo { [Inject] public IBar Bar { get; private set; } }
4.方法注入
public class Foo { IBar _bar; Qux _qux; [Inject] public void Init(IBar bar, Qux qux) { _bar = bar; _qux = qux; } }
**注意事项**
- 方法注入一般用于MonoBehaviour,因为MonoBehaviour不能拥有构造函数
- 可以拥有多个注入方法,按照从基类到派生类的顺序来调用
- 方法注入在所有注入方式之后运行
- 当你使用一个引用的时候,你先要确保引用的类已经绑定到容器里面
- 不要使用注入方法来初始化逻辑
**推荐使用方式**
构造函数注入、方法注入和成员变量注入、属性注入对比优缺点:
- 构造函数注入在类实例化的时候创建一次,并且是强制创建
- 构造函数注入可以保证类之间不会被循环引用,Zenject任何注入方式禁止循环引用类
- 如果你需要复用代码,构造函数、方法注入是一种轻快的解决方式
- 构造函数、方法注入代码更清晰,方便阅读源码
05. Zenject Installer(安装器)与Container(容器)
1. 什么是安装器
可以组织收集为每个子系统绑定相关内容,并且可以将其分组绑定到一个可复用的对象内容里面管理;
在Zenject中,这些可以复用物体称作‘installer’安装器,安装器最主要的工作是处理那些需要被注入的类,以下示例:
public class FooInstaller : MonoInstaller { public override void InstallBindings() { Container.Bind<Bar>().AsSingle(); Container.BindInterfacesTo<Foo>().AsSingle(); // etc... } }
2.安装器按照继承关系分类
MonoInstaller、Installer和ScriptableObjectInstaller
- 最常用的是继承MonoInstaller,可以挂载到游戏物体上。
using zenject.bind; using Zenject; namespace zenject.installer { public class ZenjectInstaller : MonoInstaller<ZenjectInstaller> { public override void InstallBindings() { Container.Bind<ZenjectBind>().AsSingle(); Container.Bind<IBinded>().To<Binded>().AsSingle(); } } } //MonoInstaller是继承自MonoBehaviour,由于安装器需要第一时间运行; //一般会定义一个SceneContext存储在Unity的Scene当中,在游戏Splash的时候启动这些代码,也就是Logo显示的时候; //重写父类里面的InstallBindings(),在这个方法里面绑定需要构建的引用的关系。
- 继承Installer,一般用来作为子安装器使用,因为安装器需要在Project Contex或者Scene Context中赋值才能执行。
using zenject.bind; using Zenject; namespace zenject.installer { public class ZenjectInstaller : MonoInstaller<ZenjectInstaller> { public override void InstallBindings() { SubGroupInstaller.Install(Container); } } public class SubGroupInstaller : Installer<SubGroupInstaller> { public override void InstallBindings() { Container.Bind<ZenjectBind>().AsSingle(); Container.Bind<IBinded>().To<Binded>().AsSingle(); } } } //Installer一般用于引用关系复杂的时候,起到分组绑定;符合软件设计的开闭原则,由于不是Mono的继承,项目里面可以拥有多个分组Installer信息;跟MonoInstaller不一样,必须要重写父类的方法
- 继承ScriptableObjectInstaller,一般作为配置文件安装器,在Project Context中指定。ScriptableObjectInstaller需要我们先去Projec窗口中生成一个Scriptable物体,方便修改里面配置信息。
using System; using UnityEngine; using Zenject; namespace zenject.installer { //创建右键菜单,生成Scriptable物体 [CreateAssetMenu(fileName = "ZenjectScriptableInstaller", menuName = "Installers/ZenjectScriptableInstaller")] public class ZenjectScriptableInstaller : ScriptableObjectInstaller<ZenjectScriptableInstaller> { public Player.Settings Player; public override void InstallBindings() { Container.BindInstances(Player); } } 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; } } }
3. 安装器按照使用位置分类 Scene Installer和Project Installer
- Scene Installer在Scene Context中指定。只能在当前场景中有效,一个场景中可以有多个Scene Install。
- Project Installer在Project Context中指定。在所有场景中有效。
如果要使用自定义Project Installer,可以在Resources文件夹中创建一个‘ProjectContext’的预制体,预制体上挂载我们需要在整个项目中使用的Installer。
4. Container容器,容器是Zenject存放被注入类的地方。
06.Bind(绑定)
前面两节介绍了Injection和Installer,本节介绍另外一个基础概念Bind。Injection、Bind、Installer是Zenject最基础的概念设计,熟悉之后可以开始写Zenject项目了。
1. **什么是绑定**
- 每个依赖注入框架最终都只是一个将类型绑定到实例的框架。
- 在 Zenject 中,依赖映射是通过将绑定添加到称为容器的东西来完成的。然后,容器应该“知道如何”通过递归解析给定对象的所有依赖项来创建应用程序中的所有对象实例。
- 当要求容器构造给定类型的实例时,它会使用 C# 反射来查找构造函数参数的列表,以及用 [Inject] 属性标记的所有字段/属性。然后,它尝试解析这些必需依赖项中的每一个,它使用这些依赖项调用构造函数并创建新实例。
- 因此,每个 Zenject 应用程序都必须告诉容器如何解析这些依赖项中的每一个,这是通过绑定命令完成的。
- 通俗的可以理解为容器通过绑定类型来创建绑定实例,再把创建的实例赋值到各个注入的位置。
2. 常用的绑定方式示例:
//要绑定的类 public class Foo { IBar _bar; public Foo(IBar bar) { _bar = bar; } }
- 最常用的绑定方式
Container.Bind<Foo>().AsSingle(); Container.Bind<IBar>().To<Bar>().AsSingle();
- FromNew() - 同过C#的new操作符来创建实例
Container.Bind<Foo>(); Container.Bind<Foo>().FromNew();
- FromInstance() - 通过创建一个实例来绑定对象到容器,手动创建的对象不能已经被绑定
Container.Bind<Foo>().FromInstance(new Foo()); // You can also use this short hand which just takes ContractType from the parameter type Container.BindInstance(new Foo()); // This is also what you would typically use for primitive types Container.BindInstance(5.13f); Container.BindInstance("foo"); // Or, if you have many instances, you can use BindInstances Container.BindInstances(5.13f, "foo", new Foo());
- FromMethod - 通过自定义的方法来绑定实例
Container.Bind<Foo>().FromMethod(SomeMethod); Foo SomeMethod(InjectContext context) { ... return new Foo(); }
- FromMethodMultiple - 可以有多个手动创建方法返回值
Container.Bind<Foo>().FromMethodMultiple(GetFoos); IEnumerable<Foo> GetFoos(InjectContext context) { ... return new Foo[] { new Foo(), new Foo(), new Foo(), } }
- 还有其他十几种绑定方式可以查看Zenject文档中绑定的介绍。
https://github.com/modesttree/Zenject#binding
07.SignalBus通信
- 快速示例
public class UserJoinedSignal { public string Username; } public class GameInitializer : IInitializable { readonly SignalBus _signalBus; public GameInitializer(SignalBus signalBus) { _signalBus = signalBus; } public void Initialize() { _signalBus.Fire(new UserJoinedSignal() { Username = "Bob" }); } } public class Greeter { public void SayHello(UserJoinedSignal userJoinedInfo) { Debug.Log("Hello " + userJoinedInfo.Username + "!"); } } public class GameInstaller : MonoInstaller<GameInstaller> { public override void InstallBindings() { SignalBusInstaller.Install(Container); Container.DeclareSignal<UserJoinedSignal>(); Container.Bind<Greeter>().AsSingle(); Container.BindSignal<UserJoinedSignal>() .ToMethod<Greeter>(x => x.SayHello).FromResolve(); Container.BindInterfacesTo<GameInitializer>().AsSingle(); } }
- 流程
创建实体类代表一个信号
使用SignalBus来订阅或者取消订阅信号
使用SignalBus来发射信号
08.使用Non-MonoBehaviour
三大框架提供频繁使用接口
ITickable、IInitializable和IDisposable
- -ITickable、ILateTickable、IFixedTickable 帧更新接口,每帧调用
using UnityEngine; using Zenject; namespace zenject.nonmonobehaviour { public class ZenjectNonMonobehaviour : MonoInstaller<ZenjectNonMonobehaviour> { public override void InstallBindings() { Container.Bind<ITickable>().To<ZenjectUpdate>().AsSingle().NonLazy(); } } public class ZenjectUpdate : ITickable { public void Tick() { Debug.Log(Time.deltaTime); } } }
- -IInitializable 初始化逻辑数据,两种方式实现
using UnityEngine; using Zenject; namespace zenject.nonmonobehaviour { public class ZenjectNonMonobehaviour : MonoInstaller<ZenjectNonMonobehaviour> { public override void InstallBindings() { Container.Bind<IInitializable>().To<ZenjectInitialize>().AsSingle().NonLazy(); Container.Bind<ZenjectInitialize2>().AsSingle().NonLazy(); } } public class ZenjectInitialize : IInitializable { public void Initialize() { Debug.Log("ZenjectInitialize, run only once."); } } public class ZenjectInitialize2 { [Inject] public void Initialize() { Debug.Log("ZenjectInitialize2, run only once."); } } }
- -IDisposable 释放内存,销毁回调
using System; using UnityEngine; using Zenject; namespace zenject.nonmonobehaviour { public class ZenjectNonMonobehaviour : MonoInstaller<ZenjectNonMonobehaviour> { public override void InstallBindings() { Container.Bind<IDisposable>().To<ZenjectDisposable>().AsSingle().NonLazy(); } } public class ZenjectDisposable : IDisposable { [Inject] public void Initialize() { this.Dispose(); } public void Dispose() { Debug.Log("Release memory."); } } }
- -多接口的绑定方式
using System; using UnityEngine; using Zenject; namespace zenject.nonmonobehaviour { public class ZenjectNonMonobehaviour : MonoInstaller<ZenjectNonMonobehaviour> { public override void InstallBindings() { Container.Bind(typeof(IInitializable), typeof(IFixedTickable), typeof(IDisposable), typeof(ZenjectMultiInterface)) .To<ZenjectMultiInterface>().AsSingle().NonLazy(); Container.BindInterfacesAndSelfTo<ZenjectMultiInterface>().AsSingle().NonLazy(); } } public class ZenjectMultiInterface : IInitializable, IFixedTickable, IDisposable { public void Initialize() { Debug.Log("Initialize"); } public void FixedTick() { Debug.Log(Time.deltaTime); } public void Dispose() { Debug.Log("Dispose"); } } }
关于Zenject还有其他很多使用方法,请查找相关资料。
参考资料:https://github.com/modesttree/Zenject
https://blog.csdn.net/unity3d_xyz/article/details/84761636