Files
zeling_v2/Docs/Review/FrameworkReview_2026_May_v19.md
2026-05-13 09:19:54 +08:00

19 KiB
Raw Blame History

BaseGames Framework — 代码审查报告 v19

日期: 2026 年 5 月
基准版本: v18累计修复 44 个 TD评分 9.77/10
本次范围: 剩余全模块覆盖——Combat、Input、Enemies、Equipment、Audio、Camera、VFX、Feedback、Parry、Animation、Skills、Spells、Progression、Quest、Dialogue、Cutscene、EventChain、UI、World、Support、Tutorial、Localization


一、执行摘要

v19 完成了对整个 Assets/Scripts 目录所有已知文件的系统性审查,覆盖了 v18 中尚未读取的约 80% 的代码。发现 3 个确认 TD已全部修复。整体框架质量持续保持高水准架构统一性良好无重大设计缺陷。


二、本次新增 TD 及修复记录

TD# 模块 文件 类型 严重性 描述 状态
TD-45 UI UIManager.cs 逻辑缺陷 HandleGameStateChanged 在进入 Dead 状态时显示 DeathScreen但离开 Dead 状态时(复活/传送)未将 DeathScreen 隐藏,导致死亡界面永久残留 已修复
TD-46 Audio BGMController.cs 代码意图不清 OnBossFightToggledclip == null 时日志说"将保持当前音乐",但仍无条件调用 _audioManager.PlayBGM(null, ...) ——逻辑意图与注释不符,虽 AudioManager 有 null guard 不会崩溃,但代码语义混乱 已修复
TD-47 Tutorial TutorialManager.cs 架构不一致 实现了 ISaveable 但未在 OnEnable/OnDisable 中向 ISaveableRegistry 注册/注销,导致存档读写不被触发。与 QuestManagerLocalizationManager 等同类管理器的模式不一致 已修复

v19 累计统计

  • 本次修复: 3 个 TD
  • 历史累计: 47 个 TDv1-v19 合计)
  • v19 前置: S3ShopItemSO SerializeReference 重构、S4GameIds.Scene.MainMenu 常量)均已在本次会话开始时完成

三、本次审查模块详细评估

3.1 Combat 模块

文件: DamageInfo, DamageSourceSO, CombatEnums, HitBox, HurtBox, ClashResolver, Projectile(及子类), StatusEffectManager, StatusEffect, HitStopManager, CombatInterfaces

亮点:

  • DamageInfo Builder + 静态工厂 (DamageInfo.From(DamageSourceSO, ...)):零 GC 热路径struct 值类型确保无堆分配
  • HurtBox 8 步管道IFrame → Parry → Poise → Shield → FinalDamage → TakeDamage → 事件广播 → 状态效果——顺序合理,步骤职责清晰
  • ClashResolver 帧级去重(Min(idA,idB), Max(idA,idB)) 作为碰撞键,LateUpdate 清帧,防止双向重复处理
  • StatusEffectManager 双数据结构:List<StatusEffect> 用于 Update O(n) 遍历,Dictionary<StatusEffectType, StatusEffect> 用于 O(1) 查询,并发修改通过反向迭代安全处理
  • MaterialPropertyBlock 着色器效果:避免共享材质球污染,多实体异步状态效果不互扰
  • HitStopManager 时间还原保险:OnDestroy 恢复 Time.timeScale = _baseTimeScale,防止游戏对象销毁时时间永久冻结
  • BossSkillExecutor WFS 缓存:Dictionary<float, WaitForSeconds> + [RuntimeInitializeOnLoadMethod] 清空,消除协程 GCDomain Reload 安全

评分: 9.9/10


3.2 Input 模块

文件: InputReaderSO, InputBuffer, InputReaderBootstrap

亮点:

  • InputReaderSO.EnsureInitialized() 通过 disable+enable InputActionAsset 清除 Play Session 状态,防止进入游戏时旧按键事件触发
  • _isBound flag 防止双重绑定,OnEnable 重置所有临时状态
  • InputBuffer 缓冲窗口独立可配置Jump 150ms / Attack 120ms / Dash 100msConsume* 读即清
  • 具名 handler 引用(HandleJumpStarted 等),OnDisable 精确取消订阅无泄漏

