Unity3D 进阶教程 - 20 完整项目框架搭建

概述

入门系列第 20 篇做了一个 2D 平台游戏的综合实战。这一篇的实战是 搭建可扩展的游戏项目框架——这不是一个具体的游戏,而是一个 架构模板,当你开始新项目时,可以用这个框架作为起点。

框架集成本系列所有进阶知识:依赖注入、事件总线、状态机、对象池、Addressables、Editor 工具、以及性能监控。

1. 框架目录结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
Assets/
Framework/ # 框架核心(不依赖项目逻辑)
DI/ # 依赖注入(VContainer)
Event/ # 事件总线
Pool/ # 对象池
StateMachine/ # 状态机
Input/ # 输入系统封装
Scene/ # 场景管理
Audio/ # 音频系统
UI/ # UI 框架
Editor/ # 编辑器工具

Game/ # 游戏业务逻辑
Managers/ # 管理器
Systems/ # 游戏系统
Entities/ # 游戏实体
Configs/ # ScriptableObject 配置
UI/ # 游戏 UI

Art/ # 美术资源
Audio/ # 音频资源
Prefabs/ # 预制体
Addressables/ # Addressables 分组

2. 框架启动流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public class GameBootstrapper : MonoBehaviour
{
private async UniTaskVoid Start()
{
DontDestroyOnLoad(gameObject);

await InitializeFrameworkAsync();
await LoadCoreAssetsAsync();
await SceneManager.LoadSceneAsync("MainMenu");
}

private async UniTask InitializeFrameworkAsync()
{
FrameworkCore.Initialize(new FrameworkConfig
{
UseVContainer = true,
EnableEventBus = true,
PoolConfig = new PoolConfig { MaxPools = 20 },
EnableProfiler = Debug.isDebugBuild
});

await UniTask.Yield();
}

private async UniTask LoadCoreAssetsAsync()
{
var handle = Addressables.LoadAssetsAsync<GameObject>(
new List<string> { "CoreAssets" }, null,
Addressables.MergeMode.Union);
await handle.ToUniTask();
}
}

3. 框架核心模块

3.1 EventBus

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public static class EventBus
{
private static readonly Dictionary<Type, List<Delegate>> _handlers = new();

public static void Initialize() { }

public static void Subscribe<T>(Action<T> handler) where T : struct
{
var type = typeof(T);
if (!_handlers.ContainsKey(type))
_handlers[type] = new List<Delegate>();
_handlers[type].Add(handler);
}

public static void Unsubscribe<T>(Action<T> handler) where T : struct
{
var type = typeof(T);
if (_handlers.TryGetValue(type, out var handlers))
{
handlers.Remove(handler);
if (handlers.Count == 0) _handlers.Remove(type);
}
}

public static void Publish<T>(T eventData) where T : struct
{
var type = typeof(T);
if (_handlers.TryGetValue(type, out var handlers))
{
foreach (var handler in handlers.ToArray())
(handler as Action<T>)?.Invoke(eventData);
}
}
}

3.2 场景管理器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public static class SceneManager
{
private static readonly Dictionary<string, SceneGroup> _sceneGroups = new();

public static void RegisterSceneGroup(string name, string[] scenes)
{
_sceneGroups[name] = new SceneGroup { scenes = scenes };
}

public static async UniTask LoadSceneGroupAsync(string groupName)
{
if (!_sceneGroups.TryGetValue(groupName, out var group)) return;

foreach (var scene in group.scenes)
{
await UnityEngine.SceneManagement.SceneManager
.LoadSceneAsync(scene, LoadSceneMode.Additive).ToUniTask();
}
}

private struct SceneGroup
{
public string[] scenes;
}
}

3.3 UI 框架

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
public abstract class BaseUI : MonoBehaviour
{
protected CanvasGroup CanvasGroup { get; private set; }
private CancellationTokenSource _cts;

protected virtual void Awake()
{
CanvasGroup = GetComponent<CanvasGroup>();
if (CanvasGroup == null)
CanvasGroup = gameObject.AddComponent<CanvasGroup>();
}

public async UniTask ShowAsync(float duration = 0.3f)
{
_cts?.Cancel();
_cts = new CancellationTokenSource();

gameObject.SetActive(true);
await FadeAlphaAsync(0f, 1f, duration, _cts.Token);
CanvasGroup.interactable = true;
CanvasGroup.blocksRaycasts = true;
}

public async UniTask HideAsync(float duration = 0.2f)
{
_cts?.Cancel();
_cts = new CancellationTokenSource();

CanvasGroup.interactable = false;
await FadeAlphaAsync(1f, 0f, duration, _cts.Token);
gameObject.SetActive(false);
}

private async UniTask FadeAlphaAsync(float from, float to, float duration, CancellationToken token)
{
var elapsed = 0f;
while (elapsed < duration)
{
elapsed += Time.deltaTime;
CanvasGroup.alpha = Mathf.Lerp(from, to, elapsed / duration);
await UniTask.Yield(token);
}
CanvasGroup.alpha = to;
}
}

4. 运行时 Profiler

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class RuntimeProfilerDisplay : MonoBehaviour
{
private float _fps;

private void Update()
{
_fps = 1f / Time.unscaledDeltaTime;
}

private void OnGUI()
{
var style = new GUIStyle { fontSize = 14 };
style.normal.textColor = _fps >= 50 ? Color.green : (_fps >= 30 ? Color.yellow : Color.red);

GUILayout.BeginArea(new Rect(10, 10, 200, 40));
GUILayout.Label("FPS: " + _fps.ToString("F1"), style);
GUILayout.EndArea();
}
}

5. 框架使用流程

1
2
3
4
5
6
7
8
1. 复制 Framework/ 目录到新项目
2. 安装依赖(VContainer、UniTask、Addressables)
3. 创建 GameBootstrapper 场景
4. 按项目需求实现 GameEntryPoint
5. 用 Scene Groups 管理关卡
6. 用 EventBus 实现模块间通信
7. 用 PoolManager + Addressables 管理对象
8. 用 BaseUI 继承实现所有 UI 面板

结语

至此,Unity3D 进阶教程的 20 篇全部完成。

架构模式 -> 依赖注入 -> 状态机 -> UniTask -> 编辑器工具 -> Addressables -> URP -> Shader -> 后处理 -> 渲染优化 -> Profiler -> 内存 -> 代码优化 -> ECS -> Job System -> Netcode -> 多人架构 -> Input System -> 高级物理 -> 项目框架,覆盖了 Unity 开发的各个深度维度。

进阶之路不仅是”会用更多功能”,更是 理解原理、选择合适方案、构建可维护的架构。继续实践,继续探索。

ByteFisher
分享编程技术 · 记录钓鱼乐趣
扫码关注
▸ 扫码关注 ◂
Unity3D进阶教程 系列
第 20/20 篇
分享: