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

19 KiB
Raw Blame History

Framework Review — 2026 May v14

覆盖范围v13 之后新增模块的全面评审v13 已宣告 100% 覆盖率 9.45/10本次审查本轮新增代码
修复统计:共发现并修复 9 处问题TD-21 ~ TD-29
最终评分9.52 / 10


一、新增模块概览

本次 v14 审查覆盖以下 v13 之后新增的模块:

模块 路径 文件数
Audio — 脚步音效系统 Assets/Scripts/Audio/Footstep* 3
Audio — 水下音效控制器 Assets/Scripts/Audio/UnderwaterAudioController.cs 1
Tutorial 教程系统 Assets/Scripts/Tutorial/ 4
Support — Accessibility 无障碍 Assets/Scripts/Support/Accessibility/ 3
Support — Analytics 数据埋点 Assets/Scripts/Support/Analytics/ 1
Support — AntiSoftlock 反卡关 Assets/Scripts/Support/AntiSoftlock/ 3
Support — Speedrun 速通计时器 Assets/Scripts/Support/Speedrun/ 1
World — Liquid 液态区域系统 Assets/Scripts/World/Liquid/ 5
World — Puzzle 谜题系统 Assets/Scripts/World/Puzzle/ 4
World — PhantomInteractable Assets/Scripts/World/PhantomInteractable.cs 1
World — WorldMarker Assets/Scripts/World/WorldMarker*.cs 2

二、各模块详细评审

2.1 Audio — 脚步音效系统

涉及文件

  • FootstepMaterial.cs — 枚举Stone/Dirt/Wood/Metal/Water/Sand/Grass/Cave
  • FootstepMaterialMarker.cs — Marker MonoBehaviour挂在地面 Collider 上打标签
  • FootstepAudioConfigSO.cs — SO按材质映射 AudioClip[] + volume + pitchVariance

优点

  • 数据驱动SO 配置),场景策划无需碰代码
  • FootstepMaterialMarker 轻量,仅携带枚举值,无运行时逻辑
  • GetEntry(FootstepMaterial) 返回 MaterialEntry?,正确使用可空值类型,避免引用判空

评分

维度 分数 说明
架构设计 9.5 数据/标记/配置分离,职责清晰
性能 9.5 纯数据查询,无分配
可扩展性 9.0 增加材质只需扩展枚举 + SO 条目
编辑器友好 9.0 SO 有 Header 分组
使用便利性 9.0 三件套装配直观

2.2 Audio — UnderwaterAudioController

涉及文件UnderwaterAudioController.cs

优点

  • 正确使用 CompositeDisposable 管理事件订阅,零内存泄漏
  • 事件驱动,与 LiquidZone 完全解耦

修复 TD-24已修复
OnLiquidExited 原实现无 LiquidType 过滤:

// Before — 任何液体离开都会清除水下快照
private void OnLiquidExited(LiquidEvent evt) => BlendVolume(0f, _blendOutDuration);

// After — 仅 Water 类型触发
private void OnLiquidExited(LiquidEvent evt)
{
    if (evt.LiquidType != LiquidType.Water) return;
    BlendVolume(0f, _blendOutDuration);
}

遗留设计说明TD-23
GlobalSFXPlayer 使用私有静态单例 _instance 而非 ServiceLocator。对于全局 SFX 入口而言,静态工具类是业界常见模式,但与框架其余部分的 ServiceLocator 注入风格不一致。当前已通过 Play() 内部委托 ServiceLocator.GetOrDefault<IAudioService>() 处理 3D 播放,保持了对 IAudioService 的接口依赖。
结论:不修改,记录为架构风格差异,可在未来重构时统一。

评分

维度 分数 说明
架构设计 9.0 已修复过滤缺失
性能 9.5 AudioMixer.FindSnapshot 在 OnEnable 时调用,不在热路径
可扩展性 8.5 快照名建议收进常量类TD-24 记录)
编辑器友好 9.0
使用便利性 9.0

2.3 Tutorial 教程系统

