VContainer-getting-started/hello-world | 入门——Hello World

VContainer 基础集成步骤如下:

  • 在场景中创建一个继承自 LifetimeScope 的组件:管理容器(container)和作用域(scope)。
  • 在 LifetimeScope 子类中使用 C# 代码注册依赖项。这是组合根(composition root)。
  • 运行场景时将自动构建容器并与Unity生命周期对接,LifetimeScope 会自动构建容器并将其分发到自己的 PlayerLoopSystem 中。

:::note
作用域(Scope)在游戏运行时会频繁创建/销毁。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

在 Hierarchy 面板中右键单击并选择创建空对象。并将其命名为 GameLifetimeScope

然后将之前创建的脚本(GameLifetimeScope.cs)附加到空对象上。

4. 如何使用新的 HelloWorldService?

注册的对象将自动执行依赖注入。
如下所示:

using VContainer;
using VContainer.Unity;

namespace MyGame
{
    public class GamePresenter
    {
        readonly HelloWorldService helloWorldService;

        public GamePresenter(HelloWorldService helloWorldService)
        {
            this.helloWorldService = helloWorldService;
        }
    }
}

同时需要注册该类(GamePresenter)。

builder.Register<HelloWorldService>(Lifetime.Singleton);
+ builder.Register<GamePresenter>(Lifetime.Singleton);

5. 对接 Unity 生命周期

要在 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);
        }
    }
posted @   凌雪寒  阅读(11)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!
点击右上角即可分享
微信分享提示