Unity 商业插件之(五)课外2 - Zenject的一些小Tips(学习备忘)

Zenject

0.依据一哥们的总结,反向总结

1.不是吐槽这哥们,是结合这大哥,从不同面向学习Zenject

2.本来无一物,何处惹尘埃

来自哥们的总结

(看得出总结很用心,也是我们依据的关键,比官方文档靠谱,比一些教学视频靠谱)

1、🎠原生的Instantiate游戏对象不会注入其定义的Inject属性,需要使用Container.InstantiatePrefab来实例化,才能受容器管控。

这么写 Kernel 会空了

必须通过Factory 创建(不确定是不是 Zenject 6.x 最新版本的原因)

cs 复制代码
    public void Initialize()
    {
        //参考代码:
        //_prefabsFactory.Create<PlayerMonoEntity>(Address.Prefabs.Player);
        prefabsFactory.Create("DataM2LevelSample");
    }

2、🌉类似的在SceneManager.LoadScene时,如果Scene没有放置Zenject的SceneContext也无法呗容器管控,其下的对象所定义的Inject属性也无法注入。

SceneContext 倒是真的;和ProjectContext 一样,甚至更加"写死",难道缺了<**ProjectContent>,**Zenject这个框架能跑起来??

3、🚉继承MonoBehavior的单例和普通Csharp单例可以分别如下定义

Container.Bind<GameManager>().FromNewComponentOnNewGameObject().AsSingle(); //自动放置新对象中

Container.Bind<FruitManager>().AsSingle();

4、🌓一些比较贴近Spring的使用

4.1、正常的Inject、IInitializable、 IDisposable接口

4.1.1、MonoBehavior脚本的全局单例

Container.Bind<GameManager>().FromNewComponentOnNewGameObject().AsSingle(); //自动放置新对象中

4.2、ScriptObject数据作为全局配置参数

Container.Bind<BackGroundColorConfigSO>().FromInstance(backGroundColorConfigSO);

Container.Bind<GameManager>().FromNewComponentOnNewGameObject().AsSingle().WithArguments(backGroundColorConfigSO).NonLazy();

4.3、Prefab预制体作为全局配置参数

Container.Bind<GameObject>().WithId("PlayerPrefab").FromInstance(playerPrefab);

Container.Bind<PlayerAttribute>().FromInstance(playerAttribute);

Container.Bind<RespawnPlayerManager>().FromNewComponentOnNewGameObject().AsSingle().WithArguments(playerPrefab, playerAttribute).NonLazy();

(如果你不知道怎么获取 预制体Prefab;我的一个项目,是用上了 addressable 获取Prefab)

cs 复制代码
//注册
        // Config Asset(原来了 GameplayInstaller.cs)
        diContainer.Bind<PlayerConfig>()
            .FromScriptableObject(service.LoadAsset<PlayerConfig>(Address.Configurations.PlayerConfig))
            .AsSingle();
        diContainer.Bind<ProjectileConfig>()
                .FromScriptableObject(service.LoadAsset<ProjectileConfig>(Address.Configurations.ProjectileConfig))
                .AsSingle();
        // UIModule Config
        diContainer.Bind<RocketConfig>()
                .FromScriptableObject(service.LoadAsset<RocketConfig>(Address.Configurations.RocketConfig))
                .AsSingle();

5.🎫补充前面问题1~2,Scene 和GameObjectContent 如何关联

待补充

6.内部 Zenject 结构

Zenject DiContainer 打印内部结构(3 种方案)

可以打印容器全部绑定、内部字典、实例缓存、父子容器层级,优先用内置 API,精细化用反射读取私有字段。

*(对比重点如上面说的:正常的Inject、IInitializable、 IDisposable接口 (小哥说的))

其他都不是重点

(也可以看到 Zenject 内部 Context 和 SceneContext是区隔的,如下对比图:)

cs 复制代码
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Text;
using UnityEngine;
using Zenject;
using static Zenject.DiContainer;

public static class ZenjectDebugger
{
    public static void PrintAllBindings(DiContainer container)
    {
        if (container == null)
        {
            Debug.LogError("ZenjectDebugger: container 为空");
            return;
        }

        // 获取 _providers 字段 (注意是 _providers,不是 _providerMap)
        FieldInfo providersField = typeof(DiContainer).GetField("_providers",
            BindingFlags.NonPublic | BindingFlags.Instance);

        if (providersField == null)
        {
            Debug.LogError("ZenjectDebugger: 未找到 _providers 字段,可能是 Zenject 版本不兼容");
            return;
        }

        var providers = providersField.GetValue(container) as Dictionary<BindingId, List<ProviderInfo>>;

        if (providers == null)
        {
            Debug.LogWarning("ZenjectDebugger: _providers 为空或类型不匹配");
            return;
        }

        var sb = new StringBuilder();
        sb.AppendLine($"=== Zenject 容器绑定信息 (共 {providers.Count} 条) ===");

        int index = 1;
        foreach (var kvp in providers)
        {
            BindingId bindingId = kvp.Key;
            List<ProviderInfo> providerInfos = kvp.Value;

            // BindingId 包含类型和可选标识符
            string typeName = bindingId.Type?.Name ?? "null";
            string identifier = bindingId.Identifier?.ToString() ?? "无";

            sb.AppendLine($"[{index}] 类型: {typeName}");

            if (bindingId.Identifier != null)
            {
                sb.AppendLine($"    标识符: {identifier}");
            }

            // 打印每个 Provider 的信息
            if (providerInfos != null)
            {
                for (int i = 0; i < providerInfos.Count; i++)
                {
                    ProviderInfo providerInfo = providerInfos[i];
                    sb.AppendLine($"    Provider[{i}]: {GetProviderTypeInfo(providerInfo)}");
                }
            }

            sb.AppendLine();
            index++;
        }

        Debug.Log(sb.ToString());
    }