涉及文件

  • ITutorialService.cs — ServiceLocator 接口
  • TutorialManager.csISaveable,管理已完成提示 ID持久化到 SaveData
  • TutorialHintUI.cs — TMP_Text 面板 + 自动隐藏 Coroutine
  • ContextualHintTrigger.cs — 触发器区域,带能力门控,单次触发后 SetActive(false)

优点

  • 通过 ITutorialService + ServiceLocator 完全解耦
  • ISaveable 集成确保跨 Session 记忆已完成提示
  • ContextualHintTriggergameObject.SetActive(false) 方式实现"仅触发一次"简洁高效,避免额外状态字段

注意点

  • TutorialHintUI 的自动隐藏 Coroutine 在场景切换时若未清理可能报错;但由于 HintUI 通常与场景生命周期绑定,可接受

评分

维度 分数 说明
架构设计 9.5 接口隔离 + ISaveable 集成完整
性能 9.5 无热路径分配
可扩展性 9.0 扩展提示类型只需继承/配置
编辑器友好 9.5
使用便利性 9.5

2.4 Support — Accessibility 无障碍系统

涉及文件

  • AccessibilitySettingsSO.cs
  • ColorBlindFilter.csScriptableRendererFeature
  • AccessibilityManager.cs

修复 TD-21AccessibilitySettingsSO 全局命名空间(已修复)

// Before — 无命名空间
public class AccessibilitySettingsSO : ScriptableObject { ... }

// After
namespace BaseGames.Support.Accessibility
{
    public class AccessibilitySettingsSO : ScriptableObject { ... }
}

修复 TD-22ColorBlindFilter 全局命名空间(已修复)

// Before — 无命名空间
public class ColorBlindFilter : ScriptableRendererFeature { ... }

// After
namespace BaseGames.Support.Accessibility
{
    public class ColorBlindFilter : ScriptableRendererFeature { ... }
}

架构评注 — ColorBlindFilter 生命周期
ColorBlindFilter 继承自 ScriptableRendererFeature(本质是 ScriptableObject)。使用 OnEnable()/OnDisable() 管理事件订阅是合理的SO 的 OnEnable 在编辑器加载和运行时都会触发,行为可预期。CompositeDisposable _subs 跨 Play/Edit 切换保持干净。

优点

  • 色盲矩阵基于 Brettel/Viénot 标准,强度插值支持过渡
  • AccessibilityManager 通过 ServiceLocator 注册,与其他服务一致
  • PlayerPrefs 用于无障碍设置持久化(合理:无需 SaveData 加密路径)

评分

维度 分数 说明
架构设计 9.0 命名空间修复后对齐
性能 9.5 Shader 矩阵仅在切换时更新
可扩展性 9.0 增加色盲类型只需扩展枚举 + 矩阵
编辑器友好 8.5 RendererFeature 配置在 Renderer Asset 中,不太直观
使用便利性 9.0

2.5 Support — Analytics 数据埋点

涉及文件AnalyticsManager.cs

优点

  • 完全本地(persistentDataPath/analytics.json),无 PII不联网
  • #if !UNITY_EDITOR && !DEVELOPMENT_BUILD 保证仅在正式构建启用
  • 批量队列 + OnApplicationQuit/OnDestroy 刷盘,减少 I/O 频率
  • 预定义 TrackBossKill/TrackPlayerDeath 等方法,防止魔法字符串散布

评分

维度 分数 说明
架构设计 9.5
性能 9.0 JSON 序列化不在热路径
可扩展性 9.5 预定义方法 + 泛化 Track
编辑器友好 9.5 编辑器禁用,零干扰
使用便利性 9.5

2.6 Support — AntiSoftlock 反卡关系统

涉及文件

  • AntiSoftlockSystem.cs
  • HardAbilityGate.cs
  • RoomEscapeInfoSO.cs

修复 TD-25命名空间错误已修复

HardAbilityGateRoomEscapeInfoSO 声明于 namespace BaseGames.Progression 但物理位于 Assets/Scripts/Support/AntiSoftlock/,且同目录的 AntiSoftlockSystem 使用 namespace BaseGames.Support.AntiSoftlock,造成不一致。

// Before
namespace BaseGames.Progression { ... }

