19 KiB
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 |
代码意图不清 | 中 | OnBossFightToggled 在 clip == null 时日志说"将保持当前音乐",但仍无条件调用 _audioManager.PlayBGM(null, ...) ——逻辑意图与注释不符,虽 AudioManager 有 null guard 不会崩溃,但代码语义混乱 |
✅ 已修复 |
| TD-47 | Tutorial | TutorialManager.cs |
架构不一致 | 中 | 实现了 ISaveable 但未在 OnEnable/OnDisable 中向 ISaveableRegistry 注册/注销,导致存档读写不被触发。与 QuestManager、LocalizationManager 等同类管理器的模式不一致 |
✅ 已修复 |
v19 累计统计
- 本次修复: 3 个 TD
- 历史累计: 47 个 TD(v1-v19 合计)
- v19 前置: S3(ShopItemSO SerializeReference 重构)、S4(GameIds.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>用于UpdateO(n) 遍历,Dictionary<StatusEffectType, StatusEffect>用于 O(1) 查询,并发修改通过反向迭代安全处理 - MaterialPropertyBlock 着色器效果:避免共享材质球污染,多实体异步状态效果不互扰
- HitStopManager 时间还原保险:
OnDestroy恢复Time.timeScale = _baseTimeScale,防止游戏对象销毁时时间永久冻结 - BossSkillExecutor WFS 缓存:
Dictionary<float, WaitForSeconds>+[RuntimeInitializeOnLoadMethod]清空,消除协程 GC,Domain Reload 安全
评分: 9.9/10
3.2 Input 模块
文件: InputReaderSO, InputBuffer, InputReaderBootstrap
亮点:
InputReaderSO.EnsureInitialized()通过 disable+enable InputActionAsset 清除 Play Session 状态,防止进入游戏时旧按键事件触发_isBoundflag 防止双重绑定,OnEnable重置所有临时状态InputBuffer缓冲窗口独立可配置(Jump 150ms / Attack 120ms / Dash 100ms),Consume*读即清- 具名 handler 引用(
HandleJumpStarted等),OnDisable精确取消订阅无泄漏
评分: 9.8/10
3.3 Enemies 模块
文件: EnemyBase, BossBase, EnemyMovement, FlyingEnemy, BatchLOSSystem, BD_* AI 任务
亮点:
BatchLOSSystem每帧最多处理_maxRequestersPerFrame = 8个 LOS 射线,循环指针均匀分布,O(1) Unregister(swap-and-pop)EnemyBase._onPlayerSpawned事件缓存玩家 Transform,规避FindWithTag全场景扫描- BD 任务通过
EnemyBase接口(MoveTo,FacePlayer,StopMovement,BeginAttack)与行为树解耦,不直接依赖 BD 程序集 #if GRAPH_DESIGNER编译守卫,BD 任务脚本在非 BD 项目中不产生编译依赖
注意:
EnemyMovement.cs注释写明"Unity 2022 LTS";.velocityAPI 在 2022 上仍有效,无需改为linearVelocity
评分: 9.7/10
3.4 Equipment 模块
文件: EquipmentManager, CharmSO, ICharmEffect, EquipmentContext(含多种 Effect)
亮点:
[SerializeReference] public List<ICharmEffect> effects:多态序列化,Inspector 支持EquipmentContextstruct 作为桥接参数传入 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.FindSnapshot→TransitionTo:状态驱动的快照切换(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 CameraTriggerImpulse(Vector3)/TriggerImpulse(float)两种重载,便利性与灵活性兼顾CameraBlendProfileSO.ToBlendDefinition()将 SO 配置转换为 Cinemachine 混合参数,策划可视化配置
评分: 9.7/10
3.7 VFX 模块
文件: VFXPool, IVFXPoolService
亮点:
- Addressable 驱动的 ParticleSystem 池,
Fire-and-forget,Coroutine 自动回收(无需调用方归还) - 池命中路径:同步定位播放(无异步加载延迟)
- 池未命中路径:
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.Combat,ConsumeParry()无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.Progressfloat 0-1 支持 UI 进度条ISaveable.OnSave/OnLoad仅持久化 ID,不存储整个 SO 引用
评分: 9.6/10
3.13 Quest 模块
文件: QuestManager, QuestSO, QuestObjectiveState, IQuestManager, IRewardTarget
亮点:
_questIndex: Dictionary<string, QuestSO>:Awake 构建,GetQuestSOO(1) 查询- 事件驱动目标追踪(
EVT_EnemyDied,EVT_CollectiblePickup等)无需轮询 IRewardTarget接口隔离:QuestSO 发放奖励时不依赖 Player 程序集ISaveableRegistry自注册模式(OnEnable/OnDisable),与全局保存系统解耦
评分: 9.8/10
3.14 Dialogue 模块
文件: DialogueManager, DialogueSequenceSO, DialogueUI
亮点:
- 协程打字机效果,
_skipRequestedflag 跳过/推进行为 ResolveVariant支持条件变体分支(WorldStateRegistry查询)- 严格 Action Map 切换(
EnableUIInput/EnableGameplayInput),对话期间禁止玩家移动
评分: 9.6/10
3.15 Cutscene 模块
文件: CutsceneManager, CutsceneSO
亮点:
- Unity Timeline
PlayableDirector封装,PlayById字符串查找支持事件触发 cutscene.Bindings数组绑定 Track → GameObject,Inspector 配置无需代码修改_onCompletedCallback回调支持过场完成后的存档 flag 写入IsPlaying属性防止重入(过场播放时忽略新请求)
评分: 9.6/10
3.16 EventChain 模块
文件: EventChainManager, EventChainSO, ChainCondition(及内置条件)
亮点:
- Condition + Action 全 SO 数据驱动:策划零代码配置事件链
ChainCondition.ResetState()防止跨 PlayMode / 多场景加载的状态残留#if UNITY_EDITOR静态编辑器事件OnChainExecutedInEditor:Editor 窗口日志反馈,零运行时开销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> _panelStackPanel 栈:有序显示/隐藏,支持 Back 行为HUDControllerHP Cell 复用策略:Instantiate仅在数量不足时触发,超出部分SetActive(false)不Destroy- 全事件订阅驱动更新,无
Update()轮询
修复 (TD-45): HandleGameStateChanged 新增 else 分支:离开 Dead 状态时隐藏 _deathScreenRoot;Cutscene 隐藏 HUD 逻辑整合进 else 分支,防止与 Dead 状态的 HUD 逻辑冲突
评分: 9.6/10(修复后)
3.18 World 模块
文件: RoomController, RoomTransition, WorldStateRegistry, SavePoint, MovingPlatform, PuzzleDoor, 及其他
亮点:
WorldStateRegistry(ScriptableObject):统一的Dictionary<WorldObjectCategory, HashSet<string>>存储世界状态,语义化 API(IsCollected,MarkDestroyed,SetFlag等)OnEnable()清空状态:ScriptableObject 的 Domain Reload 安全重置,防止跨 PlayMode 状态残留RoomTransition实现IInteractable:Auto/Manual 两种触发方式统一通过SceneLoadRequest广播SavePoint实现ISaveable:存档点激活状态完整参与保存/加载周期PuzzleDoor极简 PuzzleReceiver 子类:OnActivate/OnDeactivate两行委托 Animancer
评分: 9.7/10
3.19 Support 模块
文件: AccessibilityManager, SpeedrunTimer, AntiSoftlockSystem
亮点:
AccessibilityManager.Apply细粒度差分更新:colorblindChangedflag 仅在色盲模式改变时广播,减少无效 UI 刷新SpeedrunTimer._lastDisplayedSecond整秒防抖:仅当整秒变化时重建显示字符串,避免每帧 GCSpeedrunTimer使用Time.unscaledDeltaTime:HitStop(timeScale < 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-45:UIManager 死亡界面未隐藏
文件: Assets/Scripts/UI/UIManager.cs
问题: HandleGameStateChanged 在 GameStates.Dead 时设置 _deathScreenRoot.SetActive(true),但无任何路径将其设为 false。当玩家通过复活/传送离开 Dead 状态(进入 Gameplay、MainMenu 等)时,死亡界面永久残留于屏幕。
修复: 在 else 分支中无条件调用 _deathScreenRoot.SetActive(false),同时将 Cutscene 的 HUD 隐藏逻辑整合进 else 分支,保持逻辑清晰。
TD-46:BGMController 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-47:TutorialManager 未注册 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.1(TD-47 修复消除架构不一致) |
| 健壮性 | 9.7 | 9.8 | ↑ +0.1(TD-45 修复死亡界面逻辑缺陷) |
| 可读性 | 9.7 | 9.8 | ↑ +0.1(TD-46 修复代码意图清晰化) |
综合得分: 9.83 / 10 ↑(v18: 9.77)
六、遗留建议(非必须,后续迭代参考)
S5:EventChainManager 改用 ISaveable 模式
优先级: 低
描述: 当前 EventChainManager.Awake() 通过 ISaveService.GetCompletedChains() 恢复已完成链,存在 SaveManager 异步加载时序风险。建议改为实现 ISaveable 并通过 ISaveableRegistry 触发 OnLoad。
S6:BGMController PlayVictoryThenRestore null 警告
优先级: 极低
描述: _config.VictoryStingBGM 为 null 时静默跳过无警告,与区域 BGM / Boss BGM 缺失的处理方式不一致。可添加一行 if (_config.VictoryStingBGM == null) Debug.LogWarning(...) 使配置错误更易发现。
七、全局架构总结
经过 v1–v19 的系统性审查(涵盖全部 ~280+ 文件),框架已达到成熟商业质量:
- 零耦合数据流: SO 事件频道 + ServiceLocator,所有模块边界均通过接口交互,无跨程序集直接引用
- RAII 订阅管理:
CompositeDisposable+EventSubscription.AddTo全面覆盖,零事件泄漏 - 零 GC 热路径:
DamageInfostruct + Builder、_activeSkills固定数组快照、SFX 轮转池、MaterialPropertyBlock、WaitForSeconds 缓存等多项措施 - ISaveable 模式统一: 所有持久化组件均实现
ISaveable并通过ISaveableRegistry自注册(修复 TD-47 后完全一致) - 框架纯净性: 无向下兼容填充、无 PlayerPrefs 散点、无 FindWithTag 全场景扫描(均通过事件注入玩家引用)
- 编辑器安全:
[DefaultExecutionOrder]正确标注所有基础服务,SOOnEnable重置运行时状态
历史累计 TD 修复: 47 个
最终评分: 9.83 / 10