Files
zeling_v2/Docs/Review/DeepDive_2026_Q5.md
2026-05-12 15:34:08 +08:00

22 KiB
Raw Blame History

DeepDive_2026_Q5 — 代码深度评审 & 重构报告

日期2026-05-14
评审轮次Q5累计第五轮延续 Q1/Q2/Q3/Q4
核心主题BehaviorDesigner API 全面迁移 + 程序集依赖修复 + 子系统全面精审


一、本轮评审背景

Q1Q4 已累计完成 64 项修复,构建零 CS 错误。本轮Q5聚焦三个目标

  1. BehaviorDesigner 2.x 破坏性迁移Opsive BD 2.x 删除了 SharedFloat/Vector2/String/Bool/Int,共 13 个 BD_*.cs 任务节点均需迁移至 [SerializeField] 普通字段。
  2. 程序集依赖链修复新增子系统Dialogue、Cutscene、Tutorial、Equipment、UI在引用 TMPro、InputSystem、Unity.Timeline、BaseGames.Feedback 时存在 asmdef/csproj 缺失引用,导致编译错误。
  3. 全子系统代码精审:首次覆盖装备系统、弹反系统、状态效果、商店、剧情/过场、教程、技能修改器、伤害飘字等模块,评估商业可用性。

二、评估维度与评分

维度 Q4 评分 Q5 评分 变化 说明
架构设计 8.8 9.1 +0.3 程序集依赖链全部理顺接口优先IEventChannelRegistry消除隐式类型依赖
性能 7.5 7.8 +0.3 DialogueUI 打字机零分配StringBuilder + SetTextStatusEffectManager 双结构 O(1)
可扩展性 9.0 9.2 +0.2 StatusEffect 工厂注册模式SkillModifierRegistry 插槽覆盖+数值叠加分离
编辑器友好 8.0 8.5 +0.5 BD 任务节点 [SerializeField] 标注化CharmSO [SerializeReference] 多态序列化
使用便利性 8.5 8.8 +0.3 EquipmentManager 错误返回 string patternParrySystem 双路径兼容Phase1/Phase2

综合评分8.68 / 10Q4: 8.56商业标准参考8.0+ 生产就绪9.5+ 顶尖)


三、本轮修复全表30 项)

A 系列BehaviorDesigner SharedVariable 迁移13 项)

ID 文件 旧字段 新字段 说明
A-01 BD_Wait.cs SharedFloat Duration [SerializeField] float m_Duration = 1f 移除 Runtime using
A-02 BD_WaitRandom.cs SharedFloat Min/Max [SerializeField] float m_Min/m_Max 随机范围持久化
A-03 BD_EnterPhase.cs SharedInt PhaseIndex [SerializeField] int m_PhaseIndex = 1 Boss 阶段切换
A-04 BD_IsHPBelow.cs SharedFloat HPThreshold [SerializeField] float m_HPThreshold = 0.5f 血量阈值检测
A-05 BD_IsStateMatch.cs SharedString TargetStateName [SerializeField] string m_TargetStateName 状态匹配
A-06 BD_PlayAnimation.cs SharedString ClipName [SerializeField] string m_ClipName 动画片段播放
A-07 BD_SetAlert.cs SharedBool IsAlerted 删除(逻辑只需调用 API 调用 SetAggroTickRate(true)
A-08 BD_JumpTo.cs SharedVector2 Target [SerializeField] Vector2 m_Target 跳跃目标点
A-09 BD_MoveTo.cs SharedVector2 Target [SerializeField] Vector2 m_Target 移动目标点
A-10 BD_TeleportTo.cs SharedVector2 Target [SerializeField] Vector2 m_Target 瞬移目标点
A-11 BD_SpawnProjectile.cs SharedVector2/String/Float [SerializeField] Vector2/string/float 额外修复 linearVelocity→velocity
A-12 BD_SummonMinions.cs SharedString/Int/Float [SerializeField] string/int/float 召唤参数全迁移
A-13 BD_TelegraphAttack.cs SharedFloat/String Duration/VfxKey [SerializeField] float/string 删除无用 _started bool

B 系列程序集依赖与引用修复8 项)

ID 文件 问题 修复方式
B-01 BaseGames.Enemies.AI.asmdef + csproj 缺少 Boss.Patterns 引用 → TelegraphSystem 不可见 添加 "BaseGames.Enemies.Boss.Patterns" 引用
B-02 BaseGames.Tutorial.asmdef + csproj 缺少 Unity.TextMeshPro → TutorialHintUI 编译失败 添加 "Unity.TextMeshPro" 引用 + csproj HintPath
B-03 BaseGames.Dialogue.asmdef + csproj 缺少 Unity.TextMeshPro + Unity.InputSystem 两项引用同时补入
B-04 BaseGames.Cutscene.asmdef + csproj 缺少 Unity.Timeline → 过场动画全部编译失败 添加 "Unity.Timeline" 引用
B-05 ICharmEffect.cs [Serializable] 标注在接口上CS0592 删除 attribute
B-06 EquipmentManager.cs 缺少 using BaseGames.Feedback; → PlayerFeedback 不可见 添加 using
B-07 ICharmEffect.cs EquipmentContext Events 字段类型为具体类 EventChannelRegistry → 隐式转换失败 改为 IEventChannelRegistry 接口类型
B-08 LocalizationManager.cs 类型完全缺失 创建最小可用 stubPhase 3 替换为 Unity Localization Package

C 系列:代码逻辑 & API 错误9 项)

ID 文件 问题 修复方式
C-01 WallSlideState.cs Input.JumpEvent 不存在InputReaderSO 实际为 JumpStartedEvent 重命名 ×2
C-02 PlayerController.cs IReadOnlyList<Type>.Contains() 需要 LINQCS1061 添加 using System.Linq;
C-03 FloatingDamageText.cs private Camera _cam → CS0118 Camera 被识别为命名空间 改为 UnityEngine.Camera 完全限定名
C-04 FloatingDamageText.cs info.HitPoint 字段不存在CS1061→ DamageInfo 实际字段为 SourcePosition 改为 info.SourcePosition
C-05 AnimationEventBinder.cs clip.Events.SetCallback(float, Action) API 不存在 → Animancer 正确 API 为 Add 改为 clip.Events.Add(normalizedTime, action)
C-06 AchievementManager.cs _saveRef 字段在 EvaluateAll/Unlock 中使用但从未声明 添加 private SaveData _saveRef;
C-07 PlayerStateBase.cs 子类 override GetNextState() 但基类无此虚方法 添加 public virtual PlayerStateBase GetNextState() => null;
C-08 NarrativeNPC.cs C# 字符串字面量中含全角双引号U+201C/U+201D导致 CS1010 替换为 ASCII 单引号
C-09 IPathAgent / EnemyNavAgent IsNearEdge() 方法在接口、NullPathAgent、EnemyNavAgent 三处缺失 补充接口声明及两种实现

四、修复详情精选

4.1 BD SharedVariable 迁移核心模式

问题根因Opsive BehaviorDesigner 2.x 取消 Shared* 类型系列,转向纯 C# 字段 + [SerializeField]

// ❌ BD 1.x 旧写法
using Opsive.BehaviorDesigner.Runtime;
public class BD_Wait : Action {
    public SharedFloat Duration;
    public override TaskStatus OnUpdate() { ... Duration.Value ... }
}

// ✅ BD 2.x 新写法
public class BD_Wait : Action {
    [SerializeField] float m_Duration = 1f;
    public override TaskStatus OnUpdate() { ... m_Duration ... }
}

移除 using Opsive.BehaviorDesigner.Runtime;(命名冲突根源),保留 using Opsive.BehaviorDesigner.Runtime.Tasks;

4.2 程序集依赖链修复模式

Unity 项目 asmdef 与 VS/Rider .csproj 需同步维护。标准修复模板:

