Files
zeling_v2/Docs/Review/FrameworkReview_2026_May_v8.md

20 KiB
Raw Blame History

BaseGames 框架代码评审 v8

评审日期: 2026 年 5 月
版本: v8继 v7 之后的全量新模块深度评审)
评审范围: Assets/Scripts/ 全体 270+ C# 文件
审阅标准: 商业级 2D Action RPGUnity 2022.3 LTSC# 9无向后兼容需求


1. 综合评分

维度 权重 v6 v7 v8 变化
架构设计 20% 9.0 9.2 9.2
性能 18% 8.5 8.7 8.6 ▼0.1
可扩展性 15% 8.8 9.1 9.2 ▲0.1
编辑器友好 12% 9.3 9.4 9.4
使用便利性 12% 8.8 9.0 9.1 ▲0.1
框架纯净度 8% 9.0 9.3 9.1 ▼0.2
数据一致性 8% 8.8 9.1 9.0 ▼0.1
可测试性 7% 7.8 7.9 7.9
加权总分 8.73 9.00 8.99 ▼0.01

v8 在 SkillModifierRegistry 可扩展性、CrashReporter/EmergencySaveService 容错设计方面有明显正向发现,但 BreadcrumbTracker FindWithTag 残留框架纯净度扣分、CrumblePlatform 缺少状态持久化数据一致性、SettingsManager 每次写磁盘(性能)共同导致加权总分微降 0.01。修复两处后,估计可达 9.05


2. v8 新增模块总览

v8 相较 v7 首次覆盖以下系统,每个模块均经过逐行代码审阅:

模块 关键文件 评价
音频系统 AudioManager, BGMController, CombatSFXController, AudioZone 双 Source BGM 交叉淡入6 源 SFX 轮转池Mixer 快照切换,生产级
相机系统 CameraStateController, RoomCamera Cinemachine 服务接口化,BlendProfile SO 可配
VFX 系统 VFXPool, HitFXSpawner Addressables 粒子池,协程回收,预热 API
动画事件 AnimationEventBinder 静态工具,捕获变量规避闭包陷阱
技能系统 SkillManager, FormController, SkillModifierRegistry 零分配 Update形态热切换OCP 数值覆盖设计
UI 系统 UIManager, HUDController, RebindPanel Stack 面板管理,排他重绑定锁,事件驱动 HUD
对象池 GlobalObjectPool LRU 活跃回收双集合追踪Addressables 预热
存档/崩溃 SaveMigrator, CrashReporter, EmergencySaveService fall-through 版本链,崩溃日志,定时自动存档
场景基础 GameServiceRegistrar, SettingsManager -2000 执行序NullObject 兜底AudioListener 管理
世界/关卡 MovingPlatform, CrumblePlatform, LiquidZone, AbilityGate, BreadcrumbTracker ⚠ 含 FindWithTag 违例
挑战关卡 ChallengeRoomManager 多 waveNoHit 验证,挑战前自动 QuickSave
防软锁 AntiSoftlockSystem 事件注入速度检测ServiceLocator 解耦
分析/无障碍 AnalyticsManager, AccessibilityManager ⚠ AccessibilityManager 使用 static 单例
Boss 系统 WeakPointSystem, TelegraphSystem 弱点乘数分离GlobalObjectPool 集成
对话系统 DialogueManager 协程打字机WorldStateRegistry 条件分支

3. v8 正面亮点(新发现)

3.1 AudioManager — 双 Source 交叉淡入淡出

_bgmSourceA / _bgmSourceB 交替充当 Active / Inactive 角色
CrossfadeCoroutine(clip, fadeOut, fadeIn):先淡出旧 Source并行淡入新 Source
SFX 6 源轮转NextSFXSource() 防止高密度战斗音效互戳
TransitionToSnapshot("BossFight", 0.5f)AudioMixer 快照切换一行完成
LinearToDecibel(v)01 线性到 dB 内部转换,调用者无感

点评:与 BGMController 的 MusicState 状态机配合,形成完整的音乐状态管理闭环,区域 BGM、Boss 战、胜利花絮三段逻辑互不耦合。