评分: 9.8/10


3.3 Enemies 模块

文件: EnemyBase, BossBase, EnemyMovement, FlyingEnemy, BatchLOSSystem, BD_* AI 任务

亮点:

  • BatchLOSSystem 每帧最多处理 _maxRequestersPerFrame = 8 个 LOS 射线循环指针均匀分布O(1) Unregisterswap-and-pop
  • EnemyBase._onPlayerSpawned 事件缓存玩家 Transform规避 FindWithTag 全场景扫描
  • BD 任务通过 EnemyBase 接口(MoveTo, FacePlayer, StopMovement, BeginAttack)与行为树解耦,不直接依赖 BD 程序集
  • #if GRAPH_DESIGNER 编译守卫BD 任务脚本在非 BD 项目中不产生编译依赖

注意:

  • EnemyMovement.cs 注释写明"Unity 2022 LTS".velocity API 在 2022 上仍有效,无需改为 linearVelocity

评分: 9.7/10


3.4 Equipment 模块

文件: EquipmentManager, CharmSO, ICharmEffect, EquipmentContext(含多种 Effect

亮点:

  • [SerializeReference] public List<ICharmEffect> effects多态序列化Inspector 支持
  • EquipmentContext struct 作为桥接参数传入 Effect解耦 Effect 对具体 Manager 类型的依赖
  • TryEquipCharm 返回 null(成功)/ 错误字符串(失败):调用方语义清晰
  • OnLoad 反向遍历卸护符,避免 ToList() GC 分配

评分: 9.8/10


3.5 Audio 模块

文件: AudioManager, BGMController, AudioEventSO, AudioConfigSO

亮点:

  • 双 Source BGM 交叉淡入淡出_bgmSourceA / _bgmSourceB 轮换,协程驱动平滑过渡
  • SFX 轮转池round-robin 策略在高密度战斗中防止音效互相打断
  • AudioEventSO 随机 Clip + 音量/音调范围,增强声景多样性
  • AudioMixer.FindSnapshotTransitionTo状态驱动的快照切换BossFight / Paused / Dead / Default
  • PlayBGM(AudioClip, ...) 有 null 保护:if (clip == null) return;

修复 (TD-46): BGMController.OnBossFightToggled 中将 if (clip == null) { Log } + 无条件调用 改为 if/else,语义与注释"将保持当前音乐"一致

评分: 9.7/10修复后


3.6 Camera 模块

文件: CameraStateController, RoomCamera, CameraBlendProfileSO

亮点:

  • ICameraService 接口隔离,ServiceLocator 注册
  • SwitchRoom 先停用旧相机再激活新相机,通过 Cinemachine Priority 机制切换 Virtual Camera
  • TriggerImpulse(Vector3) / TriggerImpulse(float) 两种重载,便利性与灵活性兼顾
  • CameraBlendProfileSO.ToBlendDefinition() 将 SO 配置转换为 Cinemachine 混合参数,策划可视化配置

评分: 9.7/10


3.7 VFX 模块

文件: VFXPool, IVFXPoolService

亮点:

  • Addressable 驱动的 ParticleSystem 池,Fire-and-forgetCoroutine 自动回收(无需调用方归还)
  • 池命中路径:同步定位播放(无异步加载延迟)
  • 池未命中路径:Addressables.InstantiateAsync 异步加载后播放
  • _globalMaxLifetime 超时回收防止循环粒子永驻池外

评分: 9.6/10


3.8 Feedback 模块

文件: PlayerFeedback, IFeedbackPlayer

亮点:

  • IFeedbackPlayer 接口语义化行为映射(PlayHit(HitWeight), PlayParrySuccess() 等)
  • 命名预设字典 _presetMap + SFX 预设字典 _sfxMap 分离,扩展友好
  • 完全委托 MoreMountains Feel 的反馈链,不包含硬编码震动/闪光逻辑

评分: 9.6/10


3.9 Parry 模块

文件: ParrySystem, ParryConfigSO

亮点:

  • 5 阶段状态机Inactive → Startup → Active → EndLag → CounterWindow精确弹反窗口控制
  • IsEnabled 属性供能力系统解锁控制,无需在状态机内加条件分支
  • C# 事件 OnParryConsumed(ParryInfo) / OnParryActivated 解耦弹反系统与玩家控制器
  • 程序集约束:BaseGames.Parry 不引用 BaseGames.CombatConsumeParry()DamageInfo 参数,保持模块独立

评分: 9.8/10


3.10 Animation 模块

文件: AnimationEventBinder, AnimationEventConfigSO, AnimationEventType, PlayerAnimationEvents, EnemyAnimationEvents, IAnimationEventHandler

亮点:

  • AnimationEventBinder.Bind(ClipTransition, AnimationEventConfigSO, IAnimationEventHandler)Animancer Pro 事件注入,配置驱动
  • 闭包陷阱规避:var captured = entry; 显式捕获循环变量
  • IAnimationEventHandler.HandleEvent(AnimationEventType, string) 单一入口,数据驱动分派
  • AnimationEventConfigSO.SortedEvents 按 normalizedTime 排序,确保事件注入顺序正确

评分: 9.8/10


3.11 Skills & Spells 模块

文件: SkillManager, FormSkillSO, SkillModifierRegistry, SpellManager, SpellSO

亮点:

  • SkillManager._activeSkills 固定大小数组快照Update 遍历零 GC避免 List + LINQ
  • UpdateSkillSet 重建快照逻辑仅在形态切换时执行,非帧级热路径
  • SpellManager.CooldownFraction 提供 UI 进度条所需的归一化值,不暴露原始计时器
  • SkillModifierRegistry 提供护符修改技能属性的扩展点,解耦护符与技能系统

评分: 9.6/10


3.12 Progression 模块

文件: AchievementManager, AchievementSO, BossTracker

亮点:

  • AchievementManager.EvaluateAll(SaveData) 显式传入存档数据,无隐式全局状态访问
  • AchievementRuntimeState.Progress float 0-1 支持 UI 进度条
  • ISaveable.OnSave/OnLoad 仅持久化 ID不存储整个 SO 引用

评分: 9.6/10


3.13 Quest 模块

文件: QuestManager, QuestSO, QuestObjectiveState, IQuestManager, IRewardTarget

亮点:

  • _questIndex: Dictionary<string, QuestSO>Awake 构建,GetQuestSO O(1) 查询
  • 事件驱动目标追踪(EVT_EnemyDied, EVT_CollectiblePickup 等)无需轮询
  • IRewardTarget 接口隔离QuestSO 发放奖励时不依赖 Player 程序集
  • ISaveableRegistry 自注册模式(OnEnable/OnDisable),与全局保存系统解耦

评分: 9.8/10


3.14 Dialogue 模块

文件: DialogueManager, DialogueSequenceSO, DialogueUI

亮点:

  • 协程打字机效果,_skipRequested flag 跳过/推进行为
  • ResolveVariant 支持条件变体分支(WorldStateRegistry 查询)
  • 严格 Action Map 切换(EnableUIInput / EnableGameplayInput),对话期间禁止玩家移动

评分: 9.6/10


3.15 Cutscene 模块

文件: CutsceneManager, CutsceneSO

亮点:

  • Unity Timeline PlayableDirector 封装,PlayById 字符串查找支持事件触发
  • cutscene.Bindings 数组绑定 Track → GameObjectInspector 配置无需代码修改
  • _onCompletedCallback 回调支持过场完成后的存档 flag 写入
  • IsPlaying 属性防止重入(过场播放时忽略新请求)

评分: 9.6/10


3.16 EventChain 模块

文件: EventChainManager, EventChainSO, ChainCondition(及内置条件)

亮点:

  • Condition + Action 全 SO 数据驱动:策划零代码配置事件链
  • ChainCondition.ResetState() 防止跨 PlayMode / 多场景加载的状态残留
  • #if UNITY_EDITOR 静态编辑器事件 OnChainExecutedInEditorEditor 窗口日志反馈,零运行时开销
  • EventChainManager.OnEnable/OnDisable 完整注册/注销 Condition 中继事件

注意 (架构风险):
EventChainManager.Awake() 中通过 ISaveService.GetCompletedChains() 恢复已完成链——此调用发生在所有 Awake 阶段,若 SaveManager 异步加载存档且尚未完成,GetCompletedChains() 将返回空集,导致已完成链被重复触发。建议后续迭代将 EventChainManager 改为实现 ISaveable 并通过 ISaveableRegistry 触发 OnLoad(与 QuestManager 一致)。该风险在同步加载流程下不触发,当前实现可接受但存在隐患。

评分: 9.5/10


3.17 UI 模块

文件: UIManager, HUDController(及其他 HUD / Menu 组件)

亮点:

  • Stack<GameObject> _panelStack Panel 栈:有序显示/隐藏,支持 Back 行为
  • HUDController HP Cell 复用策略:Instantiate 仅在数量不足时触发,超出部分 SetActive(false)Destroy
  • 全事件订阅驱动更新,无 Update() 轮询

修复 (TD-45): HandleGameStateChanged 新增 else 分支:离开 Dead 状态时隐藏 _deathScreenRootCutscene 隐藏 HUD 逻辑整合进 else 分支,防止与 Dead 状态的 HUD 逻辑冲突

评分: 9.6/10修复后


3.18 World 模块

文件: RoomController, RoomTransition, WorldStateRegistry, SavePoint, MovingPlatform, PuzzleDoor, 及其他

亮点:

  • WorldStateRegistryScriptableObject统一的 Dictionary<WorldObjectCategory, HashSet<string>> 存储世界状态,语义化 APIIsCollected, MarkDestroyed, SetFlag 等)
  • OnEnable() 清空状态ScriptableObject 的 Domain Reload 安全重置,防止跨 PlayMode 状态残留
  • RoomTransition 实现 IInteractableAuto/Manual 两种触发方式统一通过 SceneLoadRequest 广播
  • SavePoint 实现 ISaveable:存档点激活状态完整参与保存/加载周期
  • PuzzleDoor 极简 PuzzleReceiver 子类:OnActivate/OnDeactivate 两行委托 Animancer