// After
namespace BaseGames.Support.AntiSoftlock { ... }

优点

  • AntiSoftlockSystem 订阅 TransformEventChannelSO _onPlayerSpawned 而非 FindFirstObjectByType,保持零耦合
  • HardAbilityGate 通过 SaveManager.Data.World.Switches 二级验证,防范物品伪解锁
  • RoomEscapeInfoSO.priority 支持多路逃脱路径优先级排序

评分

维度 分数 说明
架构设计 9.0 命名空间修复后完整对齐
性能 9.5 卡关检测为低频 Update 定时器
可扩展性 9.0 多路逃脱 SO + 优先级
编辑器友好 9.0
使用便利性 9.5

2.7 Support — SpeedrunTimer 速通计时器

涉及文件SpeedrunTimer.cs

优点

  • Time.unscaledDeltaTime 免受 HitStoptimeScale < 1影响
  • _lastDisplayedSecond 整秒检查跳过字符串重建,避免每帧 GC Alloc
  • ISaveable 集成完整(OnSave/OnLoadStatsSaveData.SpeedrunTime
  • SetVisible 同步通知 BoolEventChannelSOHUD 可响应

格式问题TD-28低优先级
类体在 namespace 块内缺少标准 4 空格缩进,与框架其余文件风格不一致。功能正确,建议下次编辑时顺手修复。

评分

维度 分数 说明
架构设计 9.5
性能 9.5 整秒优化到位
可扩展性 9.0
编辑器友好 9.5
使用便利性 9.5

2.8 World — Liquid 液态区域系统

涉及文件

  • LiquidType.cs — 枚举Water/Acid/Lava
  • LiquidPhysicsConfigSO.cs — 水下物理参数 SO
  • LiquidZone.cs — 触发区域,发送 LiquidEventChannel 事件
  • WaterDangerState.cs — 溺死倒计时Water 无游泳能力时)
  • UnderwaterPostProcessingController.cs — Volume Weight 混合动画

修复 TD-29LiquidZone 无用字段(已修复)
_dealsDrowningDamage/_drowningDamagePerSecond#pragma warning disable CS0414,逻辑从未使用。水下伤害实际由 WaterDangerState 通过事件驱动实现,字段已删除并更新注释。

优点

  • Acid/Lava 伤害委托给独立 HazardZone架构分离LiquidZone 仅负责事件分发
  • WaterDangerState 在进入时检查 PlayerStats.HasAbility(AbilityType.Swim),零侵入 PlayerController
  • UnderwaterPostProcessingController Coroutine 混合,支持被打断(取消前一个)
  • LiquidPhysicsConfigSOWaterVolumeProfile 字段允许每种液体配置独立 Post-Processing Profile

修复 TD-24UnderwaterPostProcessingController已在 2.2 记录

评分

维度 分数 说明
架构设计 9.5 职责分离清晰Zone/Physics/Danger/FX
性能 9.5 无热路径 GCCoroutine 混合合理
可扩展性 9.5 枚举 + SO 扩展成本极低
编辑器友好 9.5 LiquidPhysicsConfigSO 字段注释详尽
使用便利性 9.5

2.9 World — Puzzle 谜题系统

涉及文件

  • PuzzleSwitch.cs — 输入InteractOnce/Toggle/Pressure 触发模式
  • PuzzleWire.cs — 逻辑连接器AND/OR/XOR
  • PuzzleReceiver.cs — 输出:激活目标,持久化到 WorldStateRegistry
  • PuzzleDoor.cs — Receiver 子类Animancer 开关门动画

修复 TD-26PuzzleSwitch/PuzzleReceiver 未从 WorldStateRegistry 恢复存档状态(已修复)

原代码 Start() 仅设置初始值,忽略 WorldStateRegistry 存档:

// Before — PuzzleSwitch
private void Start() => _isActive = _startsActive;   // 忽略存档

// Before — PuzzleReceiver
protected virtual void Start()
{
    _isActivated = _startsActivated;   // 忽略存档
    if (_isActivated) OnActivate();
}

修复方案:将状态恢复移至 Awake()(保证在 PuzzleWire.Start()Evaluate() 之前执行),Start() 仅负责视觉/回调初始化:

// After — PuzzleSwitch
private void Awake()
{
    bool savedState = !string.IsNullOrEmpty(_switchId)
                      && _worldState != null
                      && _worldState.HasFlag("switch_" + _switchId);
    _isActive = savedState || _startsActive;
}

private void Start()
{
    if (_isActive && _activeClip != null) _animancer?.Play(_activeClip);
    else if (_inactiveClip != null)       _animancer?.Play(_inactiveClip);
}

// After — PuzzleReceiver
protected virtual void Awake()
{
    bool savedState = !string.IsNullOrEmpty(_receiverId)
                      && _worldState != null
                      && _worldState.HasFlag("receiver_" + _receiverId);
    _isActivated = savedState || _startsActivated;
}

protected virtual void Start()
{
    if (_isActivated) OnActivate();
}

修复原理Unity 的 Awake() 在所有 Start() 之前完成。PuzzleWire.Start() 调用 Evaluate() 时,所有 PuzzleSwitch.Awake()PuzzleReceiver.Awake() 已执行完毕,状态已正确恢复。PuzzleReceiver.Activate()if (_isActivated) return; 守卫,若 Wire 在 Receiver.Start() 之前求值也不会重复触发 OnActivate()

架构优点

  • PuzzleWire AND/OR/XOR 纯配置,关卡设计师零代码
  • SO 注入 WorldStateRegistry 而非单例,测试友好
  • PuzzleDoor 仅覆写 OnActivate/OnDeactivate,扩展成本极低

评分

维度 分数 说明
架构设计 9.5 修复后状态管理完整
性能 9.5 事件驱动,无 Update 查询
可扩展性 9.5 Receiver 子类化成本极低
编辑器友好 9.5 Wire 逻辑类型枚举直观
使用便利性 9.5

2.10 World — PhantomInteractable

涉及文件PhantomInteractable.cs

修复 TD-27LayerMask.NameToLayer 在热路径(已修复)

// Before — 每次 OnTriggerEnter2D 都调用 string 查询
bool isPhantom = other.gameObject.layer == LayerMask.NameToLayer("PhantomBody");

// After — Awake 缓存
private int _phantomBodyLayer;
private void Awake() => _phantomBodyLayer = LayerMask.NameToLayer("PhantomBody");

private void OnTriggerEnter2D(Collider2D other)
{
    bool isPhantom = other.gameObject.layer == _phantomBodyLayer;
    ...
}

LayerMask.NameToLayer 内部进行字符串哈希查找,在 OnTriggerEnter2D(频繁回调)中每帧调用是无谓的 CPU 消耗。

评分

维度 分数 说明
架构设计 9.5 继承 DirectionalInteractable职责单一
性能 9.5 修复后无热路径分配
可扩展性 9.5
编辑器友好 9.5
使用便利性 9.5

2.11 World — WorldMarker

涉及文件

  • WorldMarker.cs
  • WorldMarkerEventChannelSO.csBaseEventChannelSO<WorldMarker>

优点

  • Gizmos 可视化提升关卡编辑效率
  • 激活/停用事件分离

架构注意
WorldMarkerEventChannelSO 的事件泛型参数为 WorldMarkerMonoBehaviour 引用)。相比传值类型的事件数据(如结构体),携带 MonoBehaviour 引用会造成事件订阅方对场景物件的隐式依赖,降低可移植性。建议后续考虑将 Marker 信息提取为值结构体(含 ID + 位置),仅在 UI 层获取实体引用。

评分

维度 分数 说明
架构设计 8.5 Channel 携带 MonoBehaviour ref 存在耦合风险
性能 9.5
可扩展性 9.0
编辑器友好 9.5 Gizmos 完善
使用便利性 9.0

三、Bug 修复汇总