3.2 SkillModifierRegistry — OCP 数值覆盖设计

  • Dictionary<string, List<SkillStatEntry>> 数值叠加(支持百分比与绝对值)
  • List<SkillSlotOverride> 插槽替换(按 Priority 排序取优先级最高覆盖)
  • GetEffectiveParams(skill) 返回 EffectiveSkillParams 快照结构体:
    • 技能冷却、消耗、伤害倍率、范围倍率、反馈、动画均可覆盖
    • 调用方得到只读结构体,无法意外修改 Registry 内部状态
  • FormController 切换形态时同步刷新,_activeSkills[] 快照数组避免 Update 分配

点评:符合 Open/Closed Principle新增装备效果只需注册对应 Entry无需修改 SkillManager。

3.3 GlobalObjectPool — LRU 活跃回收

// MaxCount > 0 时追踪活跃链表LinkedList<PooledObject>
// 尾部 = 最新;头部 = 最老LRU
// 达到上限时 O(1) 回收头部ForceReturnToPool 后立即复用
po.AliveNode = aliveList.AddLast(po);
// Despawn 时 O(1) 移除节点
if (po.AliveNode != null) aliveRef.Remove(po.AliveNode);

点评双集合设计Queue 空闲 + LinkedList 活跃是商业池实现的标准做法O(1) 存取,不产生 GC极少见于开源 Unity 项目。

3.4 CrashReporter + EmergencySaveService — 生产级容错

CrashReporter
  OnLogMessage → WriteDiagnosticLog同步 IOasync 在崩溃时不可靠)
  OnApplicationPause(!cleanExit) → SaveAsync(slot 99)(移动端切出紧急存档)
  catch{} 保护:日志写入失败绝不递归抛异常