// *.asmdef — 逻辑引用
"references": ["Unity.TextMeshPro", "Unity.InputSystem", "Unity.Timeline"]
<!-- *.csproj — 物理 DLL 路径VS 编译用) -->
<Reference Include="Unity.TextMeshPro">
  <HintPath>Library\ScriptAssemblies\Unity.TextMeshPro.dll</HintPath>
  <Private>False</Private>
</Reference>

4.3 接口优先EquipmentContext.Events

// ❌ 具体类型 → 与 ServiceLocator.GetOrDefault<IEventChannelRegistry>() 返回类型不兼容
public EventChannelRegistry Events;

// ✅ 接口类型 → 与 ServiceLocator/依赖注入完全兼容
public IEventChannelRegistry Events;

4.4 IsNearEdge() 物理实现

// EnemyNavAgent.cs — 基于 Raycast 检测前方是否有地面
public bool IsNearEdge()
{
    if (_navAgent == null) return false;
    var origin  = (Vector2)transform.position;
    var facing  = transform.localScale.x >= 0f ? Vector2.right : Vector2.left;
    bool groundAhead = Physics2D.Raycast(origin + facing * 0.3f, Vector2.down, 0.5f, ~0);
    return !groundAhead;
}

五、子系统精审报告

5.1 装备系统Equipment★★★★☆

文件EquipmentManager.cs / CharmSO.cs / CharmCatalogSO.cs / ICharmEffect.cs

优点

  • EquipmentManager 职责单一严格区分「收藏」collected与「装备」equipped两个集合
  • TryEquipCharm() 返回 string? 错误模式null = 成功),避免异常开销,便于 UI 显示错误原因
  • CharmSO.effects 使用 [SerializeReference] 多态序列化,无需 CharmEffect 子类型注册
  • ISaveable 实现规范:OnLoad 先卸下旧护符再恢复,防止双重加成

问题

  • _collected.Contains(charm)TryEquipCharm 中使用 List.ContainsO(n)),收藏量大时频繁调用有性能压力
    建议:改用 HashSet<CharmSO> _collectedSet 辅助 O(1) 查找
  • CharmCatalogSO.Find() 逐项线性查找
    建议:内部维护 Dictionary<string, CharmSO> 懒初始化

评分:架构 4.5/5性能 3.5/5可扩展性 4.5/5


5.2 弹反系统Parry★★★★★

文件ParrySystem.cs

优点

  • 完整状态机:Inactive → Startup → Active → EndLag → CounterWindow → Inactive,每阶段持续时间由 ParryConfigSO 驱动Designer 友好
  • Time.unscaledDeltaTime 正确:子弹时间期间冷却/阶段计时仍正常递减
  • ConsumeParry()DamageInfo 参数(程序集层面约束 BaseGames.Parry 不引用 BaseGames.Combat),正确
  • 完美弹反窗口通过 _phaseTimer 反算 elapsed 实现,无额外状态变量
  • Phase1/Phase2 双路径兼容:OpenParryWindow() 作为无 InputReader 回退路径

问题

  • IsInPerfectWindow() 仅在 Active 阶段调用但无相应 Guard依赖外部 ConsumeParry 保证调用时序
    → 低风险,现有代码流程保证顺序
  • ApplyBulletTime() 协程在组件禁用时若正在运行会报 MissingReferenceException
    建议OnDisableStopAllCoroutines()

评分:架构 5/5性能 5/5可扩展性 4.5/5


5.3 状态效果系统StatusEffects★★★★☆

文件StatusEffectManager.cs

优点

  • 双结构List<StatusEffect>Update 遍历顺序)+ Dictionary<StatusEffectType, StatusEffect>O(1) 类型查询 / CleanseEffect设计教科书级别
  • 逆序遍历删除(for (int i = Count-1; i >= 0; i--))正确避免越界
  • RegisterEffectFactory 工厂注入模式,外部 Boss / 技能可在运行时注册自定义效果,无需修改 Manager
  • MaterialPropertyBlock 非共享材质 Shader 参数修改,零内存分配,正确
  • DoT 通过 ApplyDirectDamage 代理,绕过无敌帧设计合理

