将VContainer集成到应用程序中的基本方法是:
- 在场景中创建一个继承自
LifetimeScope
的组件。它有一个容器和一个作用域。 - 在LifetimeScope的子类中使用C#代码注册依赖项。这是组合根。
- 当播放场景时,LifetimeScope会自动构建容器并将其分发到自己的PlayerLoopSystem中。
:::note
通常,“作用域”在游戏过程中会反复创建和销毁。LifetimeScope
假设了这一点,并具有父子关系。
:::
1. 编写一个依赖于其他类的类
让我们说“Hello world”。
namespace MyGame
{public class HelloWorldService{public void Hello(){UnityEngine.Debug.Log("Hello world");}}
}
2. 定义组合根
接下来,让我们编写一个可以自动装配类的设置。
- 在项目选项卡中的文件夹中右键单击,选择创建 -> C#脚本。
- 将其命名为
GameLifetimeScope.cs
。
:::note
VContainer会自动为以*LifetimeScope结尾的C#脚本生成模板。
:::
你指示builder
并注册上面的类。
using VContainer;
using VContainer.Unity;namespace MyGame
{public class GameLifetimeScope : LifetimeScope{protected override void Configure(IContainerBuilder builder){
+ builder.Register<HelloWorldService>(Lifetime.Singleton);}}
}
:::note
VContainer始终需要显式地传递Lifetime
参数。这为我们提供了透明性和一致性。
:::
3. 创建附加了LifetimeScope的GameObject
在层次结构选项卡中右键单击并选择创建空对象。并将其命名为GameLifetimeScope
。
然后附加上面创建的组件。
4. 如何使用新的HelloWorldService?
注册的对象将自动具有依赖注入功能。
如下所示:
using VContainer;
using VContainer.Unity;namespace MyGame
{public class GamePresenter{readonly HelloWorldService helloWorldService;public GamePresenter(HelloWorldService helloWorldService){this.helloWorldService = helloWorldService;}}
}
并且让我们也注册这个类。
builder.Register<HelloWorldService>(Lifetime.Singleton);
+ builder.Register<GamePresenter>(Lifetime.Singleton);
5. 在PlayerLoopSystem上执行注册的对象
要在Unity中编写应用程序,我们必须中断Unity的生命周期事件。
(通常是MonoBehaviour的Start / Update / OnDestroy / 等)
使用VContainer注册的对象可以独立于MonoBehaviour执行此操作。
这是通过实现和注册一些标记接口自动完成的。
using VContainer;
using VContainer.Unity;namespace MyGame{
- public class GamePresenter
+ public class GamePresenter : ITickable{readonly HelloWorldService helloWorldService;public GamePresenter(HelloWorldService helloWorldService){this.helloWorldService = helloWorldService;}+ void ITickable.Tick()
+ {
+ helloWorldService.Hello();
+ }}}
现在,Tick()
将在Unity的Update时机执行。
因此,通过标记接口保持任何副作用入口点是一个好习惯。
(从设计上讲,对于MonoBehaviour来说,使用Start / Update等已经足够了。VContainer的标记接口是一个将领域逻辑和表现逻辑的入口点分离的功能。)
我们应该将其注册为在Unity的生命周期事件上运行。
- builder.Register<GamePresenter>(Lifetime.Singleton);
+ builder.RegisterEntryPoint<GamePresenter>();
:::note
- RegisterEntryPoint<GamePresenter>()是注册与Unity的PlayerLoop事件相关的接口的别名。
- 类似于Register<GamePresenter>(Lifetime.Singleton).As<ITickable>()
- 注册生命周期事件而不依赖于MonoBehaviour有助于解耦领域逻辑和表现逻辑!
:::
如果你有多个EntryPoints,你也可以使用以下声明进行分组。
builder.UseEntryPoints(Lifetime.Singleton, entryPoints =>
{entryPoints.Add<GamePresenter>();// entryPoints.Add<OtherSingletonEntryPointA>();// entryPoints.Add<OtherSingletonEntryPointB>();// entryPoints.Add<OtherSingletonEntryPointC>();
})
这使得EntryPoints在设计中得到特殊处理更加清晰。
6. 控制反转(IoC)
它通常响应用户输入等事件调用逻辑。
考虑以下视图组件。
using UnityEngine.UI;
public class HelloScreen : MonoBehaviour
{public Button HelloButton;
}
在普通的Unity编程中,你将逻辑调用嵌入到HelloScreen中,但如果你使用DI,你可以将HelloScreen与任何控制流分离。
namespace MyGame
{
- public class GamePresenter : ITickable
+ public class GamePresenter : IStartable{readonly HelloWorldService helloWorldService;
+ readonly HelloScreen helloScreen; public GamePresenter(HelloWorldService helloWorldService,
+ HelloScreen helloScreen){this.helloWorldService = helloWorldService;
+ this.helloScreen = helloScreen;}+ void IStartable.Start()
+ {
+ helloScreen.HelloButton.onClick.AddListener(() => helloWorldService.Hello());
+ }}
}
通过这样做,我们成功地将领域逻辑 / 控制流 / 视图组件分离开来。
- GamePresenter: 仅负责控制流。
- HelloWorldService: 仅负责可以随时调用的功能
- HelloScreen: 仅负责视图。
在VContainer中,你需要注册依赖的MonoBehaviour。别忘了注册HelloScreen。
public class GameLifetimeScope : LifetimeScope{
+ [SerializeField]
+ HelloScreen helloScreen;protected override void Configure(IContainerBuilder builder){builder.RegisterEntryPoint<GamePresenter>();builder.Register<HelloWorldService>(Lifetime.Singleton);
+ builder.RegisterComponent(helloScreen);}}