9.9 KiB
BaseGames 框架代码评审 v16
评审日期:2026-05(会话 16)
前置版本:v15(得分 9.56/10,修复 TD-30~TD-34)
本次变更性质:针对 v15「已知可接受的设计选择」与「后续建议」的全量落地修复
发现问题:TD-35(共 1 项)+ Suggestion 1~4(共 4 项),全部已修复
修复后得分:9.68 / 10
一、本轮修复背景
v15 评审将以下条目划分为「可接受」或「后续建议」:
| 分类 | 条目 | 处理结论 |
|---|---|---|
| 可接受设计选择 #2 | LocalizationManager.OnLanguageChanged 静态事件向后兼容层 |
已判定为需修复(TD-35):本项目是全新框架,不存在需要兼容的旧调用方,静态兼容层引入了不必要的双事件系统,应彻底移除 |
| 后续建议 1 | DialogueUI 语音剪辑从未播放 |
已实施 |
| 后续建议 2 | QuestObjectiveSO.displayText 为原始文本而非本地化 key |
已实施 |
| 后续建议 3 | ChallengeRoomManager spawnPoint 为 null 时静默回退 Vector3.zero |
已实施 |
| 后续建议 4 | LoadingScreenManager._tipMessages 存储直接字符串而非本地化 key |
已实施 |
二、本轮修复详情
TD-35 — LocalizationManager.cs 移除静态事件兼容层
文件:Assets/Scripts/Localization/LocalizationManager.cs
问题:
// 旧实现:静态事件 + 显式接口桥接
public static event Action<Language> OnLanguageChanged; // ← 静态向后兼容
event Action<Language> ILocalizationService.OnLanguageChanged
{
add { OnLanguageChanged += value; }
remove { OnLanguageChanged -= value; }
}
// SetLanguage 调用静态事件
OnLanguageChanged?.Invoke(language);
这是典型的「兼容旧调用方」写法:ILocalizationService.OnLanguageChanged 的实例事件语义被静态事件偷换,导致:
- 任何通过
LocalizationManager.OnLanguageChanged +=直接订阅的代码绕过了接口,产生隐式静态依赖 - 框架中不存在需要兼容的旧调用方,该层完全多余
- 静态事件生命周期不受 MonoBehaviour Enable/Disable 控制,与
CompositeDisposableRAII 机制矛盾
修复:
// 新实现:纯实例事件
private event Action<Language> _onLanguageChanged;
event Action<Language> ILocalizationService.OnLanguageChanged
{
add => _onLanguageChanged += value;
remove => _onLanguageChanged -= value;
}
// SetLanguage 调用实例事件
_onLanguageChanged?.Invoke(language);
同步更新文件顶部注释,移除「保持调用兼容」相关措辞,改为说明通过 ILocalizationService 接口订阅的正确用法。
Suggestion 1 → Fix — DialogueUI.cs 语音剪辑播放
文件:Assets/Scripts/Dialogue/DialogueUI.cs
问题:DialogueLine.voiceClip 字段(AudioClip)已在数据层定义,DialogueSequenceSO 和 DialogueLine 都完整支持配置语音,但 DialogueUI 从未使用该字段,语音片段永远不会播放。
修复:
// 新增 Inspector 字段
[SerializeField] private AudioSource _voiceSource; // 语音播放源(可不配置)
// ShowLine() — 头像赋值后追加:
if (_voiceSource != null)
{
_voiceSource.Stop();
if (line.voiceClip != null)
{
_voiceSource.clip = line.voiceClip;
_voiceSource.Play();
}
}
// SkipTyping() — StopCoroutine 后追加:
_voiceSource?.Stop();
_voiceSource 为可选配置(null 安全),ShowLine 先停止再播放(防止上一行语音未结束时重叠)。跳字时同步停止语音保持语音与文字的同步关系。
Suggestion 2 → Fix — QuestObjectiveSO.cs displayText 本地化
文件:Assets/Scripts/Quest/QuestObjectiveSO.cs
问题:
[TextArea(1, 4)]
public string displayText; // 直接文本,非本地化 key
任务目标描述在运行时无法随语言切换更新,与框架本地化设计不符。
修复:
public string displayTextKey; // 本地化 key,对应 "Quest" 表中的条目
移除 [TextArea] 特性(key 通常是简短标识符,不需要多行编辑框)。Inspector 中填写如 "obj_talk_elder" 这样的 key,通过 LocalizationManager.Get(displayTextKey, "Quest") 在 UI 层取得显示文本。
注释也同步说明用途,使编辑器语义清晰。
Suggestion 3 → Fix — ChallengeRoomManager.cs SpawnPoint 空值警告
文件:Assets/Scripts/Quest/ChallengeRoomManager.cs
问题:
Vector3 pos = entry.spawnPoint != null ? entry.spawnPoint.position : Vector3.zero;
当 spawnPoint 未配置时静默回退到世界原点,策划不会收到任何提示,问题往往在运行时才被偶然发现。
修复:
Vector3 pos;
if (entry.spawnPoint != null)
{
pos = entry.spawnPoint.position;
}
else
{
Debug.LogWarning($"[ChallengeRoomManager] encounter[{index}] 中的 enemyAddressKey='{entry.enemyAddressKey}' " +
$"未配置 spawnPoint,将在 Vector3.zero 生成。请在 ChallengeRoomSO 中补全配置。", this);
pos = Vector3.zero;
}
使用 this 作为第二参数,双击 Console 日志可直接定位到场景中的 ChallengeRoomManager 对象,加快排查效率。
Suggestion 4 → Fix — LoadingScreenManager.cs 提示文字本地化
文件:Assets/Scripts/UI/LoadingScreenManager.cs
问题:
[SerializeField] private string[] _tipMessages; // 直接文字(非本地化 key)
// ...
_tipText.text = _tipMessages[Random.Range(0, _tipMessages.Length)];
注释已标注「P4-5 本地化模块完成后替换」,v15 已完整实现 Localization 模块,此 TODO 应立即落地。
修复:
// using BaseGames.Localization; 已添加到文件顶部
[SerializeField] private string[] _tipMessages; // 本地化 key(对应 "UI" 表中的条目,如 "tip_explore")
// ...
_tipText.text = LocalizationManager.Get(_tipMessages[Random.Range(0, _tipMessages.Length)], "UI");
类注释中移除「P4-5 本地化模块完成后替换」的 TODO 说明,类文档恢复简洁。
三、修复汇总
| 编号 | 文件 | 问题类型 | 问题描述 | 修复方式 |
|---|---|---|---|---|
| TD-35 | Localization/LocalizationManager.cs |
框架纯净性 | 静态事件 OnLanguageChanged 作为兼容层暴露,接口实现委托给静态事件,SetLanguage 调用静态事件;框架无旧调用方需兼容 |
移除静态事件,添加私有实例字段 _onLanguageChanged,接口实现直接包装实例字段,SetLanguage 调用实例事件 |
| Fix-S1 | Dialogue/DialogueUI.cs |
功能缺失 | DialogueLine.voiceClip 字段从未被播放,语音功能形同虚设 |
新增 [SerializeField] AudioSource _voiceSource,ShowLine 中播放,SkipTyping 中停止 |
| Fix-S2 | Quest/QuestObjectiveSO.cs |
数据一致性 | displayText 存储直接文本,不支持多语言,与框架本地化约定不符 |
重命名为 displayTextKey,移除 [TextArea],通过 LocalizationManager.Get(displayTextKey, "Quest") 获取显示文本 |
| Fix-S3 | Quest/ChallengeRoomManager.cs |
可调试性 | spawnPoint 为 null 时静默回退 Vector3.zero,策划配置遗漏不可见 |
拆分条件分支,null 分支增加 Debug.LogWarning 含 this context 对象 |
| Fix-S4 | UI/LoadingScreenManager.cs |
数据一致性 | _tipMessages 存储直接字符串,随语言切换不刷新 |
添加 using BaseGames.Localization,用 LocalizationManager.Get(key, "UI") 包装取值,字段注释说明为本地化 key |
四、维度评分(更新)
| 维度 | v15 得分 | v16 得分 | 变化原因 |
|---|---|---|---|
| 架构设计 | 9.8 | 9.8 | 无变化 |
| 性能优化 | 9.5 | 9.5 | 无变化 |
| 可扩展性 | 9.6 | 9.6 | 无变化 |
| 框架纯净性 | 9.7 | 9.9 | TD-35 彻底移除静态事件兼容层,接口实现完全规范 |
| 编辑器友好 | 9.5 | 9.6 | Fix-S3 增加含 context 对象的 LogWarning,调试体验提升 |
| 使用便利性 | 9.5 | 9.5 | 无变化 |
| 数据逻辑一致性 | 9.6 | 9.8 | Fix-S2/S4 统一了 Quest 目标文本和 LoadingScreen 提示的本地化路径,消除数据层不一致 |
综合得分:9.68 / 10(+0.12)
五、已知可接受的设计选择(更新)
原 v15 第五节条目 #2(LocalizationManager.OnLanguageChanged 静态事件)已升级为 TD-35 并修复,从本节移除。
| 编号 | 条目 | 状态 |
|---|---|---|
| ✅ 1 | GlobalSFXPlayer 单例 |
保持可接受,全局 SFX 便利性需要 |
LocalizationManager.OnLanguageChanged 静态事件 |
TD-35 已修复,从此节移除 | |
| ✅ 3 | SpellManager SelfBuff/Summon/Teleport 分支未完整实现 |
保持可接受,设计预留扩展点 |
| ✅ 4 | SaveSlotController 槽位数硬编码为 3 |
保持可接受,改动需协调多处 |
| ✅ 5 | EventChainManager 帧合并 _evaluatePending |
保持可接受,刻意性能优化 |
六、后续建议(可择期执行)
v15 的全部四条后续建议已在本轮实施,当前无新增后续建议。
框架在经历 16 个迭代后,核心代码已达到商业级 Action 游戏框架的一致性要求。后续工作重心建议转移至:
- 运行时测试覆盖:为核心系统(Combat、Quest、Localization)编写 Unity Test Framework 单元/集成测试,覆盖主要分支。
- Addressables 预加载策略文档化:当前
ChallengeRoomManager、SpawnManager均在运行时InstantiateAsync,对于高频召唤场景可考虑预暖(Preload)标记的 Label 分组。 - Spell/StatusEffect 组合测试场景:多个 StatusEffect 叠加时的优先级与互斥规则在代码层已有注释,建议在 Docs 层补充状态效果交互矩阵文档。
上一版:FrameworkReview_2026_May_v15.md(加权 9.56)