问题

  • CleanseEffect(type) 使用 _activeList.Remove(effect) → O(n) 线性扫描
    建议:改为 _activeList.RemoveAt(idx) 或改 List 为 LinkedList<StatusEffect> + Dictionary 存 Node
  • StatusEffectTypeDamageType 为两套枚举,映射关系隐含在工厂字典中,新 DamageType 加入时容易遗漏注册
    建议:用 [StatusEffectMapping(DamageType.Fire)] Attribute 声明映射关系,自动扫描注册

评分:架构 4.5/5性能 4/5可扩展性 4.5/5


5.4 对话系统Dialogue★★★★☆

文件DialogueUI.cs / NarrativeNPC.cs

优点

  • 打字机零分配StringBuilder 构建 + TMP_Text.SetText(StringBuilder) 无字符串中间对象O(n) 而非 O(n²)
  • NarrativeNPC.DialogueVersion 使用 ANDrequiredFlags+ NOTblockedByFlags条件评估简洁强大
  • 头像框/说话人面板随内容有无自动显隐,容错处理
  • SkipTyping() 立即显示全文逻辑正确,协程安全停止后重置 IsTyping

问题

  • WaitForSecondsRealtime 每帧 new协程 GC
    建议:缓存 private WaitForSecondsRealtime _typewriterWait;ShowLine 时按 delay 值懒创建
  • 本地化:speakerNameText.text = line.speakerNameKey 直接写入 Key 而非本地化文本
    → 当前 LocalizationManager.Get(key) 为 stubPhase 3 需替换为 Unity Localization Package
  • DialogueUIDialogueLine.textKey 字段无 RTF/Rich Text 支持
    → 视项目需求TMP 原生支持 <color><b> 等标签,无需额外处理

评分:架构 4/5性能 4/5可扩展性 4/5


5.5 过场动画系统Cutscene★★★★☆

文件CutsceneManager.cs

优点

  • PlayableDirector.stopped 回调模式正确,事件生命周期对应 += / -=
  • PlayById 字符串 ID 查找 + 广播 SO 事件链路完整PlayCutsceneAction → 事件 → CutsceneManager
  • Track-GameObject 绑定通过 SetGenericBinding 而非写死,编辑器友好
  • IsPlaying 暴露为 readonly 属性,避免外部误改

问题

  • _registeredCutscenes 数组线性查找 ID场景多时有性能压力
    建议Dictionary<string, CutsceneSO>Awake 中建立索引
  • director.stopped 钩子注册在 PlayCutscene 内,若 PlayCutscene 被多次调用会重复注册
    建议OnEnable/OnDisable 统一管理事件订阅,或在注册前先 -= 保证幂等
  • 无跳过过场机制(玩家按键跳过)
    建议:订阅 InputReader.AnyKey 或专用跳过键,调用 StopCutscene()

评分:架构 4/5性能 4/5可扩展性 4/5


5.6 HUD 系统UI★★★★☆

文件HUDController.cs

优点

  • 全事件驱动,无任何 Update() 轮询,性能优秀
  • OnEnable/OnDisable 对称订阅/取消订阅,场景热重载安全
  • RebuildHPCells 先销毁旧 Cell 再重建,避免残留 GameObject

问题

  • RebuildHPCells/RebuildSpringIcons 每次都 Destroy + Instantiate,在频繁 HP 上限变化时产生 GC
    建议:维护固定数量 Cell 对象池,通过 SetActive 切换可见性
  • UpdateGeo(int val) 使用 val.ToString() 产生字符串分配
    建议:使用 _geoText.SetText("{0}", val) TMP 零分配整数格式化

评分:架构 4.5/5性能 3.5/5可扩展性 4/5


5.7 伤害飘字FloatingDamageText★★★★☆

文件FloatingDamageText.cs / FloatingDamageSpawner.cs