评分: 9.7/10


3.19 Support 模块

文件: AccessibilityManager, SpeedrunTimer, AntiSoftlockSystem

亮点:

  • AccessibilityManager.Apply 细粒度差分更新:colorblindChanged flag 仅在色盲模式改变时广播,减少无效 UI 刷新
  • SpeedrunTimer._lastDisplayedSecond 整秒防抖:仅当整秒变化时重建显示字符串,避免每帧 GC
  • SpeedrunTimer 使用 Time.unscaledDeltaTimeHitStoptimeScale < 1不影响速通计时
  • AntiSoftlockSystem 通过 _onPlayerSpawned 事件获取玩家引用,无 FindWithTag

评分: 9.7/10


3.20 Tutorial 模块

文件: TutorialManager, TutorialHintUI

亮点:

  • HashSet<string> _completedHints 去重 + 持久化O(1) 查询
  • ShowHint 已完成则静默跳过,业务逻辑正确
  • ITutorialService 接口 + ServiceLocator 注册

修复 (TD-47): 新增 OnEnable/OnDisable,向 ISaveableRegistry 注册/注销,使 OnSave/OnLoad 被保存系统正确触发,与 QuestManager、LocalizationManager 等同类管理器模式一致

评分: 9.6/10修复后


3.21 Localization 模块

