加载中...
Unity 游戏开发框架设计笔记
发表于:2024-06-02 | 分类: 作品展示

Unity 游戏开发框架设计笔记

一款自己从零搭建的 Unity 开发框架,沉淀了多年项目中的通用能力,覆盖 UI、资源、场景、输入、定时器、红点 等模块,服务端(C#)与 Unity 客户端共用,目标是用一套代码解决团队高频复用的基础问题。


一、基础框架功能(2021.9)

框架的第一版,先把游戏开发里”绕不开的基础设施”搭好。设计原则是每个模块职责单一、可插拔、不相互依赖,方便项目按需取用。

UI 模块

  • 统一的 界面管理(打开/关闭/层级管理);
  • 支持 UI 预加载与缓存,避免频繁实例化;
  • 提供界面之间的事件通信,降低耦合。

缓存池模块

  • 对象池化:游戏物、特效、子弹等高频创建销毁对象统一池化;
  • 减少 Instantiate / Destroy 带来的 GC 与卡顿;
  • 支持 自动回收 与 按需扩容。

资源加载模块

  • 统一的资源加载入口(同步/异步);
  • 支持 引用计数 与 自动卸载,避免资源泄漏;
  • 为 Addressable / AssetBundle 预留扩展接口。

场景加载模块

  • 统一场景切换入口(含异步加载、进度回调);
  • 管理加载中的加载界面与状态回调;
  • 支持场景叠加与切换的扩展。

音乐管理模块

  • BGM / 音效的统一播放与管理;
  • 音量控制、暂停/恢复、音效池;
  • 全局单例,任何模块可调用。

管理基类模块(继承/不继承 Mono)

这是框架最有辨识度的一点——管理类分成两种:

类型 特点 适用
继承 Mono 依赖 MonoBehaviour,走 Update 生命周期 需要每帧逻辑、挂载到场景对象的模块
不继承 Mono 纯 C# 类,不依赖引擎生命周期 服务端逻辑、可脱离引擎运行、易单测

意义:同一套业务逻辑,既能跑在 Unity(继承 Mono),也能跑在纯 C# 环境(不继承),为双端复用打基础。

UI 事件管理器

  • 全局事件发布/订阅;
  • 解耦 UI 与业务(UI 订阅事件,业务发事件,互不引用);
  • 支持带参数与带优先级的事件。

输入管理模块

  • 统一封装键盘、鼠标、触摸输入;
  • 屏蔽平台差异(PC / 移动端);
  • 提供输入事件回调,业务不直接监听底层输入。

二、定时器功能模块(2024.5)

定时器是游戏逻辑的”心脏”——技能冷却、Buff 跳结算、野怪刷新、倒计时都离不开它。本项目定时器模块解决了”既要在 Unity 用、又要在服务器用“的痛点。

继承 Mono / 不继承 Mono 双实现

和框架基调一致,定时器也提供两种形态:

  • 继承 Mono 版:在 Unity 内用,依赖 Update / 协程驱动,方便直接挂载;
  • 不继承 Mono 版:纯 C# 实现,服务器可用,用主循环/Tick 驱动,不依赖引擎。

▎真实代码(两种实现核心逻辑一致,仅”驱动源”不同)

继承 Mono 版(Assets/Scripts/Manager/Timer/AtimerMono.cs)由 Update() 驱动,用 Time.deltaTime 推进:

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
public class AtimerMono : BaseMonoManager<AtimerMono>
{
private List<TimerInfo> _timerInfos = new List<TimerInfo>();
private void Update() { OnUpdateTimer(); OnUpdate?.Invoke(); }

private void OnUpdateTimer()
{
for (int i = 0; i < _timerInfos.Count; i++)
{
var timerInfo = _timerInfos[i];
if (timerInfo.isPuase) continue;
// isFrame=true 按帧计数,否则按时间(秒)推进
timerInfo.current -= timerInfo.isFrame ? 1 : Time.deltaTime;
if (timerInfo.current <= 0)
{
timerInfo.loopCount--;
timerInfo.current = timerInfo.target;
if (timerInfo.loopCount == 0) { RemoveTimer(timerInfo.guid); i--; }
timerInfo.callback?.Invoke();
}
}
}
// 对外 API:Delay(延时) / DelayLoop(循环) / DelayFrame(帧) / NextFrame(下一帧)
public long Delay(float second, Action callback, int loopCount = 1, string flag = null) { ... }
public long NextFrame(Action callback) => AddTimer(1, callback, 1, true).guid;
}

不继承 Mono 版(Assets/Scripts/Manager/Timer/ATimer.cs)用 System.Timers.Timer(1ms) 驱动,可在服务器/纯 C# 环境运行:

1
2
3
4
5
6
7
8
9
10
11
12
public class ATimerMgr : BaseManager<ATimerMgr>
{
private System.Timers.Timer srvTimer;
public ATimerMgr()
{
srvTimer = new System.Timers.Timer(1) { AutoReset = true }; // 1ms 定时驱动
srvTimer.Elapsed += (s, e) => Update(1);
srvTimer.Start();
}
private void Update(int interval) { OnUpdateTimer(interval); OnUpdate?.Invoke(); }
// OnUpdateTimer 逻辑与 Mono 版一致,仅推进基准为 interval(毫秒)
}

同一套 TimerInfo 结构与 Delay/DelayLoop/DelayFrame/NextFrame API,只换驱动源——这正是”服务器和 Unity 都支持、毫秒级、支持每帧”的来源。

服务器与 Unity 都支持

一套定时器 API,双端通用:

1
2
3
4
// 延迟执行
Timer.Wait(delayMs, () => { ... });
// 间隔循环
Timer.Repeat(intervalMs, () => { ... });

服务器端用它做战斗周期任务,Unity 端用它做 UI 倒计时、技能冷却,同一套心智模型。

毫秒级精度

时间基准统一为毫秒,配合 DateTime/Stopwatch 计时,避免浮点误差导致的定时漂移,适合对精度敏感的战斗/服务端逻辑。

支持每帧(每 Tick)回调

除了”延时/间隔”两种模式,还支持每帧/每 Tick 执行:

  • Unity 端:作为轻量级 Update 分发器,替代部分 MonoBehaviour Update;
  • 服务器端:作为主循环的 Tick 分发,统一驱动多个模块的帧逻辑。

好处:模块只需注册回调,无需各自维护 Update/循环,代码更干净、也便于统一测试。


三、红点系统(2024.6)

红点(提示角标)是游戏 UI 的标配,但维护起来很痛——层级多、来源杂、数量要实时。本项目红点系统解决了多层级与动态数量两大痛点。

支持多层级

红点不是”一个开关”,而是树状结构:

1
2
主城 → 邮箱(未读) → 邮件系统
→ 商店(上新) → 商城
  • 每个节点可挂红点状态;
  • 父节点自动聚合子节点的红点(子节点有红点,父节点也亮);
  • 刷新沿树向上传播,一次数据变更自动更新所有相关节点。

▎真实代码(Assets/Scripts/Manager/RedDot/ 精简)

节点树 + 向上传播(RedDotNode.cs):子节点状态变了,UpdateState 会递归刷新父节点:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 更新红点状态:子节点有红点则父节点也亮,数量向上聚合
public void UpdateState(bool state)
{
this._state = state || IsTrueInChildren();
UpdateNumber(); // 重算本节点数量
UpdateRedDot(state, _type, redDotNumber); // 触发显示回调
_parent?.UpdateState(this._state); // 向上传播到父节点
}

// 数量 = 自身 + 所有子节点数量之和
public int GetNumber()
{
var num = _state ? 1 : 0;
if (_childRens != null)
for (int i = 0; i < _childRens.Count; i++) num += _childRens[i].redDotNumber;
return num;
}

无限嵌套 + GameObject 绑定(RedDotSystem.cs):支持任意层级挂节点,绑定 GameObject 后自动显隐、显示数字:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 添加子节点(无限嵌套):parent 下挂 children
public void AddRedDot(RedDotId parentId, RedDotId childrenId,
RedDotType redDotType = RedDotType.Normal, Action<bool> action = null)
{
var childrenNode = GetOrCreateNode(childrenId, redDotType, action);
var parentNode = GetOrCreateNode(parentId);
parentNode.AddChildren(childrenNode);
childrenNode.SetParent(parentNode);
}

// 绑定 GameObject:状态变化自动 SetActive,Number 类型自动刷 TextMeshPro 文案
public void Bind(RedDotId id, GameObject go)
{
if (!_goBindings.TryGetValue(id, out var list)) { list = new HashSet<GameObject>(); _goBindings[id] = list; }
if (!list.Contains(go)) list.Add(go);
go.SetActive(GetState(id));
}

红点类型(RedDotType)区分 Normal(纯红点)/ Number(数量角标)/ Exclamation(叹号),对应不同显示回调——这就是”多层级、绑定 GameObj、数量显示、多类型显示”的实现。

绑定 GameObj

红点节点可绑定到具体 GameObject(图标、按钮),自动挂载角标并随对象生命周期管理,无需手写 Show/Hide。

支持数量显示

除了”亮/灭”,还支持数字角标(如”未读邮件 12 封”):

  • 节点维护一个计数;
  • 达到阈值显示数字,超阈值显示 99+;
  • 计数变化自动刷新角标文案。

支持多类型显示

同一节点可按需呈现不同形态:

  • 纯红点:仅一个圆点;
  • 数字角标:显示数量;
  • 自定义图标 / 动效:可扩展。

实现要点

  • 数据驱动:红点状态由业务数据(计数)计算,而非手动开关;
  • 增量刷新:只有数据变化的节点及其祖先被刷新,避免全量遍历;
  • Todo 记录:如”未打开子节点时红点显示是否正常”这类边界,用清单跟踪。

小结

这套框架覆盖了游戏开发的高频基础设施,核心特色是**”继承 / 不继承 Mono 双实现”**,让同一套 C# 逻辑既能在 Unity 表现、也能在服务器运行,为双端项目省去大量重复代码。

后续方向:编辑器工具链(一键打包、热更新)、网络模块接入、红点系统的可视化调试。

更多作品见:本人作品

上一篇:
战斗编辑器
下一篇:
unity性能优化心得
本文目录
本文目录