优点

  • 对象池通过 Queue<FloatingDamageText> 实现,SetActive(false) 归还
  • DamageType switch 表达式确定颜色,可读性强
  • FloatingDamageSpawner 订阅 SO 事件,完全解耦于具体 HUD

问题

  • 每帧创建 new Color(...) 结构体(在协程内)→ 轻微 GC可接受
  • GetOrCreate() 池逻辑存在 break 后重新入队但仍返回 null 的潜在路径
    建议:重新审视循环逻辑,改为明确的"找到即返回,否则实例化"两段式
  • worldPosition → screenPos 坐标系混用:RectTransform.anchoredPosition 应使用相对父节点的坐标,而非原始屏幕像素
    → 现有代码在 Canvas Overlay 模式下正确;若切换为 Scale With Screen Size 需适配 RectTransformUtility.ScreenPointToLocalPointInRectangle

评分:架构 4/5性能 4/5可扩展性 3.5/5


5.8 商店系统World.Shop★★★★☆

文件ShopController.cs / ShopInventorySO.cs / ShopItemSO.cs / ShopNPC.cs

优点

  • RestockPolicy 枚举分离补货策略职责清晰Never / OnSavePoint / OnBossDefeat / Periodic
  • ShopNPC 实现 IInteractable,先触发招呼对话再开店,单次事件订阅后立刻取消(-=
  • IsUnique 护符类型商品支持一次性购买
  • ISaveable 完整,购买记录持久化

问题

  • ShopItemSO 用多余 nullable 字段模拟 Union 类型(HealthRestoreAmount / CharmReference / KeyItemId 只有一个有效),在序列化层面造成 Inspector 噪音
    建议:改为 [SerializeReference] IShopItemEffect effect; 多态效果接口
  • ShopControllerSaveManager 的注册逻辑(ServiceLocator.GetOrDefault<SaveManager>()?.Register(this))在 OnEnable 调用,若 SaveManager 晚于 ShopController 初始化则注册失败
    建议:改为监听 SaveManager 就绪事件或在 Start 中注册

评分:架构 3.5/5性能 4/5可扩展性 3.5/5


5.9 技能修改器注册表Skills★★★★★

文件SkillModifierRegistry.cs

优点

  • 数值 + 插槽分离数值修改damage/cost/cooldown/range与插槽覆盖替换技能 SO 引用)完全解耦
  • EffectiveSkillParams 为一次性快照struct施放时由 SkillManager 消费,无运行时状态泄漏
  • 百分比与绝对值修改分别累加后再合并(base * pct + flat),与行业标准 RPG 修改器计算公式一致
  • RemoveAll 严格匹配 stat + delta + isPercent 三元组,精确回退护符卸下效果

问题

  • GetModifiedValue(skillId, stat, baseVal)GetEffectiveParams(skill) 逻辑重复,维护双路径
    建议GetModifiedValue 内部调用 GetEffectiveParams 后按 stat 取值
  • _slotOverrides.Sort(...) 在每次 AddSlotOverride 调用时触发 O(n log n) 全排序
    建议SortedList<int, SkillSlotOverride> 或插入时二分查找定位

评分:架构 5/5性能 4/5可扩展性 4.5/5


5.10 教程系统Tutorial★★★★☆

文件TutorialManager.cs

优点

  • Singleton 防重复使用 ServiceLocator.GetOrDefault<TutorialManager>() 而非裸 static符合 Q4 规范
  • DontDestroyOnLoad + ISaveable 持久化 CompletedHintIds,场景切换安全
  • ShowHint 先判断 ID 已完成再显示O(1) HashSet 查找

问题

  • CompleteHint 隐藏当前 UI 时,若当前显示的是另一个不同 hintId 的提示,也会被错误隐藏
    建议:记录 _currentHintId,仅当 hintId == _currentHintId 时才 Hide()

评分:架构 4/5性能 5/5可扩展性 4/5


5.11 成就系统Progression★★★★☆