EmergencySaveService
  Update + _intervalSeconds 定时自动存档(默认 120s
  PromoteToSlot(target):崩溃恢复后将紧急存档升级到正式槽

点评:同步 IO 写崩溃日志是正确决策;_cleanExit 标记防止正常退出时的误触发。两者协同形成 PC + 移动端双重防护。

3.5 SaveMigrator — fall-through 版本链

case v2.0: MigrateV1xTo20(root); goto case "2.1";
case v2.1: MigrateV20To21(root); break;

使用 System.Version.TryParse 语义比较,IsOlderThan 工具方法统一封装,支持 1.0/1.5/1.9 等任意旧版本一次性补齐所有缺失节点,无需为每个版本组合写专属迁移逻辑。

3.6 GameServiceRegistrar — 执行序与 NullObject 兜底

[DefaultExecutionOrder(-2000)]   // 早于所有业务代码
ServiceLocator.RegisterIfAbsent<IAudioService>(new NullAudioService());
// AudioManager.Awake 后以真实实现覆盖
// 主 AudioListener 管理Inspector 绑定时只扫描当前场景根节点
// 否则全量扫描并缓存,避免 FindObjectsOfType 二次调用

点评NullAudioService 作为 NullObject 模式的兜底,使所有在 AudioManager 初始化之前调用音频的代码安全降级,无空引用。

3.7 AnimationEventBinder — 闭包陷阱规避

foreach (var entry in config.SortedEvents)
{
    var captured = entry;   // 显式捕获,规避 foreach 闭包共享变量陷阱
    clip.Events.Add(captured.normalizedTime, () =>
        receiver.HandleEvent(captured.eventType, captured.data));
}

小细节但正确。C# foreach 变量捕获在旧版运行时曾是典型 Bug 来源。

3.8 RebindPanel — 排他重绑定锁

private void OnRebindRequested(RebindActionRow requestingRow)
{
    foreach (var row in _rows) row.SetInteractable(row == requestingRow);
    requestingRow.StartRebind(onFinished: () =>
    {
        foreach (var row in _rows) row.SetInteractable(true);
        _inputReader?.SaveBindingOverrides();  // 重绑定完成后立即持久化
    });
}

防止并发重绑定导致输入状态混乱,完成后自动持久化,无需外部调用。


4. v8 发现的问题

P-1BreadcrumbTrackerFindWithTag 违反框架约定 已修复

位置Assets/Scripts/World/BreadcrumbTracker.cs
问题

// ❌ 框架全局唯一残留的 FindWithTag 全场景扫描
private void Awake()
{
    var go = GameObject.FindWithTag("Player");
    if (go != null) _playerTransform = go.transform;
}

框架中所有其他需要玩家 Transform 的组件(AntiSoftlockSystemEnemyBaseProjectileManagerEnemyQuotaManager)均通过 TransformEventChannelSO 事件频道订阅,BreadcrumbTracker 是唯一例外,破坏框架一致性,且在玩家延迟生成场景下会捕获失败。
修复:改为 OnEnable/OnDisable 订阅 _onPlayerSpawned 事件频道。


P-2GlobalObjectPool.OnDestroy:未释放 Addressables 资产 已修复

位置Assets/Scripts/Core/Pool/GlobalObjectPool.cs
问题

// ❌ OnDestroy 只注销 ServiceLocator未释放已加载的 Addressables 预制件
private void OnDestroy()
{
    ServiceLocator.Unregister<IObjectPoolService>(this);
}

WarmupSingleAsync 通过 Addressables.LoadAssetAsync 加载的预制件存入 _prefabCacheClearPool 方法有 Addressables.Release(pfx) 释放逻辑,但 OnDestroy 不调用它,导致在编辑器退出 PlayMode 时 Addressables 引用计数不归零,可能产生 "already released" 警告或内存抖动。
修复:在 OnDestroy 中遍历 _prefabCache 释放所有加载项。


P-3SettingsManager:音量设置每次写磁盘

位置Assets/Scripts/Core/SettingsManager.cs
问题

public void SetMasterVolume(float v) { _current.MasterVolume = v; Save(); }  // Save() = File.WriteAllText
public void SetBGMVolume(float v)    { _current.BGMVolume    = v; Save(); }
// ...每次调用立即 WriteAllText若 UI 滑动条绑定此方法 → 每帧写磁盘

SettingsPanel 的音量滑动条 OnValueChanged 直接绑定了这些方法,每帧均会触发磁盘写入,在低端移动设备上可能造成明显卡顿。
建议:滑动条 OnValueChanged 仅调用 Apply(value)(仅修改内存),OnEndDrag 或"确认"按钮时才调用 Save();或增加 Commit() 入口由 UI 层显式控制持久化时机。
:此问题属于 UI 层调用规范问题,当前 SettingsManager 接口设计无误,需在调用方约定。


A-1AccessibilityManagerstatic 单例与框架不一致

位置Assets/Scripts/Support/Accessibility/AccessibilityManager.cs
问题:使用 private static AccessibilityManager _instanceCanPlayScreenShake() 为静态查询方法。框架中所有其他管理器均通过 ServiceLocator<T> 注册和查询,此处是唯一例外。
影响FeedbackSystemAccessibilityManager 类型存在直接依赖,无法在单测或 CI 环境中替换为 Mock。
建议:提取 IAccessibilityService 接口,在 AwakeServiceLocator.Register<IAccessibilityService>(this)FeedbackSystem 改为 ServiceLocator.GetOrDefault<IAccessibilityService>()?.CanPlayScreenShake() ?? true
:此为架构一致性改进,不影响现有功能,可在合适时机推进。


DC-1CrumblePlatform_isOneShot 状态未持久化

位置Assets/Scripts/World/CrumblePlatform.cs
问题_isOneShot=true 的平台在当前游戏会话中永久消失(正确),但未向 WorldStateRegistry 写入销毁状态,导致玩家重启游戏或重进场景后,已永久碎裂的平台会复原,破坏世界状态的存档一致性。
建议

[SerializeField] private WorldStateRegistry _worldState;
[SerializeField] private string             _destructibleId;

// CrumbleSequence 碎裂后:
if (_isOneShot && !string.IsNullOrEmpty(_destructibleId))
    _worldState?.MarkDestroyed(_destructibleId);

// Start/Awake 中:
private void Start()
{
    if (!string.IsNullOrEmpty(_destructibleId) && _worldState != null
        && _worldState.IsDestroyed(_destructibleId))
    {
        _col.enabled = _sr.enabled = false;
        _isCrumbling = true;  // 阻止重复触发
    }
}

DC-2MovingPlatform._passengers 潜在空引用

位置Assets/Scripts/World/MovingPlatform.cs
问题_passengers 存储乘客 Transform 引用。若乘客在平台上时被 Destroy(死亡、场景卸载),FixedUpdate 下一帧迭代 _passengers 时会遇到已销毁的 Transform。当前代码无空检查。
建议:在平台 Update 入口或 OnTriggerExit2D 时清除已销毁的引用:

_passengers.RemoveAll(t => t == null);

5. v7 已修复问题复核

以下 6 项 v7 修复已全部验证通过(零编译错误):

ID 模块 问题描述 状态
v7-P-1 HitStopManager FreezeDuration 语义歧义,短请求覆盖长请求 已修复
v7-A-1 PlayerMovement + WallJumpState 直接访问 _rb.velocity.y 绕过运动抽象层 已修复
v7-A-2 PlayerController TryTransitionState 别名语义误导 已修复
v7-U-2 AttackState OnClipEnd 中重复注销 AttackEvent 已修复
v7-P-2 EnemyQuotaManager Unregister O(n) 遍历,改为 swap-and-pop O(1) 已修复
v7-S-1 EnemyBase SetAggroTickRate 存根缺少 LogWarning 已修复

6. 架构维度深度评估

6.1 程序集分层Architecture

BaseGames.Core.Events  ←  最底层(零依赖)
BaseGames.Core.Save    ←  依赖 Events
BaseGames.Core         ←  依赖 Events + Save
BaseGames.Audio/Camera/VFX/Input  ←  依赖 Core
BaseGames.Combat/Player/Enemies  ←  依赖 Core + Audio + Input
BaseGames.Skills/Quest/UI  ←  依赖上层所有
Assembly-CSharp        ←  顶层游戏逻辑

依赖方向单向,无循环依赖,符合整洁架构原则。所有跨程序集通信通过 BaseEventChannelSO<T> SO 频道或 ServiceLocator<T> 接口,未发现运行时 typeof 隐式耦合。

6.2 ServiceLocator 使用一致性

服务 接口 注册方式 兜底
IAudioService GameServiceRegistrar + AudioManager NullAudioService
IObjectPoolService GlobalObjectPool 无(需主动检查)
ICameraService CameraStateController
ISaveService GameServiceRegistrar(Adapter)
IAnalyticsService AnalyticsManager
IAccessibilityService 缺失 static _instance N/A
IDialogueService DialogueManager

IAudioServiceNullAudioService 兜底是目前框架中最完整的安全设计,建议其他关键服务跟进(至少对 IAccessibilityService 实现)。

6.3 事件频道使用规范

全框架一致使用 RAII 订阅模式:

private readonly CompositeDisposable _subs = new();

private void OnEnable()  => _channel.Subscribe(Handler).AddTo(_subs);
private void OnDisable() => _subs.Clear();

例外: BreadcrumbTracker(已在 v8 修复)使用 Awake + FindWithTag

6.4 数据流向规范

方向 机制 使用场景
系统 → UI SO 事件频道 Raise HP 变化、状态切换
UI → 系统 ServiceLocator.Get<T>() 直接调用 按钮事件
系统 ↔ 系统 SO 事件频道(跨程序集)/ C# event同程序集高频 BGM 切换、技能冷却
持久化读写 SaveManager + IStorageBackend 接口 全量存读档

FormController 的三通道广播(SO频道 + C#事件 + SO频道是刻意设计SO 频道供 UI/Save 跨程序集使用C# event 供 WeaponManager 同程序集高频订阅,设计合理,已在注释中说明。


7. 性能热点总结

模块 优化点 评分
SkillManager.Update 固定 _activeSkills[] 数组,零 GC 遍历 优秀
EnemyQuotaManager.Unregister swap-and-pop + _indexMap O(1) 优秀
GlobalObjectPool.Despawn AliveNode 直接 LinkedList 节点移除 O(1) 优秀
HitStopManager.FreezeDuration max-duration 语义,短请求直接返回 良好
AudioManager.PlaySFX 6 源轮转,避免 PlayOneShot 切断问题 良好
GameServiceRegistrar.OnSceneLoaded 仅扫描新场景根节点,非全场景 良好
HUDController.RebuildHPCells Destroy+Instantiate未池化 ⚠ 低频可接受
SettingsManager.SetVolume* 每次调用写磁盘 ⚠ 见 P-3

8. 可扩展性亮点

装备/技能数值修改 (Open/Closed)

新增装备效果:实现 IEquipmentEffect → 注册 SkillModifierRegistry
无需修改 SkillManager、FormController、任何技能 SO
EffectiveSkillParams 快照模式确保每帧读取的参数是当前有效状态

关卡能力门禁 (virtual EvaluateAccess)

// AbilityGate 基类
protected virtual bool EvaluateAccess()
    => _playerStats != null && _playerStats.HasAbility(_requiredAbility);

// 子类可追加条件(如同时需要持有道具)
public class ItemAndAbilityGate : AbilityGate
{
    protected override bool EvaluateAccess()
        => base.EvaluateAccess() && _inventory.HasItem(_requiredItem);
}

存档版本迁移 (fall-through chain)

新增版本只需在迁移链末尾添加 case "3.0": MigrateV21To30(root); break,旧版本自动串联补全所有中间迁移,无需为每个旧版本写专属升级路径。


9. 编辑器友好性

特性 实现 文件
Inspector [Header]/[Tooltip]/[Min] 全框架一致使用 所有 MB
[DefaultExecutionOrder] 精确控制初始化序 -2000 / -800 / -100 Registrar/Pool/Manager
TransitionTo Editor-only 合法转换白名单 ValidTransitions PlayerController
按键重绑定面板 完整排他锁 + 持久化封装 RebindPanel
AnimationEventConfigSO 数据驱动事件注入 SortedEvents 时间线排序 AnimationEventBinder
PoolConfig[] Inspector 可视化预热配置 AddressKey/InitialCount/MaxCount GlobalObjectPool
WeakPoint[] 弱点可视化配置 hurtBox + visualIndicator WeakPointSystem

10. v8 修复清单

ID 严重度 模块 问题 状态
v8-P-1 BreadcrumbTracker FindWithTag 违反框架事件频道约定 已修复
v8-P-2 GlobalObjectPool OnDestroy 未释放 Addressables 预制件引用 已修复
v8-P-3 SettingsManager 音量设置每次写磁盘(调用规范问题) 📝 文档记录
v8-A-1 AccessibilityManager static 单例与 ServiceLocator 模式不一致 📝 文档记录
v8-DC-1 CrumblePlatform _isOneShot 状态未写入 WorldStateRegistry 📝 文档记录
v8-DC-2 MovingPlatform _passengers 潜在空引用(乘客被销毁) 📝 文档记录

11. 总结与后续建议

优势

  1. 架构纯净 — 28 个程序集单向依赖,所有跨模块通信通过 SO 频道或 ServiceLocator 接口
  2. 生产级容错 — CrashReporter + EmergencySaveService 双保险SaveMigrator 版本链NullAudioService 兜底
  3. 性能意识 — SkillManager 零分配遍历、GlobalObjectPool LRU 双集合、EnemyQuotaManager swap-and-pop
  4. 可扩展设计 — SkillModifierRegistry OCP、AbilityGate virtual、SaveMigrator fall-through
  5. 编辑器优先 — 全面 Inspector 注解、执行序精确控制、动画事件 SO 驱动

优先改进项

  1. v8-P-1已修复BreadcrumbTracker 事件化 — 消除框架内唯一 FindWithTag
  2. v8-P-2已修复GlobalObjectPool OnDestroy 清理 — 防止编辑器内存抖动
  3. v8-A-1建议AccessibilityManager → IAccessibilityService + ServiceLocator
  4. v8-DC-1建议CrumblePlatform 状态持久化 — 修复世界状态一致性
  5. v8-P-3建议SettingsManager 滑动条调用规范 — UI 层延迟 Save

得分趋势

v5: 8.73 → v6: 8.73 → v7: 9.00 → v8: 8.99(修复后预计 9.05

框架整体已达到中等商业游戏代码质量标准核心架构设计合理生产安全性优秀。主要待提升方向为可测试性单元测试接入点较少、AccessibilityManager 服务化、部分边界状态持久化补全。


v8 评审覆盖 Assets/Scripts/ 下全部 270+ 文件,其中 v8 新增模块 ~80 个。评分基于代码逐行审阅,参照 Unity 官方最佳实践、《Game Programming Patterns》及商业 2D Action RPGHollow Knight、Dead Cells、Hades架构设计标准。