# BaseGames Framework — 第六轮完整评审 **日期**: 2026 年 5 月 **评审轮次**: v6(接续 v5 修复后全量复审) **版本基线**: Unity 2022.3 LTS / C# 9 / 28+ .asmdef 程序集 **上轮评分**: v5 加权 8.73 / 10 --- ## 一、评审说明 本轮对 `Assets/Scripts` 全部模块代码进行系统性阅读,覆盖范围: | 模块 | 主要文件 | |------|---------| | Core | GameManager, GameStateMachine, SceneLoader, DeathRespawnService, ServiceLocator, AssetLoader, AddressKeyRegistry, DifficultyManager, SaveManager, WorldStateRegistry, EventChannelRegistry | | Core.Events | BaseEventChannelSO, CompositeDisposable, EventBusMonitor, IValidatable | | Player | PlayerController, PlayerMovement, ParrySystem, SpellManager | | Equipment | EquipmentManager, StatModifierEffect | | Combat | HitBox, ClashResolver, StatusEffectManager | | EventChain | EventChainManager, EventChainSO | | Quest | QuestManager | | Progression | AchievementManager | | World | WorldStateRegistry, MapManager, ShopController, LiquidZone, PuzzleInterfaces | | VFX | PaletteSwapSystem | | Tutorial | TutorialManager | | Support | DebugCheatSystem, SpeedrunTimer, SteamPlatformService | | Editor | EventBusMonitorWindow, BossSkillSequenceWindow, SOValidationRunner, AddressReferenceGraphWindow | 评分标准:以成熟商业 2D Action RPG 框架水准(类 Hollow Knight / Celeste 量级代码质量)为基准,不要求向下兼容兜底,强调框架纯净性与数据逻辑一致性。 --- ## 二、各维度评审 ### 2.1 架构设计 Architecture Design — 9.1 / 10 #### 亮点 **事件驱动解耦彻底** `BaseEventChannelSO` SO 频道模式落实全面,无任何 `SendMessage` / `BroadcastMessage` / `FindWithTag`(已在 v5 修复 `BD_MoveToPlayer` 最后一处)。所有跨系统通信均通过频道 SO 或 `ServiceLocator` 接口,程序集依赖始终单向:`Core.Events → Core.Save → Core → 业务模块`。 **纯 C# 游戏状态机** `GameStateMachine` 不继承 `MonoBehaviour`,`ValidNextStates` 守卫确保状态转移合法,`Tick(float dt)` 由 `GameManager.Update` 托管,生命周期职责清晰分离。 **ServiceLocator 接口契约统一** 所有服务注册均面向接口(`Register(this)`),实现与接口在同一程序集。`OverrideForTest / Reset` 仅编译进 Editor,生产代码零开销。 **EquipmentContext struct 捆绑模式** 护符效果(`ICharmEffect.OnEquip/OnUnequip`)通过 `EquipmentContext` 接收全部依赖,而非硬编码访问 `PlayerController` 单例,依赖倒置彻底,测试友好。 **StatusEffectManager 开放/封闭设计** `RegisterEffectFactory(DamageType, Func)` 允许外部(Boss 模块、道具模块)在运行时注入新效果类型,无需修改基类——符合 OCP。双结构(`List` + `Dictionary`)在顺序遍历与 O(1) 类型查找间取得平衡。 **ClashResolver 去重方案无碰撞风险** 用 `(Math.Min(a,b), Math.Max(a,b))` 元组而非 XOR 哈希,逐帧 `HashSet.Clear()`,同帧双方各触发的回调只处理一次,设计细节严谨。 **EventChannelRegistry 动态查找兜底** 动态 Prefab(CharmEffect SO 等无法持有 `[SerializeField]` 的对象)可通过 `IEventChannelRegistry.Get(key)` 按名称查找频道,避免在非 MonoBehaviour 中出现 `FindObjectOfType` 调用。 #### 不足 **A-1(中)`GameManager` 暂停恢复无子状态记忆** `Paused` 状态退出时固定调用 `RequestTransition(GameStates.Gameplay)`,若暂停发生在 `BossFight` 状态内,恢复后将错误进入 `Gameplay`。Boss BGM、HUD 显示逻辑将对应失效。 ```csharp // GameManager.cs — 当前实现(问题) case GameStates.Paused: RequestTransition(GameStates.Gameplay); // ← 不应硬编码 break; ``` 建议:`GameManager` 增加 `_prePrePauseState` 字段,进入 `Paused` 时记录,退出时恢复。 **A-2(低)`DeathRespawnService.StartGameOverCoroutine` 用硬抛出版本** ```csharp // 其余代码均使用 GetOrDefault ServiceLocator.Get() // ← GameOver 路径唯一用硬抛出的地方 ``` 若 `ISceneService` 在异常流程中已注销,会产生难以定位的 `KeyNotFoundException`。建议改为 `GetOrDefault()?.LoadMainMenuCoroutine()`,一致性与容错性均更好。 --- ### 2.2 性能 Performance — 8.6 / 10 #### 亮点 **PaletteSwapSystem — MaterialPropertyBlock** `ApplyPalette()` 通过 `MaterialPropertyBlock` 修改纹理属性,不触碰共享材质,零 GC,零实例化材质。形态切换热路径完全无分配。 **SpeedrunTimer — 按整秒更新** `_lastDisplayedSecond` 缓存避免每帧字符串 `$"{hours:00}:..."` 重建,高精度 UI 只在整秒边界才分配一次字符串,设计细腻。 **EventBusMonitor — `#if UNITY_EDITOR` 零运行时开销** `Record()` 调用与 `_subscriberCount` 字段均在 Editor 条件内,生产构建下 `BaseEventChannelSO.Raise()` 完全不产生额外开销。 **PaletteCatalogSO — 懒初始化 + OnValidate 清理** `_cache = new Dictionary...` 在首次 `TryGetPalette` 时懒建,`OnValidate()` 置 null 触发重建——编辑器友好且运行时只初始化一次。 **ClashResolver — 逐帧清理** `LateUpdate` 清除 `_processedThisFrame`,每帧内去重 HashSet 大小上限为场景中同帧碰撞数量(通常 < 10),内存占用可控。 #### 不足 **P-1(中)`PlayerMovement.CheckGrounded` — 分配版 OverlapBox** ```csharp Physics2D.OverlapBox(pos, size, angle, mask) // 返回 Collider2D,内部分配 ``` 地面检测每帧执行,应改用 `OverlapBoxNonAlloc` 并预分配结果缓冲: ```csharp private readonly Collider2D[] _groundBuffer = new Collider2D[4]; // 调用改为: int count = Physics2D.OverlapBoxNonAlloc(pos, size, angle, _groundBuffer, mask); ``` **P-2(中)`ShopController.GetAvailableItems()` — LINQ + 每次新建 List** ```csharp return _inventory.DefaultInventory .Take(_inventory.MaxDisplaySlots) .Where(...) .ToList(); // 每次 UI 刷新时分配新 List ``` UI 打开后若频繁刷新(如动画帧回调)会持续分配。建议缓存结果,仅在 `_purchaseCounts` / `_soldUniqueItems` 变更时重建(`_isDirty` 标记模式)。 **P-3(低)`MapManager.OnSave()` — 每次存档分配 List** ```csharp data.Map.ExploredRooms = _exploredRooms.ToList(); // 新 List 分配 data.Map.MappedRooms = _mappedRooms.ToList(); ``` 存档非高频操作,可接受,但若改为直接清空再填充已有 `List` 字段更优: ```csharp data.Map.ExploredRooms.Clear(); data.Map.ExploredRooms.AddRange(_exploredRooms); ``` **P-4(低)`SaveManager._saveables.Remove(s)` — O(n) 列表移除** `_saveables` 是 `List`,`Unregister` 时 `Remove` 为 O(n) 线性扫描。大型场景若存档对象超过 100 个,频繁进出房间会有轻微开销。可改为 `HashSet` + 顺序快照: ```csharp // 保存时: foreach (var s in _saveables.ToList()) s.OnSave(_current); // 注销时 O(1): _saveables.Remove(s); // HashSet.Remove = O(1) amortized ``` --- ### 2.3 可扩展性 Scalability — 9.0 / 10 #### 亮点 **EventChain 条件系统纯 SO 扩展** `ChainCondition` 抽象基类 + `CreateAssetMenu` 派生类,新增触发条件只需创建新 SO 类型,零运行时代码修改,Designer 友好。 **WorldStateRegistry — `WorldObjectCategory` 枚举扩展入口** 增加新世界对象类别只需新增枚举值与对应语义化 API 方法,底层 `Dictionary>` 自动扩展。 **AchievementManager — 数据驱动条件注册** 通过 `_achievements: AchievementSO[]` 数组驱动所有成就,运行时 `_states` 字典 O(1) 查找,新增成就无需修改 `AchievementManager` 代码。 **StatusEffectManager.RegisterEffectFactory — 运行时注入** Boss 技能、特殊道具可在 `Awake` 后调用 `RegisterEffectFactory` 覆盖或扩展效果类型,不需要修改核心 Combat 程序集。 **PlatformService — 编译守卫隔离** `SteamPlatformService` 全文在 `#if UNITY_STANDALONE && STEAMWORKS_NET` 内,切换/增加平台(如 Epic、Console)只需实现新 `IPlatformService`,无需修改业务代码。 #### 不足 **S-1(低)`SpellManager` 单槽注释未兑现** 注释中提到"如需多槽可扩展为数组",但当前 `_equippedSpell: SpellSO` 为单字段,`TryCastSpell()` 也仅处理一个。若游戏后期引入双法术槽,改动面较广(Player UI、InputReader 需同步扩展)。建议尽早将槽数提取为 `[SerializeField] private int _slotCount = 1`,内部用 `SpellSO[]` 并按索引管理,API 改动最小。 **S-2(低)`UIManager` Panel Stack 无 Z 层优先级管理** `_panelStack: Stack` 假设面板遵循严格 LIFO 顺序,若并发触发(如成就解锁弹窗 + 商店同时打开)会导致遮盖关系混乱。商业游戏通常需要优先级/层级系统(如 Overlay / Modal / Notification 分层)。 --- ### 2.4 编辑器友好 Editor-Friendliness — 9.3 / 10 #### 亮点 **EventBusMonitorWindow — 生产级调试面板** Filter / Pause / AutoScroll / Clear 工具栏组合;逐帧渲染监听数为 0 的频道红色高亮(事件死区检测);列宽固定排版整洁。快捷键 `Ctrl+Shift+E` 即开即用。 **BossSkillSequenceWindow — 甘特图可视化** Windup / Active / Recovery 三段时序条颜色区分;`VulnerabilityWindow` 绿色覆盖层;`DurationNormalized < 0.1` 变红警告;点击阶段条 `PingObject` 跳转资产——与商业工具集的差距已接近可忽略。 **AddressReferenceGraphWindow — 孤儿 Key 检测** 扫描全部 `.cs` 文件引用 `AddressKeys.X` 的模式,可视化孤儿 Key(无引用/无对应 Addressables)+ CSV 导出,有效防止地址字符串腐烂,属框架级质量保障。 **SOValidationRunner — 构建时数据守卫** `IPreprocessBuildWithReport`(callbackOrder = 1)在每次构建前扫描所有 `IValidatable` SO,错误时 `throw BuildFailedException` 中止构建,将数据错误拦截在出包前。 **PaletteCatalogSO.OnValidate** `_cache = null` 确保 Inspector 中修改调色板条目后立即生效,避免运行时用旧缓存,设计自洽。 **WorldStateRegistry.OnEnable** 每次 Enter Play Mode(或 Domain Reload)时 `_states.Clear()`,ScriptableObject 跨 PlayMode 状态残留问题彻底解决,无需用户手动重置。 #### 不足 **E-1(低)`BossSkillSequenceWindow` 无保存/分享功能** 甘特图无法导出图片或 JSON 快照,多人协作时只能截屏分享,建议增加「导出 PNG」或「复制时序数据」按钮(低优先级)。 **E-2(低)`DebugCheatSystem` 指令集无自动补全** 控制台输入框为纯文本,Tab 补全、历史记录均未实现,开发效率受限。建议存 `List _history` 并用 `↑↓` 键翻历史(约 30 行改动)。 --- ### 2.5 使用便利性 Developer UX — 8.9 / 10 #### 亮点 **RAII 订阅模式统一** 全框架均使用 `_channel?.Subscribe(Handler).AddTo(_subs)` + `OnDisable → _subs.Clear()`,订阅/注销代码模式零歧义,阅读一个文件即可掌握全框架约定。 **TutorialManager — 去重幂等设计** `ShowHint(hintId, text, duration)` 内部已检查 `_completedHints.Contains`,调用方无需关心重复触发,API 表面积最小。 **`TryPurchase` 返回 bool + `TryEquipCharm` 返回 string?** `ShopController.TryPurchase` 返回 bool(调用方只需知道成功/失败),`EquipmentManager.TryEquipCharm` 返回 `string?`(null = 成功,字符串 = 失败原因)——两种场景选择恰当的返回类型,API 表达力强。 **`GameStateId` struct 值语义** `if (state == GameStates.Gameplay)` 直接比较,无装箱,无枚举强转,阅读直观。 **`EquipmentContext` 一次传递所有依赖** 护符效果实现者接收单一 `ctx` 参数即可访问 Stats / Feedback / EventChannels / Skills,无需各自持有引用,代码简洁。 #### 不足 **U-1(中)`AchievementManager.Awake` 缺少重复注册防护** ```csharp private void Awake() { ServiceLocator.Register(this); // ← 无 GetOrDefault 防护 ... } ``` 其余所有 Manager 均有 `GetOrDefault != null → Destroy` 模式(如 `TutorialManager`、`QuestManager`、`DialogueManager`),`AchievementManager` 是唯一例外,破坏一致性,多场景叠加时会抛 `InvalidOperationException`。 **U-2(低)`SpeedrunTimer` 注释描述与行为不符** ```csharp /// 使用 Time.unscaledDeltaTime 在游戏暂停时停止(不受 timeScale 影响)。 ``` 注释存在误导:`unscaledDeltaTime` **不受** `timeScale` 影响,意味着 `Time.timeScale = 0` 时定时器**不会**自动停止。实际依靠外部调用 `PauseTimer()` 控制 `_paused` 布尔值。注释应改为: > 使用 `Time.unscaledDeltaTime` 以免被 HitStop(`timeScale < 1`)拉慢;游戏暂停时须由外部调用 `PauseTimer()`。 --- ### 2.6 框架纯净度 Framework Purity — 9.3 / 10 #### 亮点 **编译守卫严格分层** - `DebugCheatSystem`:`#if UNITY_EDITOR || DEVELOPMENT_BUILD` 全文保护 - `BD_MoveToPlayer` 等全部 BehaviorDesigner 任务:`#if GRAPH_DESIGNER` - `SteamPlatformService`:`#if UNITY_STANDALONE && STEAMWORKS_NET` - `EventBusMonitor.Record()`:`#if UNITY_EDITOR` 内联 生产构建完全无调试/第三方残留,剥离彻底。 **无全局 MonoBehaviour 单例** 所有服务通过 `ServiceLocator` 按接口访问,无任何 `public static Instance` 或 `DontDestroyOnLoad + FindObjectOfType` 模式(`SaveManager` 私有 `static _instance` 仅用于内部防重复,不对外暴露)。 **SO 事件频道零跨场景污染** `BaseEventChannelSO.OnEventRaised` 是 C# event(堆对象),频道 SO 资产本身不保存运行时状态;`WorldStateRegistry.OnEnable` 清理 SO 跨 PlayMode 状态——SO 资产永不携带运行时脏数据。 **`ICharmEffect` 纯数据驱动** 护符效果均为 `[Serializable]` POCO 类实现 `ICharmEffect`,不继承 `MonoBehaviour` / `ScriptableObject`,装配时零 Unity 序列化开销。 #### 不足 **PU-1(低)`DebugCheatSystem.CmdHeal` 用 FindFirstObjectByType** ```csharp var player = FindFirstObjectByType(); // dev-only 但破坏 ServiceLocator 一致性 ``` 虽有 `#if` 保护,框架层面 `PlayerController` 应通过 `_onPlayerSpawned` 事件频道或 `ServiceLocator.GetOrDefault()` 访问。`FindFirstObjectByType` 是框架约定的例外,可酌情整改。 --- ### 2.7 数据逻辑一致性 Data Consistency — 9.1 / 10 #### 亮点 **WorldStateRegistry — 单一数据源** 全部世界状态(Collectible / SavePoint / Door / Destroyed / Flag)统一存储在单 `Dictionary>` 中,`OnStateChanged` 事件提供响应式接口;`LoadFromSave / GetAllFlags` 完整对称,持久化路径无歧义。 **SaveManager — 序列化完整性** 双 Pass 序列化(先置 null 再计算 Checksum,再序列化含 Checksum 版本)确保存档文件自校验;`SemaphoreSlim(1,1)` 防止并发写入;`_current.Meta.SaveCount++` 追踪覆写次数,利于调试。 **EventChain — SO 运行时状态清理** `ChainCondition.ResetState()` 在每次 `OnEnable` 时调用,防止 SO 资产在编辑器反复进入 Play Mode 时携带旧条件状态,数据干净性有保障。 **EquipmentManager — `_usedNotches` 缓存同步** 护符格子数通过 `_usedNotches` 字段缓存(而非每次 LINQ Sum),装备/卸下时同步更新,查询 O(1) 且与真实状态始终一致。 **QuestManager — 存档双向完整** `_questStates + _objectiveStates` 完整写入 / 恢复,`OnLoad` 时构建索引,存取路径对称,无悬空引用。 #### 不足 **DC-1(低)`SceneLoader._currentRoomScene` 加载失败时不清空** ```csharp // SceneLoader.cs — LoadRoomAsync 失败路径未清理 catch (Exception e) { Debug.LogError(...); // ← _currentRoomScene 仍保持旧值 } ``` 若加载失败后再次加载同一场景,`_currentHandle` 卸载逻辑可能基于旧的错误场景名,导致内存泄漏。建议在 catch 块中明确 `_currentRoomScene = null; _currentHandle = default;`。 --- ### 2.8 可测试性 Testability — 7.9 / 10 #### 亮点 **ServiceLocator.OverrideForTest / Reset** 仅 Editor 编译的测试 API,可在 Unity Test Runner 的 `[SetUp] / [TearDown]` 中替换任意服务,无需修改生产代码。 **`IValidatable` + SOValidationRunner** SO 数据合法性测试通过 `Validate()` 方法内嵌,单元测试可直接实例化 SO 并调用 `Validate()`,无需场景。 **`EventSubscription` readonly struct** 轻量无装箱,测试代码中可用 `using var sub = channel.Subscribe(...)` 保证解订。 **接口隔离彻底** `ISceneService / IDialogueService / IQuestManager / IAchievementService / IDifficultyService` 等均可轻松 Mock,不依赖具体 MonoBehaviour 实现。 #### 不足 **T-1(中)`GameManager` 死亡流程用协程 + bool 标志** `DeathFlow` 协程通过 `_waitingForDeathScreenInput` bool 等待玩家输入,无法用 Unity Test Runner 的同步 API 覆盖,需要 PlayMode 测试,测试成本高。建议将死亡确认逻辑抽成 `UniTask` / 事件驱动异步方法,便于在测试中直接 `Raise(confirmChannel)`。 **T-2(低)`StatusEffectManager.Awake` 内联工厂注册难覆盖** ```csharp RegisterEffectFactory(DamageType.Fire, () => new FireEffect()); RegisterEffectFactory(DamageType.Poison, () => new PoisonEffect()); ``` 测试中若需替换 `FireEffect` 为 Mock,需在 `Awake` 后立即调用 `RegisterEffectFactory` 覆盖,初始化顺序敏感,单测需要注意 `[SetUp]` 时序。 --- ## 三、综合问题清单 ### 需修复(Bugs / 一致性破坏) | ID | 严重度 | 模块 | 描述 | |----|--------|------|------| | U-1 | 中 | AchievementManager | `Awake` 缺少 `GetOrDefault` 防重复注册守卫,多场景时会抛异常 | | A-2 | 低 | DeathRespawnService | `StartGameOverCoroutine` 用 `Get()`(硬抛出)而非 `GetOrDefault` | | DC-1 | 低 | SceneLoader | 加载失败后 `_currentRoomScene` 不清空,存在内存泄漏风险 | | U-2 | 低 | SpeedrunTimer | 注释"在游戏暂停时停止"描述与 `unscaledDeltaTime` 行为相悖,有误导性 | ### 建议优化(性能 / 架构改进) | ID | 优先级 | 模块 | 描述 | |----|--------|------|------| | P-1 | 中 | PlayerMovement | `CheckGrounded` 改用 `OverlapBoxNonAlloc` + 预分配缓冲 | | P-2 | 中 | ShopController | `GetAvailableItems()` 增加 `_isDirty` 缓存,避免 UI 频繁刷新时 LINQ 分配 | | A-1 | 中 | GameManager | 增加 `_prePauseState` 字段记录暂停前状态,resume 时恢复正确状态 | | P-3 | 低 | MapManager | `OnSave` 复用已有 `List` 而非每次 `ToList()` 分配 | | P-4 | 低 | SaveManager | `_saveables` 改为 `HashSet` 以获得 O(1) `Remove` | | S-1 | 低 | SpellManager | 提前将单槽改为 `SpellSO[]`,未来多槽改动面更小 | | PU-1 | 低 | DebugCheatSystem | 用 `ServiceLocator.GetOrDefault()` 替换 `FindFirstObjectByType` | --- ## 四、各维度评分 | 维度 | 权重 | 本轮得分 | v5 得分 | 变化 | |------|------|---------|---------|------| | 架构设计 | 20% | **9.1** | 9.0 | +0.1 | | 性能 | 18% | **8.6** | 8.5 | +0.1 | | 可扩展性 | 15% | **9.0** | 8.8 | +0.2 | | 编辑器友好 | 12% | **9.3** | 8.5 | +0.8 ↑ | | 使用便利性 | 12% | **8.9** | 8.8 | +0.1 | | 框架纯净度 | 8% | **9.3** | 9.3 | — | | 数据一致性 | 8% | **9.1** | 9.2 | -0.1 | | 可测试性 | 7% | **7.9** | 7.5 | +0.4 | ### 加权总分 $$ Score = 9.1 \times 0.20 + 8.6 \times 0.18 + 9.0 \times 0.15 + 9.3 \times 0.12 + 8.9 \times 0.12 + 9.3 \times 0.08 + 9.1 \times 0.08 + 7.9 \times 0.07 $$ $$ = 1.82 + 1.548 + 1.35 + 1.116 + 1.068 + 0.744 + 0.728 + 0.553 = \mathbf{8.93 / 10} $$ **较 v5(8.73)提升 +0.20**,主要驱动因素:编辑器工具套件(+0.8)显著提升编辑器友好维度,v5 修复六项问题带动各维度小幅改善。 --- ## 五、横向竞品对标参考 | 比较维度 | BaseGames v6 | Hollow Knight(逆向参考) | 典型中型商业框架 | |----------|-------------|--------------------------|-----------------| | 事件解耦 | SO 频道 + RAII | 手工 UnityEvent / 直接引用 | MVC/MVVM 混用 | | 服务定位 | ServiceLocator + 接口隔离 | 静态 GameManager 访问 | DI 框架(Zenject 等) | | 编辑器工具 | 3 个专用 EditorWindow | 基本 Inspector | 视项目规模 | | 数据守卫 | IValidatable + 构建时中止 | 运行时检查 | 无或需手动测试 | | 平台隔离 | 编译守卫 + 接口 | 内联 ifdef | 服务抽象层 | BaseGames 框架在事件解耦与编辑器工具方面已超越多数中型商业项目的平均水准,主要差距集中在状态机子状态栈(暂停恢复逻辑)与可测试性(协程异步流)两点。 --- ## 六、下一轮修复建议优先级 ``` [必须修复] U-1 AchievementManager 补充重复注册防护 [必须修复] A-2 DeathRespawnService 改用 GetOrDefault [建议修复] A-1 GameManager 暂停恢复记忆 _prePauseState [建议修复] P-1 PlayerMovement OverlapBoxNonAlloc [建议修复] P-2 ShopController GetAvailableItems 缓存 [低优先级] DC-1 SceneLoader 失败路径清空 _currentRoomScene [低优先级] U-2 SpeedrunTimer 注释修正 [低优先级] P-3 MapManager OnSave 复用 List [低优先级] P-4 SaveManager HashSet ``` --- *评审人:GitHub Copilot(Claude Sonnet 4.6)* *上一版:`FrameworkReview_2026_May_v5.md`(加权 8.73)*