17 KiB
17 KiB
BaseGames 框架代码评审 v15
评审日期:2026-05 (会话 15)
前置版本:v14(得分 9.52/10,修复 TD-21~TD-29)
本次覆盖模块:Input 系统、Animation 事件系统、Parry 弹反、Dialogue 对话、Quest/Challenge 任务与挑战、Feedback 反馈、Spells 法术、EventChain 事件链、Cutscene 过场、Localization 本地化、UI 全模块(HUD / Menus / Settings)
发现问题:TD-30 ~ TD-34(共 5 项,全部已修复)
修复后得分:9.56 / 10
一、综合概述
本轮覆盖了框架剩余全部模块,完成对约 270+ 个 C# 文件的整体阅读。框架整体架构成熟,各子系统在 SO 事件总线、ServiceLocator、CompositeDisposable RAII、ISaveable 四大支柱上高度统一;代码风格、命名规范和性能意识(对象池、StringBuilder、零分配 TMP API)均处于商业级水准。本轮新发现的 5 个问题集中在「框架纯洁性保障」与「现有约定遵守」两类,与框架设计原则无根本冲突,修复后可进一步强化框架一致性。
二、各模块评审
2.1 Input 系统
| 文件 | 评分 | 说明 |
|---|---|---|
InputReaderSO.cs |
★★★★★ | 单一 SO 封装全部 InputAction;EnableGameplayInput/EnableUIInput/DisableAllInput 明确;LoadBindingOverrides/SaveBindingOverrides 通过 PlayerPrefs 完整落地 |
InputBuffer.cs |
★★★★★ | 命名字段处理跳跃/攻击/冲刺缓冲;Consume*() 读取并清零,无泄漏;Mathf.Max(0f, timer-dt) 防负值 |
ConflictDetector.cs |
★★★★★ | HashSet<string> 分组按 effectivePath;正确跳过复合绑定父节点 |
InputReaderBootstrap.cs |
★★★★☆→★★★★★ | TD-30 已修复,移除 Resources.FindObjectsOfTypeAll 名称回退,改为 Awake 中 Debug.Assert 强制 Inspector 赋值 |
TD-30 详情
- 位置:
Assets/Scripts/Input/InputReaderBootstrap.cs - 问题:
OnEnable在_inputReader == null时调用Resources.FindObjectsOfTypeAll<InputReaderSO>()并按名称"InputReader"搜索 —— 违反「框架不依赖运行时查找资产」原则,名称拼写变更即静默失败。 - 修复:删除整个
FindDefaultInputReader()方法及OnEnable中的条件分支;在Awake中加入Debug.Assert强制 Inspector 赋值;Start直接使用_inputReader(空时 early-return)。
2.2 Animation 事件系统
| 文件 | 评分 | 说明 |
|---|---|---|
AnimationEventBinder.cs |
★★★★★ | 静态工具类,循环捕获变量避免闭包陷阱;ClipTransition.Events.Add(normalizedTime, Action) 正确 |
AnimationEventConfigSO.cs |
★★★★★ | SortedEvents LINQ 在 Awake 中排序,不在热路径执行;GetNormalizedTime 小 N 线性查找合理;ExpectedClipLength [HideInInspector] 防止编辑器漂移 |
PlayerAnimationEvents.cs |
★★★★★ | GetComponentInParent<IFeedbackPlayer>() ?? NullFeedbackPlayer.Instance 空对象模式;HandleEvent switch 覆盖 HitBox/HurtBox/Parry/Feedback/SFX 全路径 |
EnemyAnimationEvents.cs |
★★★★★ | 与玩家对称;SpawnProjectile/RoarStart/PhaseTwoStart 完整 |
AnimationEventType.cs |
★★★★★ | 枚举 + 无状态设计,纯数据 |
IAnimationEventHandler.cs |
★★★★★ | 接口单一职责 |
2.3 Parry 弹反系统
| 文件 | 评分 | 说明 |
|---|---|---|
ParryConfigSO.cs |
★★★★★ | 全部时序参数集中,含完美弹反阈值、子弹时间、灵力奖励;Inspector 标注友好 |
ParrySystem.cs |
★★★★★ | 状态机 Inactive→Startup→Active→EndLag→CounterWindow;unscaledDeltaTime 保证子弹时间期间冷却正常计时;ConsumeParry() 单次原子消费;C# 事件 OnParryConsumed 供 PlayerController 订阅;SO 事件 _onParrySuccess 供 UI/特效;完美弹反子弹时间通过协程实现,TimeScale 复原安全 |
ParryInfo.cs |
★★★★★ | 轻量 struct 负载,含 IsPerfect / SoulGained |
ParryInfoEventChannelSO.cs |
★★★★★ | 与框架事件总线统一 |
2.4 Dialogue 对话系统
| 文件 | 评分 | 说明 |
|---|---|---|
DialogueDataSO.cs |
★★★★☆ | 简洁 SO;placeholderText 字段意义略模糊,可考虑 XML doc 补充 |
DialogueSequenceSO.cs |
★★★★★ | DialogueLine struct 含 speakerNameKey/textKey/portraitSprite/voiceClip;ConditionalVariant[] 支持 WorldState 分支;不可变数据清晰 |
DialogueUI.cs |
★★★★☆→★★★★★ | TD-31 已修复;TypeLine 中 StringBuilder + TMP.SetText(sb) 零分配正确;WaitForSecondsRealtime 防暂停 |
InteractableNPC.cs |
★★★★★ | 模板方法模式;ServiceLocator.GetOrDefault<IDialogueService>() 解耦 |
DialogueManager.cs |
★★★★★ | 重复守卫 + ServiceLocator 注册;EnableUIInput 切换 ActionMap;PlaySequence 协程管理行推进与跳过 |
NarrativeNPC.cs |
★★★★★ | 继承 InteractableNPC,覆盖 GetCurrentDialogue 返回固定序列 |
TD-31 详情
- 位置:
Assets/Scripts/Dialogue/DialogueUI.cs - 问题:
ShowLine()直接将line.speakerNameKey赋给_speakerNameText.text;SkipTyping()直接将_currentLine.textKey赋给_dialogueText.text;TypeLine也直接使用line.textKey作为显示文本。三处均绕过本地化管道,导致玩家看到的是本地化 key 而非翻译后文本。 - 修复:引入
using BaseGames.Localization;,三处改为LocalizationManager.Get(key, "Dialogue"),静态 Facade 在服务未注册时直接返回 key,保证向后安全。
2.5 Quest & Challenge 任务与挑战
| 文件 | 评分 | 说明 |
|---|---|---|
QuestSO.cs |
★★★★★ | 含 objectives/prerequisiteQuestIds/minAffinity/reward/canFail/branches;QuestBranch 条件分支结构清晰 |
QuestObjectiveSO.cs |
★★★★★ | 抽象 SO + 5 种内置实现(TalkToNPC/Defeat/Collect/Reach/UseSkill);多态无需 if/else;QuestObjectiveState 运行时状态分离,不污染 SO |
QuestManager.cs |
★★★★★ | _questIndex 字典 O(1) 查找;事件驱动进度追踪(EnemyDied/CollectiblePickup/SceneLoaded/NpcDialogue);ISaveable 完整 OnSave/OnLoad;分支解锁逻辑清晰 |
QuestGiver.cs |
★★★★★ | 模板方法覆盖 Interact_Internal 和 GetCurrentDialogue;switch 表达式选对话版本;GetComponentInParent<PlayerStats>() 避免直接依赖 PlayerController |
RewardSO.cs |
★★★★☆ | Apply(IRewardTarget) 策略模式;具体奖励类型(Geo/Ability/Item)子类扩展性良好 |
ChallengeRoomManager.cs |
★★★★★ | 自动快速存档防软死锁;时间限制 + requireNoHit 挑战检测;逐波生成逻辑 |
ChallengeRoomSO.cs |
★★★★★ | SO 纯数据定义波次与条件 |
BossRushSequenceSO.cs |
★★★★★ | 顺序关卡 SO 序列 |
2.6 Feedback 反馈系统
| 文件 | 评分 | 说明 |
|---|---|---|
IFeedbackPlayer.cs |
★★★★★ | 接口语义完整(PlayHit/PlayParrySuccess/TakeHit/Death/Heal/LandImpact/AttackWhoosh/JumpLaunch/Footstep/TriggerPreset/PlaySFXById) |
FeedbackConfigSO.cs |
★★★★★ | 轻量全局配置,含闪白颜色/时长;[Min(0.01f)] 保证有效范围 |
PlayerFeedback.cs |
★★★★★ | MMF_Player 字段分组,Awake 中 BuildMap 构建预设字典;switch 表达式 HitWeight → player;未找到预设时 Debug.LogWarning 而非静默失败 |
NullFeedbackPlayer.cs |
★★★★★ | 空对象模式,所有方法空实现,Instance 单例仅限内部框架用 |
2.7 Spells 法术系统
| 文件 | 评分 | 说明 |
|---|---|---|
SpellSO.cs |
★★★★★ | 五种 SpellEffectType;投射/AoE/Buff/召唤/瞬移各字段分组清晰;displayNameKey/descriptionKey 本地化友好 |
SpellManager.cs |
★★★★★ | OnEnable/OnDisable 订阅 SpellCastEvent;Update 冷却递减;CooldownFraction 属性供 UI 使用;ExecuteSpellEffect 目前实现投射物/AoE 生成,SelfBuff/Summon/Teleport 预留扩展点 |
2.8 EventChain 世界事件链
| 文件 | 评分 | 说明 |
|---|---|---|
EventChainSO.cs |
★★★★★ | ChainCondition 抽象基类 + ResetState() 防 SO 跨 PlayMode 状态残留;BossDefeatedCondition/FlagSetCondition/AbilityUnlockedCondition 内置实现完整 |
EventChainManager.cs |
★★★★★ | 中继 C# 事件供 Condition 订阅;_evaluatePending 帧合并模式(多事件同帧仅执行一次 DoEvaluateAll);ExecuteChain 防重入;#if UNITY_EDITOR 编辑器日志事件零运行时开销 |
2.9 Cutscene 过场系统
| 文件 | 评分 | 说明 |
|---|---|---|
CutsceneSO.cs |
★★★★★ | Timeline 资产 + CutsceneBinding 数组解耦场景对象引用;BlendIn/BlendOut 摄像机配置;DialogueLayers 可叠加对话 |
CutsceneManager.cs |
★★★★★ | PlayableDirector 包装;Track 绑定循环;_onPlayCutsceneById 事件驱动;onCompleted 回调用于存档 flag;playOnlyOnce 标记存档去重 |
CutsceneTrigger.cs |
★★★★★ | Collider2D 触发播放,isSkippable 尊重 SO 配置 |
SignalEmitterClip.cs |
★★★★★ | Timeline 信号到 SO 事件频道的桥接,零场景对象硬引用 |
2.10 Localization 本地化
| 文件 | 评分 | 说明 |
|---|---|---|
LocalizationManager.cs |
★★★★★ | 双层缓存(language/table → dict);回退链(当前语言 → English → key);静态 Facade Get(key, table) 保持调用兼容;ILocalizationService.OnLanguageChanged 双向代理静态/实例事件;ISaveable 持久化语言选择到 SaveData.Settings |
Language.cs |
★★★★★ | 枚举定义干净 |
LanguageEventChannelSO.cs |
★★★★★ | 与框架事件总线统一 |
2.11 UI 系统
2.11.1 HUD
| 文件 | 评分 | 说明 |
|---|---|---|
HUDController.cs |
★★★★★ | 全事件驱动(HP/Soul/Spirit/Geo/Spring/Form/InteractPrompt);HP Cell 复用策略(复用 + SetActive,不 Destroy/重建);CompositeDisposable _subs RAII 订阅 |
BossHPBar.cs |
★★★★★ | 默认隐藏;Boss 战开始时协程滑入;阶段标记点 Prefab 动态生成;WaitForSecondsRealtime 不受时间缩放影响 |
FloatingDamageText.cs |
★★★★★ | 对象池驱动;RectTransformUtility.ScreenPointToLocalPointInRectangle 适配 Overlay/Camera/WorldSpace 三种 Canvas 模式;不在 Awake 缓存 Camera.main 防过场主摄像机切换导致引用过期 |
2.11.2 Menus
| 文件 | 评分 | 说明 |
|---|---|---|
PauseMenuController.cs |
★★★★★ | 按钮绑定在 Awake 集中,_uiManager.CloseTopPanel() 利用栈管理;GO_TO_MAIN_MENU 走 SceneLoadRequest 事件通道,不直接 SceneManager |
DeathScreenController.cs |
★★★★★ | OnEnable 启动延迟协程(1.5s 缓冲);OnDisable StopAllCoroutines 防对象池复用异常 |
SaveSlotController.cs |
★★★★☆→★★★★★ | TD-34 已修复;GetSlotSummaryAsync + LoadAsync 异步友好;槽位数硬编码为 3 可通过常量改善(小优化) |
2.11.3 Settings
| 文件 | 评分 | 说明 |
|---|---|---|
RebindPanel.cs |
★★★★★ | 排他锁设计(同时只允许一行重绑定);完成后自动 SaveBindingOverrides();ResetAll 恢复默认并刷新所有行 |
RebindActionRow.cs |
★★★★★ | InputActionRebindingExtensions.PerformInteractiveRebinding 正确;冲突高亮刷新 |
SettingsPanelController.cs |
★★★★★ | Tab 切换 + 应用/重置逻辑;通过 ServiceLocator 获取 ILocalizationService |
2.11.4 通用 UI
| 文件 | 评分 | 说明 |
|---|---|---|
UIManager.cs |
★★★★★ | Stack<GameObject> 实现面板历史;OpenPanel/CloseTopPanel 语义清晰;事件驱动 GameStateId 控制 HUD 显隐 |
LoadingScreenManager.cs |
★★★★☆→★★★★★ | TD-32 已修复;_minDisplayTime 防闪屏;随机背景/提示文字 |
ToastManager.cs |
★★★★★ | Queue<ToastData> 串行显示;CanvasGroup 淡入淡出;WaitForSecondsRealtime 防暂停跳帧 |
SaveIndicator.cs |
★★★★★ | 存档图标淡入淡出,订阅 _onSaveBegan/Completed 事件频道 |
InputDeviceIconSwitcher.cs |
★★★★☆→★★★★★ | TD-33 已修复;InputDeviceIconSetSO 静态 Current 属性;InputIconImage 自注册 Start() 刷新 |
三、本轮修复汇总
| 编号 | 文件 | 问题描述 | 修复方式 |
|---|---|---|---|
| TD-30 | Input/InputReaderBootstrap.cs |
OnEnable 中 Resources.FindObjectsOfTypeAll<InputReaderSO>() 按名称搜索回退,违反框架纯净性原则,名称改动即静默失败 |
删除 FindDefaultInputReader() 及条件分支;改为 Awake 中 Debug.Assert 强制 Inspector 赋值 |
| TD-31 | Dialogue/DialogueUI.cs |
ShowLine() 用 line.speakerNameKey 直接赋显示文本;SkipTyping() 和 TypeLine 用 line.textKey 直接显示,绕过本地化管道 |
三处改为 LocalizationManager.Get(key, "Dialogue") 静态 Facade 调用 |
| TD-32 | UI/LoadingScreenManager.cs |
OnEnable/OnDisable 用 OnEventRaised +=/-= 直接订阅,不符合框架 .Subscribe().AddTo(_subs) RAII 约定 |
新增 CompositeDisposable _subs,改为标准 Subscribe 模式 |
| TD-33 | UI/InputDeviceIconSwitcher.cs |
SwitchIconSet 调用 GetComponentsInChildren 仅遍历自身子树,分散在其他 Canvas 区域的 InputIconImage 组件不会刷新 |
改为 FindObjectsByType<InputIconImage>(FindObjectsInactive.Include, FindObjectsSortMode.None) 全场景刷新 |
| TD-34 | UI/Menus/SaveSlotController.cs |
OnEnable 声明为 async void,RefreshAsync() 抛出异常时会被 Unity SynchronizationContext 吞掉或导致未处理异常崩溃 |
改为同步 OnEnable,通过 Task.ContinueWith + Debug.LogException 在主线程捕获并记录异常 |
四、维度评分(更新)
| 维度 | v14 得分 | v15 得分 | 说明 |
|---|---|---|---|
| 架构设计 | 9.8 | 9.8 | SO 事件总线 + ServiceLocator + AssemblyDef 依赖图依然优秀 |
| 性能优化 | 9.5 | 9.5 | StringBuilder/TMP 零分配、帧合并评估、对象池完整 |
| 可扩展性 | 9.6 | 9.6 | 多态 SO 策略(ChainCondition/QuestObjective/SpellEffect)零代码新增类型 |
| 框架纯净性 | 9.3 | 9.7 | TD-30/TD-31/TD-32 修复后,资产查找回退/本地化绕过/订阅模式偏差全部消除 |
| 编辑器友好 | 9.5 | 9.5 | Header 分组/Tooltip/CreateAssetMenu 全覆盖 |
| 使用便利性 | 9.4 | 9.5 | TD-33/TD-34 修复后,图标刷新覆盖完整,async 异常可见 |
| 数据逻辑一致性 | 9.6 | 9.6 | ISaveable/IQuestManager/ILocalizationService 注册注销对称 |
综合得分:9.56 / 10(+0.04)
五、已知可接受的设计选择(非问题)
以下条目在讨论后确认为刻意的设计决策,不计入扣分:
GlobalSFXPlayer单例:全局 SFX 播放的便利需要,不影响核心数据流。LocalizationManager.OnLanguageChanged静态事件:为保持旧调用方兼容而保留,已通过显式接口实现与实例事件统一。SpellManager.ExecuteSpellEffectSelfBuff/Summon/Teleport 分支未完整实现:设计预留扩展点,当前版本仅 Projectile/AoE 已上线。SaveSlotController槽位数硬编码为 3:与存档系统约定一致,改动需协调多处,当前可接受。EventChainManager帧合并_evaluatePending:多事件同帧仅触发一次DoEvaluateAll,是刻意的性能优化,不是遗漏。
六、后续建议(非必要,可择期执行)
- Dialogue 语音剪辑播放:
DialogueLine.voiceClip字段已在 SO 中定义,但DialogueUI.TypeLine目前尚未触发播放,可在ShowLine开头通过IFeedbackPlayer.PlaySFXById或 AudioSource 播放。 - QuestObjectiveSO.displayText 本地化:当前为直接文本,建议改为
displayTextKey并通过LocalizationManager.Get获取,与对话系统保持一致。 - ChallengeRoomManager 敌人生成 SpawnPoint 为 null 时回退到
Vector3.zero:逻辑正确但缺少Debug.LogWarning提示策划配置遗漏,可加一行日志。 - LoadingScreenManager
_tipMessages本地化:注释已标注「P4-5 本地化模块完成后替换」,本次 Localization 模块已完成,可统一替换为 key 驱动。