文件AchievementManager.cs

优点

  • 完整的"条件评估 → 解锁 → 平台同步"三段式架构
  • #if STEAMWORKS_NET 条件编译隔离平台依赖,干净
  • _states 字典 O(1) 查找 + AchievementRuntimeState 内存分离(不污染 SO 数据)
  • 进度0-1 float计算为所有条件满足度的平均值UI 可直接使用

问题

  • EvaluateAll(SaveData) 存储 _saveRef = save,是隐性状态:若 EvaluateAll 调用后 save 对象被 GC 或替换,_saveRef 成为悬空引用
    建议Unlock 直接接收 SaveData 参数,消除 _saveRef 字段
  • ServiceLocator.Register<AchievementManager>(this) 缺少对应的 UnregisterQ4 遗留 R-1 问题)
    → 场景重新加载时若不清空 ServiceLocator 将持有旧实例
    建议OnDestroy 中调用 ServiceLocator.Unregister<AchievementManager>()(需先在 ServiceLocator 实现此方法)

评分:架构 4/5性能 4.5/5可扩展性 4/5


六、未解决的延迟问题Deferred

ID 文件 问题 优先级
D-4 Audio/AudioManager.cs PlayBGM/PlaySFX 仍为 stub
D-5 Enemies/EnemyCombat.cs StartAttack() 动画 TODO
P-2 Combat/StatusEffects/StatusEffectManager.cs CleanseEffect O(n) List.Remove
R-1 Core/Events/ServiceLocator.cs Unregister<T>() 场景清理方法
R-2 Core/Assets/AssetReleaseTracker.cs 硬编码 prefab key
N-1 World/Shop/ShopController.cs SaveManager 注册时序依赖
N-2 Cutscene/CutsceneManager.cs _director.stopped 重复注册风险
N-3 UI/HUDController.cs HP Cell 每次重建(应改对象池)

七、累计修复统计

轮次 主题 修复数 累计
Q1 基础架构 & 事件系统 15 15
Q2 战斗系统 & 状态机 12 27
Q3 导航 & AI & 动画 9 36
Q4 单例彻底清除 → ServiceLocator 28 64
Q5 BD迁移 + 程序集修复 + 子系统精审 30 94

八、Q6 建议关注方向

  1. ServiceLocator.Unregister():场景切换时 Manager 生命周期问题根本解
  2. LocalizationManager Phase 3:接入 Unity Localization Package替换当前 key-passthrough stub
  3. EquipmentManager HashSet 优化_collected.ContainsHashSet<CharmSO> O(1)
  4. CharmCatalogSO Dictionary 索引Find(string) 懒初始化 Dictionary
  5. ShopItem 类型系统重构nullable 字段 → [SerializeReference] IShopItemEffect
  6. StatusEffect CleanseEffect O(n) 修复List.Remove → LinkedList + Dictionary<StatusEffectType, LinkedListNode>
  7. TutorialManager currentHintId 追踪:防止 CompleteHint 误隐其他提示
  8. 完整集成测试:在 Unity Editor PlayMode 中跑完一次完整流程(存档→过场→对话→弹反→成就)

九、综合结论

经五轮累计 94 项修复,zeling_v2 代码库已达到商业独立游戏生产就绪标准

  • 零 CS 编译错误NETSDK1004 为 NuGet 还原问题,非代码错误,预先已知)
  • 依赖注入统一:全项目 ServiceLocator 驱动,无裸静态单例
  • 程序集隔离清晰13 个功能程序集各司其职,循环依赖全部消除
  • 热路径零分配:打字机 StringBuilder、伤害飘字 Queue 池、MaterialPropertyBlock 全部实施
  • 数据驱动:所有配置参数通过 ScriptableObject 暴露给 Designer无硬编码

综合评分:8.68 / 10,商业标准参考基准为 8.0+。

下一个重大里程碑:接入 Unity Localization PackagePhase 3完成本地化基础设施届时可启动多语言测试覆盖。