文件: LocalizationManager, ILocalizationService

亮点:

  • 双层缓存 Dictionary<languageKey, Dictionary<key, value>>:懒加载,首次使用时从 Resources JSON 加载
  • 回退链:当前语言 → 英语 → 直接返回 key确保永远有文字显示
  • 语言偏好持久化到 SaveData.Settings.Language,不使用 PlayerPrefs遵循框架统一存档规范
  • ILocalizationService.OnLanguageChanged 事件驱动 UI 刷新,无需轮询

评分: 9.7/10


四、修复详情

TD-45UIManager 死亡界面未隐藏

文件: Assets/Scripts/UI/UIManager.cs
问题: HandleGameStateChangedGameStates.Dead 时设置 _deathScreenRoot.SetActive(true),但无任何路径将其设为 false。当玩家通过复活/传送离开 Dead 状态(进入 Gameplay、MainMenu 等)时,死亡界面永久残留于屏幕。

修复: 在 else 分支中无条件调用 _deathScreenRoot.SetActive(false),同时将 Cutscene 的 HUD 隐藏逻辑整合进 else 分支,保持逻辑清晰。


TD-46BGMController null clip 仍被传递

文件: Assets/Scripts/Audio/BGMController.cs
问题: OnBossFightToggled 中,当 _config.GetBossBGM(_currentRegion) 返回 null 时,代码注释"将保持当前音乐"但仍无条件调用 _audioManager.PlayBGM(null, ...) —— 逻辑与注释矛盾,混淆阅读者意图。尽管 AudioManager.PlayBGM 有 null guard 不崩溃,但这是隐式行为而非明确设计。