    private static string GetProviderTypeInfo(ProviderInfo providerInfo)
    {
        if (providerInfo == null) return "null";

        // 尝试获取实际 Provider 实例
        FieldInfo providerInstanceField = typeof(ProviderInfo).GetField("Provider",
            BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);

        if (providerInstanceField != null)
        {
            object provider = providerInstanceField.GetValue(providerInfo);
            if (provider != null)
            {
                return provider.GetType().Name;
            }
        }

        return "Unknown";
    }

    // 可选:按类型筛选打印
    public static void PrintBindingsForType<T>(DiContainer container)
    {
        PrintBindingsForType(container, typeof(T));
    }

    public static void PrintBindingsForType(DiContainer container, Type targetType)
    {
        if (container == null || targetType == null) return;

        FieldInfo providersField = typeof(DiContainer).GetField("_providers",
            BindingFlags.NonPublic | BindingFlags.Instance);

        if (providersField == null) return;

        var providers = providersField.GetValue(container) as Dictionary<BindingId, List<ProviderInfo>>;

        if (providers == null) return;

        var sb = new StringBuilder();
        sb.AppendLine($"=== 查找类型 [{targetType.Name}] 的绑定信息 ===");

        bool found = false;
        foreach (var kvp in providers)
        {
            if (kvp.Key.Type == targetType || targetType.IsAssignableFrom(kvp.Key.Type))
            {
                found = true;
                string identifier = kvp.Key.Identifier?.ToString() ?? "无";
                sb.AppendLine($"类型: {kvp.Key.Type?.Name}, 标识符: {identifier}");
            }
        }

        if (!found)
        {
            sb.AppendLine("未找到该类型的绑定");
        }

        Debug.Log(sb.ToString());
    }
}
cs 复制代码
        //参考调用代码
        ZenjectDebugger.PrintAllBindings(Container);
        ZenjectDebugger.PrintAllBindings(ContainerBridge._Container);

6.1

🧩 方法一:容器层次结构是天然的区分线索

Zenject的DiContainer存在父子层次结构,通过ParentContainers属性可以构建容器树。这是框架原生的区分逻辑:子容器会继承父容器的绑定,但可以拥有自己独立的实例。

你可以通过反射获取DiContainer_parentContainers字段(类型为List<DiContainer>),来打印容器的父子关系:

🧵 方法三:利用上下文与绑定信息倒推容器

如果只是为了调试输出,可以不直接获取容器ID,而是观察它内部绑定的特征,来推断容器身份:

  1. 检查核心绑定ProjectContext中通常会绑定全局服务(如IAnalyticsService),而SceneContext中则绑定场景特有服务(如ISceneManager)。通过反射查看容器内特定的绑定类型,可以判断容器的类型和用途。

  2. 检查 InstallersDiContainer内部有一个_installers字段,存放了已执行安装的安装器实例列表。通过反射读取这个列表,可以知道是哪些Installer向该容器进行了注册,从而推断容器场景归属。

错误1

提供了方法一,和三,你是不是觉得很贴心呢;

同问题一提供了,Instantiate和 PrefabFactory 两个方法

|-----------------------|----------------|
| 不行 | 行? |
| 方法一 | 方法三 |
| Container.Instantiate | _prefabFactory |

错误2

一、核心概念先理清

  1. Installer :负责向 DiContainer 注册依赖的脚本(MonoInstaller/ScriptableObjectInstaller
  2. Context :Zenject 的容器载体(ProjectContext/SceneContext/GameObjectContext
  3. Installer 存储位置 :所有 Installer 最终都绑定在对应的 Context 上,而非直接存在 DiContainer 中

说人话,没办法从 diContainer 获取 installer ,但是Context 可以获取 installer

cs 复制代码
        var allInstaller  = ContainerBridge._Container.ResolveAll<Installer>();
        int count=0;
        foreach(var installer in allInstaller)
        {
            Debug.Log($"容器已安装{count} Installer: {installer.GetType().Name}");
            count++;
        }
        Debug.Log("====Zenject 容器数量:" + allInstaller.Count);

你是不是觉得又很贴心,又行了呢

结果获取数量为0,根本没用

总结

Zenject 适合什么项目

话不多说了,只说一句:

要是你的项目是一个新的项目

那么Zenject + NewInput + Addressable + UniTask

所有新的框架,Zenject 框架都有提供相关代码和案例,一些案例是有的,甚至是丰富有余;并且有些代码值得参考和学习和借鉴;

🙋‍♀️参考:

Unity Zenject的一些小Tips(学习备忘)

这哥们还有几个github的开源项目呢

相关推荐
元气少女小圆丶2 小时前
SenseGlove Nova 2+Unity开发笔记4
笔记·unity·游戏引擎
basketball6163 小时前
Go 语言从入门到进阶:6. 一文彻底吃透结构体(Struct)
开发语言·unity·golang
SmalBox5 小时前
【节点】[TriangleWave节点]原理解析与实际应用
unity3d·游戏开发·图形学
cheniie6 小时前
如何优化大景深场景光场渲染背景模糊问题
unity·光场渲染
UWA6 小时前
5 月刊|GPM 2.0 实现全场景可视化溯源、多维度数据解析与根因精准定位
性能优化·游戏开发·uwa
拾忆丶夜15 小时前
unity webgl 阴影条纹问题
unity·游戏引擎·webgl
tealcwu1 天前
【Unity实战】Unity IAP 4.x 在 Windows Store (UWP) 平台上的实现指南
windows·unity·游戏引擎
玉夏1 天前
【Shader基础】CG/HLSL 基础语法
unity·shader
垂葛酒肝汤1 天前
Unity的UGUI的坐标
unity