Zenject框架(十二)
默认的场景的父场景(Default Scene Parents)
使用场景的父场景合同名称(parent contract names)而不是项目上下文(ProjectContext)来添加跨场景共享的绑定的一个缺点是,您必须记住在运行场景前,要在Unity内配置场景层次结构以包含正确的场景。 你不能像使用项目上下文(ProjectContext)时一样简单地打开不同的场景,然后点击运行。 因此,为了解决这个问题,zenject允许在尚未加载现有值的情况下为不同的合同名称指定默认值。
还按上面的例子,我们希望在打开飞船场景后可以立即点击播放而无需首先放置一个环境场景。 要实现此目的,请在Project面板中右击,然后选择Creat- >Zenject - >Default Scene Contract Config
。 请注意,您必须通过在Resources文件夹上右击。
创建此对象后,您可以单击ZenjectDefaultSceneContractConfig
对象并通过输入合同名添加任意数量的默认值,然后将对应的场景文件从Project面板中拖拽到Scene属性中。 执行此操作后,您应该能够直接运行Ship场景,并且将自动加载默认环境场景。
请注意,使用场景装饰器(scene decorators)时也将使用给定合同的默认场景
请注意,这是仅限编辑器的功能。 默认合同名称不会在构建中使用。 在这些情况下,您必须在代码中自己显式加载正确的父场景。
ZenAutoInjecter
正如在前面的工厂部分所述,任何动态创建的对象都需要通过zenject创建才能注入。你不能简单地执行GameObject.Instantiate(prefab),或者调用new Foo()。
但是,这有时会出现问题,尤其是在使用其他第三方库时。例如,某些网络库会自动实例化预设体来在多个客户端中同步状态。在这些情况下仍然需要执行zenject注入。
为了解决这些问题,我们书写了一个名为ZenAutoInjecter
的脚本,您可以将其添加到这些对象中。如果你将这个脚本添加到预设体中,那么你应该能够调用GameObject.Instantiate并且注入会自动发生。
添加此组件后,您可能会注意到其上有一个名为“Container Source”的字段。 此值将确定使用哪个容器注入,有以下选项:
- SearchInHiearchy-在实例化预设体的场景层次面板中查找容器,因此,如果预制件在GameObjectContext下面实例化,它将使用与GameObjectContext相关联的容器。 如果它在DontDestroyOnLoad对象下实例化,那么它将使用ProjectContext容器。否则将使用实例预设体的场景的SceneContext容器。
2.SceneContext-总是使用当前SceneContext容器。
3.ProjectContext-总是使用ProjectContext容器
场景装饰器(Scene Decorators)
除了上面的父场景外,场景装饰器提供了另一种在多场景中使用zenject的方式。区别在于,使用场景装饰器时,多个场景将共享相同的容器,因此所有的场景都能访问其他场景中的绑定(使用父场景时,只有子场景能访问父场景的绑定,反之亦然)
考虑场景装饰器的另一种方式是,它是一种实现跨场景注入数据过程的更好的方式。也就是说,它可以用来向另一个场景中添加行为而不必改变对应场景的安装器
通常,当您想根据某些条件为给定场景自定义不同的行为时,您可以在MonoInstallers上使用boolean或enum属性,然后根据设置的值添加不同的绑定。 但是,场景装饰器方法有时更清晰,因为它不涉及更改主场景。
例如,假设我们要为您的主场景添加一些特殊的键盘快捷键以便进行测试。 为了使用装饰器执行此操作,请按以下步骤操作:
- 打开主场景
- 在层次面板中场景名称旁边右击选择
Add New Scene
- 拖动新场景至主场景的上方
- 在新场景层次面板中右击选择
Zenject -> Decorator Context
- 选中装饰器上下文(Decorator Context)并将“Decorated Contract Name”字段设置为“Main”
- 在主场景中选中SceneContext并添加具有相同值的contract name('Main')
- 创建新脚本,输入以下内容。然后将该脚本添加到装饰器场景中的新游戏对象上,然后将游戏对象拖拽到
SceneDecoratorContext
的Installers
属性中
public class ExampleDecoratorInstaller : MonoInstaller
{
public override void InstallBindings()
{
Container.Bind<ITickable>().To<TestHotKeysAdder>().AsSingle();
}
}
public class TestHotKeysAdder : ITickable
{
public void Tick()
{
if (Input.GetKeyDown(KeyCode.Space))
{
Debug.Log("Hotkey triggered!");
}
}
}
注意以下几点:
-
如果您运行场景,它现在应该与主场景完全相同,除非在装饰器安装程序中添加了功能。 另请注意,虽然此处未显示,但两个场景都可以访问彼此的绑定,就好像所有内容都在同一个场景中一样。
-
验证命令(CTRL + ALT + V)可用于快速验证不同的多场景设置。 如果您在执行此操作时发现场景已卸载,请参阅此处。
-
装饰器场景必须在他们装饰的场景之前加载。
-
Unity目前没有内置的方法来保存和恢复多场景设置。 我们使用一个简单的编辑器脚本,如果感兴趣,可以在这里找到。
-
最后,如果您想节省一些时间,可以为上面使用的合同名称添加默认场景
子容器和外观(Sub-Containers And Facades)
见后续章节
编写自动化单元测试/集成测试(Writing Automated Unit Tests / Integration Tests)
见后续章节
Zenject原理
在理解Zenject的设计方面,有一点需要注意的是,与许多其他框架不同,它并不是固执己见的。 许多框架,例如ROR,ECS,ASP.NET MVC等,为您提供了必须严格遵循的设计选择。 Zenject唯一的假定是您正在编写面向对象的代码,否则,您如何设计代码完全取决于您。
在我看来,依赖注入对面向对象编程非常重要。 如果没有依赖注入框架,组合根很快就会成为一个难以维护的问题。 因此,依赖注入框架也是相当重要的。
这就是Zenject所追求的 - 一个针对Unity的依赖注入框架。 Zenject中有很多功能,但它们都是可选的。如果你愿意,你可以遵循传统的Unity开发模式并使每个类使用MonoBehaviour,只有一个例外是你使用[Inject]而不是[SerializeField]。或者你可以完全放弃MonoBehaviours并使用包含的接口,如ITickable和IInitializable。由您决定要使用哪种设计。
当然,与更严格的框架相比,使用DI框架有一些缺点。 主要缺点是对新的开发人员来说,快速的了解代码库并运行会更具挑战性,因为他们需要了解以前的开发人员选择的特定架构。 而使用严格的框架时,开发人员可以获得非常明确的跟踪途径,因此可以更快地提高工作效率。 使用严格的框架时,更难以犯下大的设计错误。 然而,这种严格也存在局限性,因为框架强制执行的任何设计决策不一定是解决每个问题的理想选择。