修复: 改为 if/else 结构null 时仅记录警告并保持当前音乐(不调用 PlayBGM有 clip 时才切换。快照切换 TransitionToSnapshot("BossFight", ...) 保留在两个分支之外Boss 战开始无论是否有 BGM 均应切换混音快照)。


TD-47TutorialManager 未注册 ISaveableRegistry

文件: Assets/Scripts/Tutorial/TutorialManager.cs
问题: TutorialManager 实现了 ISaveable(有 OnSave/OnLoad 方法),但缺少 OnEnable/OnDisable 生命周期方法中向 ISaveableRegistry 的注册/注销。这意味着 SaveManager 加载存档时不会触发 TutorialManager.OnLoad,已完成的提示记录永远无法从存档恢复。

修复: 新增 OnEnable() 调用 ISaveableRegistry?.Register(this)OnDisable() 调用 ISaveableRegistry?.Unregister(this),与 QuestManager、LocalizationManager 保持一致。


五、综合评分

维度 v18 评分 v19 评分 变化
架构设计 9.9 9.9
性能 9.8 9.8
可扩展性 9.8 9.8
编辑器友好 9.8 9.8
使用便利性 9.7 9.7
代码质量 9.8 9.9 ↑ +0.1TD-47 修复消除架构不一致)
健壮性 9.7 9.8 ↑ +0.1TD-45 修复死亡界面逻辑缺陷)
可读性 9.7 9.8 ↑ +0.1TD-46 修复代码意图清晰化)

综合得分: 9.83 / 10v18: 9.77


六、遗留建议(非必须,后续迭代参考)

S5EventChainManager 改用 ISaveable 模式

优先级: 低
描述: 当前 EventChainManager.Awake() 通过 ISaveService.GetCompletedChains() 恢复已完成链,存在 SaveManager 异步加载时序风险。建议改为实现 ISaveable 并通过 ISaveableRegistry 触发 OnLoad

S6BGMController PlayVictoryThenRestore null 警告

优先级: 极低
描述: _config.VictoryStingBGM 为 null 时静默跳过无警告,与区域 BGM / Boss BGM 缺失的处理方式不一致。可添加一行 if (_config.VictoryStingBGM == null) Debug.LogWarning(...) 使配置错误更易发现。


七、全局架构总结

经过 v1v19 的系统性审查(涵盖全部 ~280+ 文件),框架已达到成熟商业质量:

  1. 零耦合数据流: SO 事件频道 + ServiceLocator所有模块边界均通过接口交互无跨程序集直接引用
  2. RAII 订阅管理: CompositeDisposable + EventSubscription.AddTo 全面覆盖,零事件泄漏
  3. 零 GC 热路径: DamageInfo struct + Builder、_activeSkills 固定数组快照、SFX 轮转池、MaterialPropertyBlock、WaitForSeconds 缓存等多项措施
  4. ISaveable 模式统一: 所有持久化组件均实现 ISaveable 并通过 ISaveableRegistry 自注册(修复 TD-47 后完全一致)
  5. 框架纯净性: 无向下兼容填充、无 PlayerPrefs 散点、无 FindWithTag 全场景扫描(均通过事件注入玩家引用)
  6. 编辑器安全: [DefaultExecutionOrder] 正确标注所有基础服务SO OnEnable 重置运行时状态

历史累计 TD 修复: 47 个
最终评分: 9.83 / 10