# IOC 控制反转
IOC 提供一个对象生成容器,在我们需要取得某个对象时,不再使用
New
关键字进行对象生成操作,而是通过 IOC 容器内部控制来获得对象。
使用这种思想方式,可以让我们无需关心对象的生成方式,只需要告诉容器我需要 xxx 对象即可,而高旭容器我需要 xxx 对象的方式就叫做 DI 依赖注入。
# DI 依赖注入
这种思想,我是在学习.net core 时发现的,一般采用 特性注入
或者使用 构造函数注入
。
经过标记的字段会在该类构造时在 IOC 容器中执行构造获取对应的对象示例。
# VContainer
VContainer 是在 Unity 端使用的一种轻量级的 DI 框架,通过 GameLifeScope 划分作用域,和普通的 web 后端的 Di 框架不同的是,针对 Unity 的 Mono 特殊性,提出了 GameLifeScope 的方式,通过在 Game Object 上挂载 Scope,可以在代码中动态切换使用的实例和 Scope。并且提供了一种父子继承 Scope 方式供用户使用。
# Scope 作用域
作用域指函数、变量的作用范围,在 Di 中一般可以注册 singleton 单例,Trans 复例等
在 netcore 中有类似下面这样的形式:
var serviceCollection = new ServiceCollection() | |
.AddTransient<ILoginService, EFLoginService>() | |
.AddSingleton<ILoginService, EFLoginService>() | |
.AddScoped<ILoginService, EFLoginService>(); |
其中添加的 Addxxxx
就是为其添加一项注入并指定注入作用域,同时在这里进行的是一个接口的注入操作,在使用这个接口依赖时会生成一个对应的接口实现实例。
在 VContainer 中的注入和以上的方式很像。
# 单例?
一般来说,在 Unity 中我们会继承自一个泛型单例类。
/// <summary> | |
/// 基础单例(线程锁) | |
/// </summary> | |
/// <typeparam name="T"></typeparam> | |
public class Singleton<T> where T : new() | |
{ | |
private static T _instance; | |
public static T Instance | |
{ | |
get | |
{ | |
if (_instance != null) return _instance; | |
try | |
{ | |
_instance = new T(); | |
Debug.Log($"{typeof(T)}".Color(new Color(0.2f,0.6f,0.8f)) + "|单例OK"); | |
Monitor.Enter(_instance); | |
} | |
finally | |
{ | |
Monitor.Exit(_instance); | |
} | |
return _instance; | |
} | |
} | |
} |
例如继承自这个泛型单例来实现单例效果,在 Di 中我们的实例无需在外部控制单例,直接由 IOC 容器管理。
# GameLifeScope
为其 GameObject 添加一个继承自
LifetimeScope
的脚本即添加了一个 Scope。++ 游戏中至少需要有一个 Scope ++
public class BaseLifeScope : LifetimeScope | |
{ | |
protected override void Configure(IContainerBuilder builder) | |
{ | |
builder.Register<MusicManager>(Lifetime.Singleton); | |
builder.RegisterInstance(GameSettingAssetMgr.Instance); | |
builder.Register<UIManager>(Lifetime.Singleton); | |
builder.RegisterEntryPoint<GameLauncher>(); | |
builder.RegisterEntryPoint<GameLoop>(); | |
} | |
} |
同时我们可以继承父级 LifeScope 来灵活选择我们的 Scope 实现。
public class GameLiftScope : BaseLifeScope | |
{ | |
public Character gamePanel; | |
protected override void Configure(IContainerBuilder builder) | |
{ | |
base.Configure(builder); | |
builder.RegisterComponentInNewPrefab(gamePanel, Lifetime.Scoped); | |
} | |
} |
# 可注入对象
在上面我们描述了 Scope,使用
builder.Register<T>
进行注册。
# Mono
可注入 Mono 的对象,例如游戏中的 UI 等与 Mono 关系密切的对象。
可将场景中的 GameObject 直接注入到容器中来使用。
# Instance
可注入具体的对象实例,例如单例我们可以直接注入对象的单例,也可以在 Scope 中选择单例。
# EnterPoint
EnterPoint
可以说是VContainer
设计十分精妙的部分 —— 将 Mono 转移到普通的 C# 类中。
通过 builder.RegisterEntryPoint<T>();
注入一个入口点。这些入口点能够代替 Unity 的 Start、Update、FixedUpdate 等函数。
通过实现不同的 > Ixxx 接口重写方法来编写原生 Unity 事件,如下例子:
这是一个类似 Start 的实现,继承自
IStartable
即可,同时这里我注入了GameSettingAssetMgr
和MusicManager
两个对象。在游戏开始运行时,执行了打印当前游戏语言和播放 Bgm 的指令。
public class GameLauncher : IStartable | |
{ | |
[Inject] private readonly GameSettingAssetMgr _gameSettingAssetMgr; | |
[Inject] private readonly MusicManager _musicManager; | |
public GameLauncher(MusicManager musicManager, GameSettingAssetMgr gameSettingAssetMgr) | |
{ | |
_musicManager = musicManager; | |
_gameSettingAssetMgr = gameSettingAssetMgr; | |
} | |
public void Start() | |
{ | |
Debug.Log(_gameSettingAssetMgr.lan); | |
_musicManager.PlayBgm("12345SEX"); | |
} | |
} |
可以看到,在我运行游戏时,根据依赖自动生成了对象并执行了相关操作。
同理我们可以,实现更多的接口来实现更多的 Unity 原生事件,将原本 Mono 脚本才能执行的内容转移到普通 Class 中。
现在让我们为 GameLauncher 类实现 Update 实现。
实现ITickable接口 | |
public void Tick() | |
{ | |
if (Input.GetKeyDown(KeyCode.Q)) | |
{ | |
_uiManager.ShowPanel("GamePanel", UILayer.Mid); | |
Debug.Log("显示面板"); | |
} | |
if (Input.GetKeyDown(KeyCode.W)) | |
{ | |
_uiManager.SwitchShowState("GamePanel"); | |
Debug.Log("切换面板显示"); | |
} | |
} |
# 在 Mono 中使用
因为 Mono 不支持构造函数,所以在 Mono 中使用时必须使用注入特性或注入方法的方式。
builder.RegisterComponentInHierarchy<Character>(); |
# 注入特性
[Inject] private MusicManager _musicManager; | |
[Inject] private GameSettingAssetMgr _gameSettingAssetMgr; |
# 注入方法
private MusicManager _musicManager; | |
private GameSettingAssetMgr _gameSettingAssetMgr; | |
[Inject] | |
public void Inject(GameSettingAssetMgr gameSettingAssetMgr, MusicManager musicManager) | |
{ | |
_gameSettingAssetMgr = gameSettingAssetMgr; | |
_musicManager = musicManager; | |
} |
# 相关链接
- 关于 | 虚拟容器 (hadashikick.jp)
- hadashiA/VContainer: The extra fast, minimum code size, GC-free DI (Dependency Injection) library running on Unity Game Engine. (github.com)