Unity 轻量级依赖注入(DI)与基础服务框架 — 用几个 Attribute 就能完成 Service 注册、依赖注入与生命周期管理,快速搭建解耦、可维护的项目基础设施。
| 包名 | com.sug.essentials(位于 Assets/Essentials) |
| 版本 | 1.0.1 |
| 最低 Unity | 2022.3 |
| 授权 | MIT |
| 作者 | Shiyi Tang (Sugar0612) |
Unity 项目中常见的痛点:
- Manager 类满天飞,管理混乱
- Singleton 单例高耦合,难以替换与测试
- 场景服务的生命周期无人管理
- 模块之间依赖关系不明确
- 一个接口多个实现难以管理
Essentials 用一条简单的规则解决这些问题:
Interface + Attribute + Service Container + Dependency Injection
即:用接口定义能力,用 Attribute 声明注册,由容器统一管理实例与生命周期,并在需要的地方自动注入。
核心代码在
Assets/Essentials(即 UPM 包com.sug.essentials),本仓库其余内容均为示例工程。
- 自动注册:实现类打上
[Service]即自动被扫描注册,无需手动添加到容器。 - 接口驱动:
[Injectable]标记接口,[Inject]注入字段,调用方只依赖接口、不依赖实现。 - 两种生命周期:
Global(全局常驻)/Scene(随场景卸载释放)。 - 多实现支持:同一接口可注册多个实现,用
Id区分。 - 零配置接入:编辑器自动在场景中创建
DIBootstrap,打开即用。 - 运行时注入:动态 Instantiate 出来的对象也会被自动注入。
| 项目 | 要求 |
|---|---|
| Unity | 2022.3 或更高 |
| 平台 | 任意(纯 C# + UnityEngine API,无第三方依赖) |
Window → Package Manager → ➕ → Add package from git URL...
输入:
https://github.com/Sugar0612/Essentials.git?path=Assets/Essentials#1.1.0
打开 Packages/manifest.json,在 dependencies 中添加:
{
"dependencies": {
"com.sug.essentials": "https://github.com/Sugar0612/Essentials.git?path=Assets/Essentials#1.1.0"
}
}
path=Assets/Essentials指向包本体;#1.1.0是版本 tag,去掉则使用最新提交。
- 等待 Unity 完成导入。打开任意场景,编辑器会自动创建一个
[Essentials Bootstrap]对象(内含DIBootstrap),它负责该场景的 Service 注册与依赖注入——无需手动搭建。 - 本仓库自带示例场景,可直接打开查看效果:
Assets/Scenes/SampleScene.unityAssets/Samples/Essentials/0.1.0/Essentials Example/Bootstrap.unity
三步完成一个 Service 从定义到注入。
using SUG.Essentials;
[Injectable]
public interface IConfigService
{
T GetConfig<T>();
}只有标记了
[Injectable]的接口才会进入 Essentials 的 Service 系统。
using SUG.Essentials;
[Service(Lifetime = ServiceLifetime.Global)]
public class ConfigManager : MonoBehaviour, IConfigService
{
public T GetConfig<T>() => default;
}
[Service]实现类必须是场景中的 MonoBehaviour 组件(Essentials 通过扫描场景对象来注册服务)。Lifetime缺省为Global。
using SUG.Essentials;
public class TestController : MonoBehaviour
{
[Inject]
private IConfigService _config;
private void Start()
{
var data = _config.GetConfig<TestData>();
}
}Essentials 会在场景加载时自动完成 IConfigService → ConfigManager 的绑定。[Inject] 字段可以是 private 或 public。
完成。现在 TestController 不依赖 ConfigManager 的具体类型——替换实现、单元测试都更加容易。
同一接口可以注册多个实现,通过 [Service(Id = "...")] 与 [Inject("...")] 配对使用。
[Injectable]
public interface IAudioService
{
void Play();
}[Service(Lifetime = ServiceLifetime.Global, Id = "SFX")]
public class SFXManager : MonoBehaviour, IAudioService { }
[Service(Lifetime = ServiceLifetime.Global, Id = "BGM")]
public class BGMManager : MonoBehaviour, IAudioService { }[Inject("SFX")] private IAudioService _sfx;
[Inject("BGM")] private IAudioService _bgm;Essentials 用 (ServiceType + Id) 作为注册键:
- 不写
Id时默认键为(ServiceType, "default"); - 注入时按同样的键查找,
[Inject]与EssentialsMethod.Resolve的 id 缺省均为"default"。
public enum ServiceLifetime
{
Global, // 全局生命周期
Scene, // 场景局部生命周期
}| 生命周期 | 说明 | 适用场景 |
|---|---|---|
Global |
全局唯一,场景切换不销毁 | AudioManager、ConfigManager、ResourceManager |
Scene |
跟随场景,场景卸载后由容器自动释放 | 场景 UI Manager、当前场景数据、SceneManager |
- Global:把实现类放在常驻对象上(例如入口场景中
DontDestroyOnLoad的对象),实例与注册在场景切换时持续有效。 - Scene:对象出现在场景中即自动注册;场景卸载时(
SceneManager.sceneUnloaded)容器自动清空对应服务。
DIBootstrap 只负责场景中已存在的对象。运行时动态创建的对象,请使用 SUG.Essentials.DI.EssentialsMethod:
using SUG.Essentials.DI;
// 实例化并自动注入(可选传入父节点)
var panel = EssentialsMethod.Instantiate(panelPrefab, transform);
// 手动解析服务(id 缺省为 "default")
var audio = EssentialsMethod.Resolve<IAudioService>("SFX");
audio.Play();
// 手动清空场景服务(例如手动切换场景时)
EssentialsMethod.ClearSceneContainer();一次场景加载的完整流程:
Editor:场景打开/保存时自动创建 [Essentials Bootstrap](DIBootstrap)
↓
DIBootstrap.Awake() (DefaultExecutionOrder = -32000,最先执行)
↓
ServiceScanner.ScanRegister(scene)
├─ 扫描全局对象上的 [Service] → 注册到 Global 容器
└─ 扫描当前场景根对象下的 [Service] → 注册到 Scene 容器
↓
Injector.InjectScene(scene)
└─ 遍历场景中带 [Inject] 字段的组件 → 按 (接口, id) 从容器解析并赋值
找不到对应服务时:字段注入会被跳过(保持原值),手动 Resolve 返回 null——请确保服务已正确注册。
| API | 命名空间 | 作用 |
|---|---|---|
[Injectable] |
SUG.Essentials |
标记接口,使其进入 Service 系统(仅限接口) |
[Service] |
SUG.Essentials |
标记 MonoBehaviour 实现类,声明生命周期与 Id(仅限类) |
[Inject] / [Inject("id")] |
SUG.Essentials |
标记需要自动注入的字段(仅限字段) |
ServiceLifetime |
SUG.Essentials |
Global / Scene 生命周期枚举 |
DIBootstrap |
SUG.Essentials |
场景启动入口组件,由编辑器自动创建 |
EssentialsMethod.Instantiate<T>(prefab[, parent]) |
SUG.Essentials.DI |
实例化对象并自动注入 |
EssentialsMethod.Resolve<T>(id = "default") |
SUG.Essentials.DI |
手动解析服务 |
EssentialsMethod.ClearSceneContainer() |
SUG.Essentials.DI |
清空 Scene 服务容器 |
ServiceKey |
SUG.Essentials |
注册键 (ServiceType, Id) |
提示:在自定义 asmdef 程序集中使用本库时,需要在 Assembly Definition References 中添加
SUG.Essentials.Runtime(该程序集autoReferenced,Assembly-CSharp可直接使用)。
[Injectable]
public interface IUIService
{
void OpenUI<T>();
}
[Service(Lifetime = ServiceLifetime.Global)]
public class UIManager : MonoBehaviour, IUIService
{
public void OpenUI<T>() { /* ... */ }
}
public class LoginPanel : MonoBehaviour
{
[Inject] private IUIService _ui;
private void Start()
{
_ui.OpenUI<SettingPanel>();
}
}[Injectable]
public interface IFileService
{
string ReadText(string path);
}
[Service(Lifetime = ServiceLifetime.Global)]
public class FileManager : MonoBehaviour, IFileService
{
public string ReadText(string path) { /* ... */ }
}
public class ConfigLoader : MonoBehaviour
{
[Inject] private IFileService _file;
private void Start()
{
string json = _file.ReadText("config.json");
}
}Assets/Essentials ← UPM 包本体(com.sug.essentials)
├── Runtime/
│ ├── Attributes/Inject/ [Injectable] [Service] [Inject] 特性
│ ├── DI/ ServiceRegistry / ServiceScanner / Injector / ReflectionCache
│ ├── Bootstrap/DIBootstrap.cs 场景启动入口
│ └── EssentialsMethod.cs 运行时实例化与手动解析 API
├── Editor/
│ └── SceneBootstrapEditor.cs 自动创建 DIBootstrap 的编辑器工具
└── package.json UPM 包配置
Assets/Scenes/SampleScene.unity 工程示例场景
Assets/Samples/Essentials/0.1.0/ 示例场景(Essentials Example)
Essentials 不替代业务框架,只负责三件事:
基础服务管理 + 依赖关系管理 + 生命周期管理
业务逻辑仍由项目自身组织。推荐结构:
Game
├── UI / Gameplay / Config ...
Essentials
├── Service Container
├── Dependency Injection
└── 常用基础服务
- Service 生命周期释放(Dispose 回调)
- Async Service 初始化
- Service Editor 可视化工具
- Dependency Graph
- 更多 Unity 常用基础服务