news 2026/6/4 17:24:36

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

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Unity 商业插件之(五)课外2 - Zenject的一些小Tips(学习备忘)

Zenject

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

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

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

来自哥们的总结

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

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

这么写 Kernel 会空了

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

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)

//注册 // 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是区隔的,如下对比图:)

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()); } }
//参考调用代码 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

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的开源项目呢

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/4 17:24:26

基于NodeMCU与Adafruit IO的物联网远程定时器项目实战

1. 项目概述与核心价值 如果你对物联网&#xff08;IoT&#xff09;项目感兴趣&#xff0c;想亲手做一个既能看到效果又能学到核心原理的玩意儿&#xff0c;那么这个基于NodeMCU和Adafruit IO的远程定时器绝对是个绝佳的选择。它不像那些简单的点灯项目那么基础&#xff0c;也不…

作者头像 李华
网站建设 2026/6/4 17:24:12

如何从零开始配置黑苹果:国光OpenCore完整教程指南

如何从零开始配置黑苹果&#xff1a;国光OpenCore完整教程指南 【免费下载链接】Hackintosh 国光的黑苹果安装教程&#xff1a;手把手教你配置 OpenCore 项目地址: https://gitcode.com/gh_mirrors/hac/Hackintosh 你是否曾羡慕苹果电脑的流畅体验&#xff0c;但又不想花…

作者头像 李华
网站建设 2026/6/4 17:23:36

K8s综合渗透测试工具,集成信息搜集、权限逃逸、横向移动

0x01 工具介绍 K8sPenTool是一款轻量化开源K8s综合渗透测试GUI工具&#xff0c;基于JavaFX开发&#xff0c;无需复杂命令行操作。工具深度集成信息搜集、集群初始访问、命令执行、权限逃逸、权限维持、内网横向移动等核心能力&#xff0c;覆盖K8s全链路攻击测试场景。适配红蓝…

作者头像 李华
网站建设 2026/6/4 17:22:33

零代码玩转树莓派GPIO:Cayenne平台图形化物联网控制实战

1. 项目概述与核心价值 对于很多刚接触树莓派的朋友来说&#xff0c;最让人既兴奋又头疼的&#xff0c;可能就是那一排排的GPIO引脚了。它们就像树莓派伸向物理世界的“触手”&#xff0c;理论上可以连接传感器、控制电机、点亮LED&#xff0c;实现各种酷炫的硬件交互。但现实…

作者头像 李华