529 lines
20 KiB
Markdown
529 lines
20 KiB
Markdown
# BaseGames Framework — 全面代码评审
|
||
|
||
> 评审时间:2026-05-12
|
||
> 评审范围:`Assets/Scripts/` 全目录
|
||
> 评审标准:成熟商业动作 RPG 框架(Unity 2022.3 LTS / C#)
|
||
> 框架定位:新框架,无需向后兼容,追求纯净、统一、无历史残留
|
||
|
||
---
|
||
|
||
## 目录
|
||
|
||
1. [总体评分](#1-总体评分)
|
||
2. [架构设计](#2-架构设计)
|
||
3. [性能](#3-性能)
|
||
4. [可扩展性](#4-可扩展性)
|
||
5. [编辑器友好性](#5-编辑器友好性)
|
||
6. [使用便利性](#6-使用便利性)
|
||
7. [问题清单(优先级排序)](#7-问题清单优先级排序)
|
||
8. [修复方案](#8-修复方案)
|
||
9. [综合结论](#9-综合结论)
|
||
|
||
---
|
||
|
||
## 1. 总体评分
|
||
|
||
| 维度 | 评分 | 说明 |
|
||
|------------------|----------|-------------------------------|
|
||
| 架构设计 | ★★★★☆ | 结构清晰,少量不一致待修复 |
|
||
| 性能 | ★★★★☆ | 热路径优化良好,若干小 GC 点待处理 |
|
||
| 可扩展性 | ★★★★☆ | SO 驱动设计优秀,接口覆盖可再完善 |
|
||
| 编辑器友好性 | ★★★★★ | 工具链完备,超出同类商业框架水平 |
|
||
| 使用便利性 | ★★★★☆ | 模式统一,极少数订阅方式需对齐 |
|
||
|
||
---
|
||
|
||
## 2. 架构设计
|
||
|
||
### 2.1 整体架构评价 ✅ 优秀
|
||
|
||
框架采用多层解耦架构,核心设计如下:
|
||
|
||
**层次清晰,程序集边界合理**
|
||
|
||
```
|
||
Core.Events ← 最低层(无任何游戏依赖)
|
||
↑
|
||
Core / Core.Save ← 服务层
|
||
↑
|
||
Combat / Player / Enemies / Audio ... ← 游戏系统层
|
||
↑
|
||
UI / World / Equipment ... ← 表现/业务层
|
||
↑
|
||
Editor ← 纯编辑器工具(运行时不可见)
|
||
```
|
||
|
||
每个程序集通过 `.asmdef` 显式声明依赖,`BaseGames.Core.Events` 是纯净基础层,无对任何游戏程序集的引用。程序集结构完全符合 Unity 最佳实践。
|
||
|
||
---
|
||
|
||
### 2.2 事件系统 ✅ 商业级
|
||
|
||
**ScriptableObject 事件频道(EventChannel)** 是框架通信的统一机制:
|
||
|
||
```csharp
|
||
// 订阅(RAII 模式,97% 的文件已全面迁移)
|
||
_channel?.Subscribe(Handler).AddTo(_subs);
|
||
|
||
// 广播(零耦合,跨程序集无问题)
|
||
_channel?.Raise(payload);
|
||
```
|
||
|
||
**`CompositeDisposable` + `EventSubscription`** 实现了 Rx.js/UniRx 风格的生命周期安全订阅,属于行业领先实践:
|
||
|
||
- `EventSubscription` 为 `readonly struct`,零堆分配
|
||
- `CompositeDisposable` 批量管理,OnDisable 一行清空,不可能泄漏
|
||
|
||
**`EventBusMonitor`** 固定大小环形缓冲区(256条),Editor 下零 GC 记录所有事件调用,订阅者计数精确,这在商业框架中都属罕见的优质工具。
|
||
|
||
---
|
||
|
||
### 2.3 服务定位器 ✅ 良好
|
||
|
||
`ServiceLocator` 轻量、类型安全,支持接口类型注册(依赖倒置):
|
||
|
||
- `Register<IInterface>(impl)` — 标准注册
|
||
- `GetOrDefault<T>()` — 安全获取,不抛异常
|
||
- `Unregister<T>(impl)` — 防止场景切换时旧实例残留(安全版重载是亮点)
|
||
- `OverrideForTest<T>` / `Reset()` — Editor 条件编译的测试支持
|
||
|
||
✅ **GameServiceRegistrar** 注册顺序由 `[DefaultExecutionOrder(-2000)]` 保证最早执行,且仅负责统一注册,不做业务逻辑,职责单一。
|
||
|
||
⚠️ **问题 2-A(中)**:`HitStopManager` 迁移至 ServiceLocator 后以具体类型注册,无 `IHitStopManager` 接口,导致 `ClashResolver` 对具体实现类产生依赖,降低可测试性。
|
||
|
||
---
|
||
|
||
### 2.4 存档系统 ✅ 设计优秀
|
||
|
||
**三层存档架构:**
|
||
|
||
```
|
||
SaveManager(协调层)
|
||
↓
|
||
ISaveStorage(接口)→ LocalFileStorage(实现)
|
||
↓
|
||
SaveData(数据层)→ JSON via Newtonsoft.Json
|
||
```
|
||
|
||
亮点:
|
||
- **原子写入**:`.tmp` → `File.Replace` → `.bak`,断电安全
|
||
- **HMAC-SHA256 校验和**:防止存档被篡改,校验失败时仍允许加载(仅警告)
|
||
- **`[JsonExtensionData]`**:未知字段保留,DLC 扩展数据隔离,优雅的前向兼容
|
||
- **异步 I/O + SemaphoreSlim**:并发存档请求串行化,无数据竞争
|
||
- **`CrashReporter`**:异常退出时同步写入崩溃日志 + 触发紧急存档槽(异步不可靠时的正确降级)
|
||
- **`ISaveable`接口 + `SaveableMonoBehaviour`基类**:组件自动注册/注销,消除样板代码
|
||
|
||
⚠️ **问题 2-B(中)**:`SaveManager.LastCheckpointScene` 和 `LastCheckpointSpawnId` 是 `public static` 字段,破坏了框架的实例化服务模型。`DeathRespawnService` 和 `AntiSoftlockSystem` 通过 `SaveManager.LastCheckpoint*` 静态访问绕过了 ServiceLocator。
|
||
|
||
⚠️ **问题 2-C(高)**:`SaveMigrator.CurrentVersion = "1.0"` 与 `SaveMeta.Version = "2.1"` 不一致,每次加载存档都会触发警告,且 `Migrate()` 内无实际迁移逻辑,等同于空实现。
|
||
|
||
---
|
||
|
||
### 2.5 战斗系统 ✅ 架构精良
|
||
|
||
**8 步伤害流水线**(`HurtBox.ReceiveDamage`):
|
||
|
||
```
|
||
① 无敌帧检查
|
||
② 弹反检查(ParrySystem,跨程序集接口隔离)
|
||
③ 霸体检查(IPoiseSource)
|
||
④ 护盾拦截(IShieldable,玩家专属)
|
||
⑤ 防御减免(最低 1 点)
|
||
⑥ TakeDamage(IDamageable)
|
||
⑦ 全局事件广播
|
||
⑧ 状态效果触发(IStatusEffectable)
|
||
```
|
||
|
||
所有步骤通过接口隔离,零直接类型依赖,高度符合开闭原则。
|
||
|
||
**`DamageInfo`** 设计优雅:
|
||
- `struct` 值类型,热路径零堆分配
|
||
- `Builder` 模式支持复杂构造
|
||
- `DamageInfo.From(DamageSourceSO, ...)` 静态工厂方法覆盖 90% 使用场景
|
||
|
||
---
|
||
|
||
### 2.6 玩家状态机 ✅ 结构清晰
|
||
|
||
`PlayerController` 持有状态字典,所有状态继承 `PlayerStateBase`,通过 `TryTransitionState()` 驱动切换。`AttackState` 中连击动画时间点由 `PlayerAnimationConfigSO` 配置,无硬编码,是优质的数据驱动设计。
|
||
|
||
---
|
||
|
||
### 2.7 残留设计不一致(需修复)
|
||
|
||
**⚠️ 问题 2-D(高)**:`GameManager.OnEnable/OnDisable` 仍使用旧式 `OnEventRaised +=/-=` 模式,是框架内唯一遗留的旧式订阅,在所有 MonoBehaviour 已完成 RAII 迁移后显得格外突出。
|
||
|
||
```csharp
|
||
// GameManager.cs(当前——旧格式)
|
||
private void OnEnable()
|
||
{
|
||
if (_onPlayerDied) _onPlayerDied.OnEventRaised += HandlePlayerDied;
|
||
// ...
|
||
}
|
||
|
||
// 应统一为
|
||
private readonly CompositeDisposable _subs = new();
|
||
private void OnEnable() => _onPlayerDied?.Subscribe(HandlePlayerDied).AddTo(_subs);
|
||
private void OnDisable() => _subs.Clear();
|
||
```
|
||
|
||
---
|
||
|
||
## 3. 性能
|
||
|
||
### 3.1 热路径优化 ✅ 优秀
|
||
|
||
| 机制 | 优化方式 |
|
||
|------|---------|
|
||
| `DamageInfo.From()` | 栈分配 struct,零 GC |
|
||
| `EventSubscription` | `readonly struct`,零 GC |
|
||
| `EventBusMonitor` | 固定大小环形缓冲区,Editor 内零 GC |
|
||
| `HitBox` 命中去重 | HashSet/Dictionary 缓存,避免重复伤害逻辑 |
|
||
| `SkillManager.Update` | `FormSkillSO[]` 快照数组无分配遍历 |
|
||
| `GlobalObjectPool` | Addressables 异步预热,Spawn 无实例化开销 |
|
||
| `WorldStateRegistry` | `HashSet<string>` O(1) 查询,ScriptableObject 常驻内存 |
|
||
|
||
### 3.2 需优化点
|
||
|
||
**⚠️ 问题 3-A(低)**:`AudioManager.PlaySFX(string key)` 使用 `foreach` 线性扫描 `_sfxRegistry` 数组,O(n)。当 SFX 条目多时(50+条)每帧高频调用时有感知延迟。
|
||
|
||
```csharp
|
||
// 当前:O(n) 线性扫描
|
||
foreach (var entry in _sfxRegistry)
|
||
if (entry.Key == key) ...
|
||
|
||
// 建议:Awake 时构建 Dictionary<string, AudioEventSO>,O(1) 查询
|
||
private Dictionary<string, AudioEventSO> _sfxLookup;
|
||
private void Awake() {
|
||
_sfxLookup = new Dictionary<string, AudioEventSO>(_sfxRegistry.Length);
|
||
foreach (var e in _sfxRegistry) _sfxLookup[e.Key] = e.Event;
|
||
}
|
||
```
|
||
|
||
**⚠️ 问题 3-B(低)**:`SkillManager.UpdateSkillSet()` 每次切换形态都创建 `new List<FormSkillSO>(3)` 并 `ToArray()`,产生两次 GC 分配。形态切换发生频率低,影响有限,但有更干净的写法:
|
||
|
||
```csharp
|
||
// 建议:固定长度数组,避免 List + ToArray
|
||
private readonly FormSkillSO[] _activeSkills = new FormSkillSO[3];
|
||
private int _activeSkillCount;
|
||
```
|
||
|
||
**⚠️ 问题 3-C(中)**:`HitBox` 中 `_hitThisActivation`(HashSet)和 `_hitCooldownTimers`(Dictionary<Collider2D, float>)在每次 `Activate/Deactivate` 时调用 `Clear()` 而不是预分配容量后复用,多次激活/停用时会引发字典内部数组的 GC。建议在初始化时预设 capacity:
|
||
|
||
```csharp
|
||
private readonly HashSet<Collider2D> _hitThisActivation = new(8);
|
||
private readonly Dictionary<Collider2D, float> _hitCooldownTimers = new(8);
|
||
```
|
||
|
||
**⚠️ 问题 3-D(低)**:`SaveManager._saveables` 使用 `List<ISaveable>`,每次 `Unregister` 是 O(n) 线性搜索。存档对象通常 < 30 个,实际无影响,记录仅作完整性参考。
|
||
|
||
---
|
||
|
||
## 4. 可扩展性
|
||
|
||
### 4.1 ScriptableObject 驱动架构 ✅ 商业顶级
|
||
|
||
整个框架数据层由 SO 驱动,新增功能只需:
|
||
1. 创建新 SO 资产
|
||
2. 在 Inspector 绑定
|
||
3. 无需修改现有代码
|
||
|
||
典型示范:
|
||
- **护符系统**:`ICharmEffect` 接口 + `CharmSO.effects[]` → 新增护符效果只需实现接口并创建资产,完美开闭原则
|
||
- **技能系统**:`FormSkillSO` 数据 + `SkillManager` 执行 → 形态技能由配置决定
|
||
- **Boss 系统**:`BossSkillSO` + `SkillSequenceSO` + `AttackPatternSO` 三层 → 纯数据驱动 Boss 行为设计
|
||
|
||
### 4.2 EventChannel 扩展 ✅ 无限扩展
|
||
|
||
新增事件类型仅需一行:
|
||
|
||
```csharp
|
||
[CreateAssetMenu(menuName = "Events/MyType")]
|
||
public class MyTypeEventChannelSO : BaseEventChannelSO<MyType> { }
|
||
```
|
||
|
||
`VoidBaseEventChannelSO` 和 `BaseEventChannelSO<T>` 两个基类覆盖全部需求。
|
||
|
||
### 4.3 存档扩展 ✅ 支持 DLC
|
||
|
||
`SaveData.DLC = new Dictionary<string, JObject>()` 专用字段 + `[JsonExtensionData]` 未知字段保留,支持 DLC 在不修改主存档结构的前提下扩展数据。`SaveMigrator` 架构(现虽为空)提供了版本升级路径。
|
||
|
||
### 4.4 接口覆盖不完整
|
||
|
||
**⚠️ 问题 4-A(中)**:`HitStopManager` 以具体类注册,无接口抽象。ServiceLocator 使用的服务应尽量对应接口类型:
|
||
|
||
```csharp
|
||
// 建议:定义接口并注册
|
||
public interface IHitStopService {
|
||
void FreezeFrames(int frames);
|
||
void FreezeDuration(float seconds);
|
||
float BaseTimeScale { get; set; }
|
||
}
|
||
ServiceLocator.Register<IHitStopService>(this);
|
||
```
|
||
|
||
**⚠️ 问题 4-B(低)**:`DialogueManager` 直接以具体类注册到 ServiceLocator,而框架中 `IDialogueService` 接口未定义,TutorialManager 也类似。如未来需要替换对话系统,需修改所有调用方。
|
||
|
||
---
|
||
|
||
## 5. 编辑器友好性
|
||
|
||
### 5.1 工具链 ✅ 超出商业标准
|
||
|
||
| 工具 | 功能 |
|
||
|------|------|
|
||
| **EventBusMonitorWindow** | 实时监控所有 SO 事件调用、payload、订阅者数、帧号,过滤搜索,自动滚动 |
|
||
| **SceneScaffoldTools** | 一键生成 Persistent 场景完整 GameObject 层级 + 自动绑定已知资产引用 |
|
||
| **EventChainEditorWindow** | 可视化事件链编辑器 |
|
||
| **BossSkillSequenceWindow** | Boss 技能序列可视化 |
|
||
| **CreateEventChannelAssets** | 批量创建 EventChannel SO 资产 |
|
||
| **AddressReferenceGraphWindow** | Addressables 引用关系图 |
|
||
| **NavSurfaceBakeShortcut** | 快捷 NavSurface Bake |
|
||
| **ScriptExecutionOrderTools** | 执行顺序管理工具 |
|
||
| **ValidationSystem** | `IValidatable` 接口 + 批量校验 |
|
||
| **Editor/Combat / Equipment / World** | 各领域专属编辑器 Inspector 扩展 |
|
||
|
||
`SceneScaffoldTools` 能自动查找资产(通过名称模式匹配)并通过反射自动绑定字段引用,这一功能在独立游戏工具链中属罕见的高完成度实现。
|
||
|
||
### 5.2 运行时调试支持 ✅ 良好
|
||
|
||
- `HurtBox` 有 `OnDrawGizmos()` 可视化受击盒状态(激活/无敌/非激活三种颜色)
|
||
- `HitBox` 中 Awake 对 `IsTrigger` 做运行时验证并日志警告
|
||
- 所有关键 `[DefaultExecutionOrder]` 有文档注释说明原因
|
||
- `PlayerController` 有 `#if UNITY_EDITOR [SerializeField] private bool _debugValidateTransitions`
|
||
|
||
### 5.3 小问题
|
||
|
||
**⚠️ 问题 5-A(低)**:`EventChannelRegistry.Awake()` 自己调用 `DontDestroyOnLoad(transform.root.gameObject)`,但 `GameServiceRegistrar` 已经负责 Persistent 场景 Root GameObject 的生命周期管理。两处 DDOL 可能导致场景层级重复,应由 `GameServiceRegistrar` 统一管理,`EventChannelRegistry` 删除 DDOL 调用。
|
||
|
||
---
|
||
|
||
## 6. 使用便利性
|
||
|
||
### 6.1 统一的服务访问模式 ✅
|
||
|
||
```csharp
|
||
// 全框架统一:ServiceLocator.GetOrDefault<T>()
|
||
var saveManager = ServiceLocator.GetOrDefault<SaveManager>();
|
||
var questManager = ServiceLocator.GetOrDefault<IQuestManager>();
|
||
var audioService = ServiceLocator.GetOrDefault<IAudioService>();
|
||
```
|
||
|
||
无 Singleton.Instance 混用,框架内服务访问路径唯一。
|
||
|
||
### 6.2 统一的事件订阅模式 ✅(95% 完成)
|
||
|
||
```csharp
|
||
// 全框架统一 RAII 模式
|
||
private readonly CompositeDisposable _subs = new();
|
||
private void OnEnable() => _channel?.Subscribe(Handler).AddTo(_subs);
|
||
private void OnDisable() => _subs.Clear();
|
||
```
|
||
|
||
**⚠️ 问题 6-A(高,已识别)**:`GameManager.cs` 是框架内唯一未完成 RAII 迁移的 MonoBehaviour,使用旧式 `OnEventRaised +=/-=`(见问题 2-D)。
|
||
|
||
### 6.3 Input 事件混用
|
||
|
||
框架中存在两套事件订阅机制:
|
||
|
||
1. **EventChannel(SO)**:`_channel?.Subscribe(H).AddTo(_subs)` — 框架标准
|
||
2. **C# 原生 event**:`_inputReader.AttackEvent += Handler` — InputReaderSO 和各 State 使用
|
||
|
||
**这是合理的混用,而非缺陷。** InputReaderSO 的 `event Action` 不需要跨程序集 SO 资产,是 Unity Input System 和状态机配合的常规写法。SkillManager、PlayerController.States 等使用 `event +=/-=` 是正确选择。**无需统一为 EventChannel,保持现状。**
|
||
|
||
### 6.4 `Debug.Assert` 统一用法 ✅
|
||
|
||
关键组件在 Awake 中用 `Debug.Assert` 验证 Inspector 引用,开发期快速发现配置错误,不会在 Release 版本执行:
|
||
|
||
```csharp
|
||
Debug.Assert(_config != null, "[PlayerStats] _config 未赋值,请在 Inspector 中指定 PlayerStatsSO。", this);
|
||
```
|
||
|
||
---
|
||
|
||
## 7. 问题清单(优先级排序)
|
||
|
||
### 🔴 高优先级(影响框架一致性/正确性)
|
||
|
||
| # | 文件 | 问题描述 |
|
||
|---|------|---------|
|
||
| H-1 | `Core/GameManager.cs` | `OnEnable/OnDisable` 仍用旧式 `OnEventRaised +=/-=`,框架内唯一残留,破坏事件订阅统一性 |
|
||
| H-2 | `Core/Save/SaveMigrator.cs` | `CurrentVersion = "1.0"` 与 `SaveMeta.Version = "2.1"` 不一致,每次加载都触发无意义警告 |
|
||
|
||
### 🟡 中优先级(影响架构纯净度)
|
||
|
||
| # | 文件 | 问题描述 |
|
||
|---|------|---------|
|
||
| M-1 | `Core/Save/SaveManager.cs` | `LastCheckpointScene`、`LastCheckpointSpawnId` 为 `public static`,破坏实例化服务模型,应改为实例属性 |
|
||
| M-2 | `Combat/HitStopManager.cs` | 无 `IHitStopService` 接口,直接注册具体类,可测试性受限 |
|
||
| M-3 | `Core/Events/EventChannelRegistry.cs` | `DontDestroyOnLoad` 应由 `GameServiceRegistrar` 统一管理,此处重复 |
|
||
|
||
### 🟢 低优先级(性能/代码质量小改进)
|
||
|
||
| # | 文件 | 问题描述 |
|
||
|---|------|---------|
|
||
| L-1 | `Audio/AudioManager.cs` | `PlaySFX` 线性扫描 `_sfxRegistry`,应在 Awake 构建 `Dictionary` |
|
||
| L-2 | `Player/SkillManager.cs` | `UpdateSkillSet` 每次 `new List + ToArray`,应用固定数组 |
|
||
| L-3 | `Combat/HitBox.cs` | `_hitThisActivation` / `_hitCooldownTimers` 未预设 capacity,多次 Clear 后再 Add 可能触发扩容 |
|
||
| L-4 | `Core/GameIds.cs` | 待确认框架中 GameId 字符串常量是否已统一使用此文件(防止硬编码字符串散落各处) |
|
||
|
||
---
|
||
|
||
## 8. 修复方案
|
||
|
||
### Fix H-1:GameManager 迁移至 RAII
|
||
|
||
```csharp
|
||
// 添加字段
|
||
private readonly CompositeDisposable _subs = new();
|
||
|
||
// 替换 OnEnable
|
||
private void OnEnable()
|
||
{
|
||
_onPlayerDied? .Subscribe(HandlePlayerDied).AddTo(_subs);
|
||
_onPauseRequested? .Subscribe(HandlePauseRequested).AddTo(_subs);
|
||
_onResumeRequested? .Subscribe(HandleResumeRequested).AddTo(_subs);
|
||
_onBossFightStarted? .Subscribe(HandleBossFightStarted).AddTo(_subs);
|
||
_onBossFightEnded? .Subscribe(HandleBossFightEnded).AddTo(_subs);
|
||
_onDeathScreenConfirmed?.Subscribe(HandleDeathScreenConfirmed).AddTo(_subs);
|
||
}
|
||
|
||
// 替换 OnDisable
|
||
private void OnDisable() => _subs.Clear();
|
||
|
||
// 删除 _deathScreenConfirmed bool 字段(DeathRespawnService 已有局部订阅方案)
|
||
```
|
||
|
||
### Fix H-2:SaveMigrator 版本对齐
|
||
|
||
```csharp
|
||
public static class SaveMigrator
|
||
{
|
||
// 与 SaveMeta.Version 对齐
|
||
public const string CurrentVersion = "2.1";
|
||
|
||
public static SaveData Migrate(SaveData data)
|
||
{
|
||
if (data?.Meta == null) return data;
|
||
// 实际迁移分支(示意)
|
||
if (data.Meta.Version == "1.0") MigrateFrom_1_0(data);
|
||
if (data.Meta.Version == "2.0") MigrateFrom_2_0(data);
|
||
data.Meta.Version = CurrentVersion;
|
||
return data;
|
||
}
|
||
|
||
private static void MigrateFrom_1_0(SaveData data) { /* 1.0 → 2.x 迁移逻辑 */ }
|
||
private static void MigrateFrom_2_0(SaveData data) { /* 2.0 → 2.1 迁移逻辑 */ }
|
||
}
|
||
```
|
||
|
||
### Fix M-1:SaveManager 静态字段迁移为实例属性
|
||
|
||
```csharp
|
||
// SaveManager.cs — 删除 static,改为实例属性
|
||
public string LastCheckpointScene { get; private set; }
|
||
public string LastCheckpointSpawnId { get; private set; }
|
||
```
|
||
|
||
```csharp
|
||
// DeathRespawnService.cs — 通过 ServiceLocator 获取
|
||
var sm = ServiceLocator.GetOrDefault<SaveManager>();
|
||
_onSceneLoadRequest?.Raise(new SceneLoadRequest
|
||
{
|
||
SceneName = sm?.LastCheckpointScene,
|
||
EntryTransitionId = sm?.LastCheckpointSpawnId,
|
||
// ...
|
||
});
|
||
```
|
||
|
||
### Fix M-2:HitStopManager 添加接口
|
||
|
||
```csharp
|
||
// 新增接口(放在 Combat 程序集)
|
||
public interface IHitStopService
|
||
{
|
||
void FreezeFrames(int frames);
|
||
void FreezeDuration(float unscaledSeconds);
|
||
float BaseTimeScale { get; set; }
|
||
}
|
||
|
||
// HitStopManager 实现接口
|
||
public class HitStopManager : MonoBehaviour, IHitStopService
|
||
{
|
||
private void Awake()
|
||
{
|
||
if (ServiceLocator.GetOrDefault<IHitStopService>() != null) { Destroy(gameObject); return; }
|
||
ServiceLocator.Register<IHitStopService>(this);
|
||
}
|
||
private void OnDestroy()
|
||
{
|
||
Time.timeScale = _baseTimeScale;
|
||
ServiceLocator.Unregister<IHitStopService>(this);
|
||
}
|
||
}
|
||
|
||
// ClashResolver.cs
|
||
ServiceLocator.GetOrDefault<IHitStopService>()?.FreezeFrames(...);
|
||
```
|
||
|
||
### Fix M-3:EventChannelRegistry 移除 DontDestroyOnLoad
|
||
|
||
```csharp
|
||
// EventChannelRegistry.Awake() — 删除以下行
|
||
// DontDestroyOnLoad(transform.root.gameObject); ← 删除
|
||
```
|
||
|
||
### Fix L-1:AudioManager SFX 查找优化
|
||
|
||
```csharp
|
||
private Dictionary<string, AudioEventSO> _sfxLookup;
|
||
|
||
private void Awake()
|
||
{
|
||
// ... 其他初始化
|
||
_sfxLookup = new Dictionary<string, AudioEventSO>(_sfxRegistry?.Length ?? 0);
|
||
if (_sfxRegistry != null)
|
||
foreach (var entry in _sfxRegistry)
|
||
if (!string.IsNullOrEmpty(entry.Key) && entry.Event != null)
|
||
_sfxLookup[entry.Key] = entry.Event;
|
||
}
|
||
|
||
public void PlaySFX(string key)
|
||
{
|
||
if (!_sfxLookup.TryGetValue(key, out var evt))
|
||
{
|
||
Debug.LogWarning($"[AudioManager] SFX key '{key}' 未注册。");
|
||
return;
|
||
}
|
||
PlayAudioEvent(evt);
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 9. 综合结论
|
||
|
||
### 框架总体水平
|
||
|
||
本框架的架构质量**达到商业独立 AA 游戏标准**,在以下方面有突出表现:
|
||
|
||
1. **事件系统**:SO 频道 + RAII CompositeDisposable 的组合,在 Unity 生态中属顶层实践
|
||
2. **战斗流水线**:`HurtBox` 8 步流水线接口隔离完整,扩展无需修改现有代码
|
||
3. **存档系统**:原子写入 + HMAC 校验 + DLC 扩展字段,工程化程度高于大多数 AAA 之外的游戏
|
||
4. **数据驱动**:SO 驱动护符、技能、Boss、道具,内容迭代不触及代码
|
||
5. **编辑器工具链**:EventBusMonitor + SceneScaffoldTools + 多个专域编辑器窗口,超出同体量框架标准
|
||
|
||
### 待解决的核心问题
|
||
|
||
| 优先级 | 数量 | 说明 |
|
||
|--------|------|------|
|
||
| 🔴 高 | 2 | GameManager 旧式事件订阅;SaveMigrator 版本不一致 |
|
||
| 🟡 中 | 3 | SaveManager 静态字段;HitStopManager 缺接口;EventChannelRegistry 重复 DDOL |
|
||
| 🟢 低 | 4 | AudioManager O(n) 查找;SkillManager GC;HitBox 容量;GameIds 统一性 |
|
||
|
||
解决以上 9 个问题后,框架代码质量可达到**完全无历史残留、统一机制、商业可发布标准**。
|
||
|
||
---
|
||
|
||
*本评审基于源码静态分析,未涵盖运行时 Profiler 数据和平台适配专项测试。*
|