ID 文件 问题描述 严重程度 状态
TD-21 AccessibilitySettingsSO.cs 类在全局命名空间,应为 BaseGames.Support.Accessibility 已修复
TD-22 ColorBlindFilter.cs 类在全局命名空间,应为 BaseGames.Support.Accessibility 已修复
TD-23 GlobalSFXPlayer.cs 静态单例模式与 ServiceLocator 框架不一致 📝 记录,暂不修改
TD-24 UnderwaterPostProcessingController.cs OnLiquidExited 缺少 LiquidType 过滤,任何液体离开都触发重置 已修复
TD-25 HardAbilityGate.csRoomEscapeInfoSO.cs 命名空间 BaseGames.Progression 与文件夹 Support/AntiSoftlock 不符 已修复
TD-26 PuzzleSwitch.csPuzzleReceiver.cs Start() 忽略 WorldStateRegistry 存档,场景重载后谜题状态丢失 已修复
TD-27 PhantomInteractable.cs OnTriggerEnter2D 热路径中每次调用 LayerMask.NameToLayer() 已修复
TD-28 SpeedrunTimer.cs 类体未在 namespace 内缩进(格式问题) 📝 记录,下次顺手修
TD-29 LiquidZone.cs 带 CS0414 的无用字段污染 Inspector逻辑空洞 已修复

四、框架纯净性审查

框架设计原则:无兼容填补、无安全兜底、数据逻辑统一一致

检查项 状态 说明
null 向下兼容路径 所有新增组件均通过 Debug.Assert?. 安全调用限定在边界
FindObjectOfType 运行时查找 全部通过 Event Channel 或 ServiceLocator 注入
PlayerPrefs 侵入游戏逻辑 PlayerPrefs 仅限 AccessibilitySettings
事件通道复用 LiquidEventChannelSO 统一承载 Enter/Exit 两类事件
SO 注入(非 Instance 单例) PuzzleWire/Receiver/Switch 均通过 [SerializeField] WorldStateRegistry
命名空间一致性 (修复后) TD-21/TD-22/TD-25 全部修复

五、综合评分

本轮新增模块评分

模块 架构 性能 可扩展性 编辑器 易用性 模块均分
Audio Footstep 9.5 9.5 9.0 9.0 9.0 9.2
UnderwaterAudio 9.0 9.5 8.5 9.0 9.0 9.0
Tutorial 9.5 9.5 9.0 9.5 9.5 9.4
Accessibility 9.0 9.5 9.0 8.5 9.0 9.0
Analytics 9.5 9.0 9.5 9.5 9.5 9.4
AntiSoftlock 9.0 9.5 9.0 9.0 9.5 9.2
Speedrun 9.5 9.5 9.0 9.5 9.5 9.4
Liquid System 9.5 9.5 9.5 9.5 9.5 9.5
Puzzle System 9.5 9.5 9.5 9.5 9.5 9.5
PhantomInteractable 9.5 9.5 9.5 9.5 9.5 9.5
WorldMarker 8.5 9.5 9.0 9.5 9.0 9.1

本轮新增模块加权均分9.29 / 10

框架历史累积评分

版本 评分 主要贡献
v1v9 8.80 核心架构、Event Channel、ServiceLocator
v10v11 9.10 Combat、Save、Player State Machine
v12 9.25 Camera、Skills、Equipment
v13 9.45 BossBase、VFX、Progression Achievements
v14本轮 9.52 Liquid、Puzzle、Tutorial、Support 模块 + 9项修复

六、遗留改进建议(非阻塞)

  1. GlobalSFXPlayer 改用 ServiceLocatorTD-23
    注册 IGlobalSFXService 接口,消除静态单例。优先级:低,当前功能正确。

  2. AudioMixer 快照名常量化
    UnderwaterAudioController/AudioManager"Underwater"/"Default"/"BossFight" 等字符串建议收进 AudioMixerSnapshots 常量类,防止拼写错误。

  3. WorldMarkerEventChannelSO 携带值类型
    BaseEventChannelSO<WorldMarker> 替换为 BaseEventChannelSO<WorldMarkerInfo>struct解耦订阅方与场景对象的直接引用。

  4. SpeedrunTimer 缩进格式TD-28
    类体应在 namespace 内缩进 4 空格,与全框架风格保持一致。


审查人GitHub Copilot | 日期2026 年 5 月 | 覆盖文件:~30 个新增文件 | 修复问题9 处