v12 全量评审:修复 TD-18(RunState 物理双重施速)+ 编写 v12 评审文档

This commit is contained in:
2026-05-12 16:45:37 +08:00
parent 1135139bc6
commit 984a738478
2 changed files with 315 additions and 2 deletions

View File

@@ -0,0 +1,315 @@
# Zeling v2 框架全量代码评审报告 v12
> **评审时间**2026 年 5 月
> **评审范围**v12 深度补充评审Camera / Localization / Spells / Skills / Player States 全量 / Enemies 核心 / World 核心 / Dialogue / Progression 成就系统)
> **前置版本**v11 评审报告(综合评分 9.30/10
> **本版改进**:精读 v11 未覆盖的剩余主要模块;新增亮点 14 条;发现并修复 1 个问题TD-18
---
## 一、综合评分总览
| 维度 | v11 评分 | v12 评分 | 变化 | 说明 |
|------|---------|---------|------|------|
| 架构设计 | 9.3 | 9.4 | ↑ | NarrativeNPC 条件版本系统 / WorldStateRegistry 泛化分类 / InteractableNPC 继承钩子等设计亮点提升 |
| 性能 | 9.2 | 9.3 | ↑ | SkillManager 固定数组快照避免 GC / EnemyQuotaManager O(1) 注销 + 双结构体加速 / LocalizationManager 双层缓存 |
| 可扩展性 | 9.4 | 9.4 | → | AchievementCondition 开放多态 / DialogueVersion 无代码条件扩展继续保持高分 |
| 编辑器友好 | 9.4 | 9.4 | → | 稳定 |
| 使用便利性 | 9.0 | 9.1 | ↑ | InteractableNPC 三钩子 API 易扩展 / CollectibleSpawner 静态入口简洁 |
| **综合** | **9.30** | **9.35** | **↑** | 本轮精读发现整体已达成熟商业品质TD-18 物理双重施速修复后无已知隐患 |
---
## 二、v11 修复验证
| ID | 修复项 | 验证结果 |
|----|--------|---------|
| TD-13 | IQuestManager.CompleteQuest 改为 IRewardTarget | ✅ 已验证:接口方法签名正确,移除 `using BaseGames.Player` |
| TD-14 | HurtFlashController 缓存 WaitForSeconds | ✅ 已验证:`_waitForFlash` 字段在构造时创建 |
| TD-15 | LiquidType 枚举迁移至 Core.Events | ✅ 已验证:`LiquidType.cs` 存在于 Core.Events 程序集目录,正确枚举类型 |
| TD-16 | LiquidEvent / LiquidZone / WaterDangerState 等改为枚举比较 | ✅ 已验证:字段类型为 `LiquidType`,比较无 `.ToString()` |
| TD-17 | DeathScreenController 订阅时序修复 | ✅ 已验证:`OnEnable` 直接 `StartCoroutine`,不再依赖已失效订阅 |
---
## 三、本轮评审模块详解
### 3.1 Camera 模块
**文件**`CameraStateController.cs``RoomCamera.cs`
| 亮点 | 说明 |
|------|------|
| **BlendProfile per-room** | `BlendProfileSO` 以 SO 形式挂在每个房间,支持独立混合曲线和时长,房间过渡效果可精细定制 |
| **HashSet 注册管理** | `_registeredCameras: HashSet<RoomCamera>` 防止重复注册,`SwitchRoom` O(1) 查找 |
| **Priority 驱动切换** | `RoomCamera.Activate()` 设 priority=15`Deactivate()` 设 priority=0Cinemachine Brain 自动选最高优先级,切换逻辑零侵入 |
| **TriggerImpulse 重载** | 提供 `Vector3``float` 两个重载,震屏 API 简洁易用 |
**评分**:架构 9.5 / 性能 9.5 / 可扩展性 9.5
---
### 3.2 Localization 模块
**文件**`LocalizationManager.cs`
| 亮点 | 说明 |
|------|------|
| **双层缓存** | `Dictionary<string, Dictionary<string, string>>`key = `"ChineseSimplified/UI"`,避免重复解析 JSON |
| **三级回退链** | 当前语言 → English → 直接返回 key任何情况下不抛异常UI 不崩溃 |
| **双事件模式** | `static event Action<Language> OnLanguageChanged`(向后兼容旧代码)+ 接口 `ILocalizationService.OnLanguageChanged` 显式实现委托给静态事件,两种订阅方式都得到支持 |
| **ISaveable 持久化** | 语言偏好通过 SaveManager 持久化,不使用 PlayerPrefs与存档系统保持一致 |
| **Resources 加载隔离** | JSON 文件放在 `Resources/Localization/{Language}/{TableName}.json`,文件组织清晰,未来迁移至 Addressables 仅需改一处加载逻辑 |
**评分**:架构 9.5 / 性能 9.3 / 可扩展性 9.4
---
### 3.3 Spells 模块
**文件**`SpellManager.cs``SpellSO.cs`
| 亮点 | 说明 |
|------|------|
| **SpellEffectType 完整枚举** | `{Projectile, AreaOfEffect, SelfBuff, SummonShade, TeleportBlink}` 覆盖主流法术类型,扩展时直接加枚举成员 |
| **CooldownFraction 属性** | `public float CooldownFraction` 供 UI 直接轮询进度,无需 UI 层了解冷却实现细节 |
| **单槽设计简洁** | 当前为单法术槽,`EquipSpell/UnequipSpell` API 清晰,未来扩展多槽只需更换集合结构 |
| **资源类型分离** | `SpellResourceType {SoulPower, SpiritPower}` 与玩家两种资源对应,条件检查统一在 `TryCastSpell()` |
**评分**:架构 9.2 / 性能 9.3 / 可扩展性 9.3
---
### 3.4 Skills 模块
**文件**`SkillManager.cs`
| 亮点 | 说明 |
|------|------|
| **固定大小数组快照** | `FormSkillSO[] _activeSkills = new FormSkillSO[3]``UpdateSkillSet` 写入固定位置,避免每帧遍历 `Dictionary.Values` 产生 GC |
| **Form 注入解耦** | `UpdateSkillSet(soul, spirit1, spirit2)``FormController` 在 Form 切换时调用SkillManager 不感知 Form 实现 |
| **Dictionary 按引用键** | `Dictionary<FormSkillSO, float> _cooldowns` 以 SO 引用为键,精确对应每个技能实例的冷却 |
| **三独立 Input 事件** | SoulSkillEvent / SpiritSkill1 / SpiritSkill2 各自订阅,互不干扰 |
**评分**:架构 9.3 / 性能 9.5 / 可扩展性 9.2
---
### 3.5 Player States 全量评审
#### 已评v11 本批)
| 状态 | 亮点 | 评分 |
|------|------|------|
| **IdleState** | Enter 时 `AerialDashState.ResetAerialDashes()` 着地重置,设计精准 | 9.5 |
| **RunState** | ~~双重 Move 调用~~**TD-18 已修复** | 9.3 |
| **JumpState** | `Input.JumpCancelledEvent` 精确 Enter/Exit 管理,`CutJump()` 可变跳跃高度 | 9.5 |
| **FallState** | 郊狼时间 + `FallGravityMult` 增强下落手感 + `MaxFallSpeed` 上限,三重机制健全 | 9.5 |
| **DashState** | `SetGravityScale(0f)` 冲刺间禁重力 + FixedUpdate 锁速防摩擦 + `IsInvincible` 无敌帧 | 9.5 |
| **WallSlideState** | Enter/Exit 精确订阅 `JumpStartedEvent`,离墙/着地转出逻辑清晰 | 9.4 |
| **WallJumpState** | `_inputLockTimer` 锁定水平输入防止立即贴回墙壁,手感设计合理 | 9.4 |
| **AerialDashState** | 独立于地面冲刺,消耗次数计数,任意方向冲刺 | 9.4 |
| **AttackState** | Animancer 归一化时间事件驱动 HitBox连击段数由 `AnimCfg.GroundAttacks.Length` 动态决定,零硬编码 | 9.5 |
| **ParryState** | `OpenParryWindow/CloseParryWindow` 生命周期严格对齐 Enter/ExitOnEnd 动画事件驱动退出 | 9.5 |
| **DeadState** | 冻结物理 + 关重力 + 禁 HurtBoxExit 时全部恢复,"不自动转出"由 DeathRespawnSystem 事件驱动 | 9.5 |
**总体 Player States 评分**9.45 / 10
---
### 3.6 Enemies 模块
**文件**`EnemyBase.cs``EnemyNavAgent.cs``EnemyQuotaManager.cs``LootTableSO.cs``LootResolver.cs`
| 亮点 | 说明 |
|------|------|
| **IPathAgent 接口隔离** | `EnemyBase._nav: IPathAgent``EnemyNavAgent` 实现接口EnemyBase 和 BD Task 不直接依赖 PathBerserker2d 类型 |
| **ILOSRequester 接口** | `EnemyBase: ILOSRequester`,视线检测系统通过接口查询,架构层次清晰 |
| **EnemyQuotaManager 双结构体** | `HashSet<EnemyBase>` O(1) 重复检测 + `List<EnemyBase>` 排序 + `Dictionary<EnemyBase,int>` 索引映射,注销时 swap-remove 保持 O(1) |
| **每 10 帧重排** | `REBALANCE_INTERVAL = 10`,只对最近 N 个敌人启用 BT大幅节省 AI 计算开销 |
| **OnPlayerSpawned 替代 FindWithTag** | 通过 `TransformEventChannelSO` 接收玩家 Transform避免 N 个敌人同帧全场景 Tag 扫描 |
| **LootResolver 加权随机** | 两次遍历 `LootTable.Entries`一次求总权重一次滚动选中Hard 难度对 `ScaleWithDifficulty` 条目加 1.5× 权重 |
| **IPoiseSource 霸体分离** | `EnemyBase` 持有 `IPoiseSource _poiseSource`,由 `EnemyPoiseComponent.Awake()` 自动注入TakeDamage 时读取霸体等级 |
**评分**:架构 9.4 / 性能 9.4 / 可扩展性 9.3
---
### 3.7 World 模块
**文件**`WorldStateRegistry.cs``RoomController.cs``HazardZone.cs``CrumblePlatform.cs``PhantomPlate.cs``FalseWall.cs``DirectionalDestructible.cs``WorldMarker.cs``CollectibleSpawner.cs`
| 亮点 | 说明 |
|------|------|
| **WorldStateRegistry 泛化分类** | `WorldObjectCategory` 枚举 + 语义化快捷 API`IsCollected / MarkSavePointActivated / IsDoorOpened`),单一 SO 管理全部世界状态 |
| **OnEnable 清状态** | SO 的 `OnEnable` 在每次 Play Mode 重置 `_states`Editor 迭代不污染 |
| **OnStateChanged 事件** | `(WorldObjectCategory, string)` 响应式广播UI / 成就系统零耦合订阅 |
| **RoomController 最简职责** | 仅负责切换相机 + 出生点查询,无多余逻辑,符合 SRP |
| **PhantomPlate 原生 PlatformEffector2D** | 利用 Unity 内置单向平台,`TriggerDropThrough()` 接口简洁,无需物理 Hack |
| **CrumblePlatform 四态协程** | Warning → Crumbling → Gone → Recovering`_isOneShot` 控制是否恢复Feedback 统一由 MMF_Player 触发 |
| **DirectionalDestructible 模式匹配** | `switch expression` 将攻击方向映射到法向量阈值,继承 `DestructibleTile` 仅覆盖 `CheckDestroyCondition`,遵循 OCP |
| **WorldMarker Gizmo 颜色** | 按 `WorldMarkerType switch` 分色 Gizmo编辑器可视化区分标记类型 |
| **CollectibleSpawner 池优先 + 回退** | 优先 `IObjectPoolService.Spawn`,不可用时 `Object.Instantiate` 兜底,编辑器 / 测试场景无需预热池 |
| **HazardZone stats.MaxHP*2 即死** | 用 `MaxHP*2` 而非 `int.MaxValue` 确保即死,同时避免整数溢出 |
**评分**:架构 9.4 / 性能 9.3 / 可扩展性 9.4
---
### 3.8 Dialogue 模块
**文件**`InteractableNPC.cs``NarrativeNPC.cs``DialogueUI.cs`
| 亮点 | 说明 |
|------|------|
| **InteractableNPC 三钩子** | `Interact_Internal()`(前置逻辑)、`GetCurrentDialogue()`(对话选择)、`PlayDialogue()`(播放),子类覆盖任意钩子即可扩展行为 |
| **NarrativeNPC 优先级排列** | `DialogueVersion[]` 按优先级遍历,`CheckConditions` AND requiredFlags / NOT blockedByFlags无代码扩展对话版本 |
| **DialogueUI 打字机** | `SkipTyping()` 立即显示全文,`IsTyping` 属性外部可查询,`_continuePrompt` 对象打字结束后显示,交互反馈完整 |
| **ServiceLocator 解耦** | `InteractableNPC.PlayDialogue` 通过 `ServiceLocator.GetOrDefault<IDialogueService>()` 获取管理器,不直接引用具体实现 |
**评分**:架构 9.4 / 性能 9.3 / 可扩展性 9.5
---
### 3.9 Progression / Achievement 模块
**文件**`BossProgressTracker.cs``HPContainerPickup.cs``ProgressLock.cs``ParryCountCondition.cs``NoHealRunCondition.cs``TimedBossKillCondition.cs``DefeatedAllBossesCondition.cs`
| 亮点 | 说明 |
|------|------|
| **AchievementCondition 多态 SO** | 每个条件类型独立 SO 子类,`CreateAssetMenu` 直接在 Editor 创建,条件组合无代码修改 |
| **ParryCountCondition GetProgress** | 实现 `GetProgress()` 返回 `[0,1]` 进度UI 可显示进度条,成就系统 API 完整 |
| **NoHealRunCondition Switches 标志** | 通过 `SaveData.World.Switches` 记录治疗失败状态,而非运行时字段,持久化后可跨场景检查 |
| **TimedBossKillCondition ChallengeRooms** | 读取 `ChallengeRooms.Records[bossRoomId].BestTime`,与挑战房间计时系统直接集成 |
| **BossProgressTracker 事件路由** | `_onBossDefeated` 接收来自 `BossCombat` 的事件,过滤 `_bossId` 后通过 `_onBossDefeatedForSave` 转发给 SaveSystem零耦合 |
| **ProgressLock Start + OnEnable 双触发** | `Start` 读档状态初始化,`OnEnable` 订阅事件,事件驱动实时响应 Boss 击败,状态机设计严谨 |
| **HPContainerPickup ISaveService 查询** | `Start()` 通过 `ISaveService.IsWorldCollected` 检查存档防重复拾取,`OnTriggerEnter2D` 再次校验防竞态 |
**评分**:架构 9.3 / 性能 9.3 / 可扩展性 9.5
---
## 四、发现问题与修复
### TD-18RunState 双重施加水平速度
| 属性 | 内容 |
|------|------|
| **ID** | TD-18 |
| **严重程度** | 中 |
| **文件** | `Assets/Scripts/Player/States/RunState.cs` |
| **问题描述** | `OnStateUpdate()``OnStateFixedUpdate()` 均调用 `Move.Move(Input.MoveInput.x * Cfg.RunSpeed)``PlayerController.Update()` 调用 `OnStateUpdate()``PlayerController.FixedUpdate()` 调用 `OnStateFixedUpdate()`。在一个渲染帧内可能执行多次 `FixedUpdate`Physics 步骤 > 渲染帧时),或水平速度已由 `Update` 设置后再由 `FixedUpdate` 覆盖,导致物理帧内速度被双重施加或互相干扰。 |
| **根因** | 物理移动逻辑(`Move.Move`)应统一放在 `FixedUpdate` 中;`Update` 中仅做输入轮询和状态转换判断。 |
| **修复方案** | 移除 `OnStateUpdate()` 中的 `Move.Move(...)` 调用,仅保留状态转换检查;物理移动仅在 `OnStateFixedUpdate()` 中执行。 |
| **修复状态** | ✅ **已修复** |
**修复前**
```csharp
public override void OnStateUpdate()
{
// ...转换检查...
if (Mathf.Abs(Input.MoveInput.x) < 0.1f) { ... return; }
Move.Move(Input.MoveInput.x * Cfg.RunSpeed); // ← 多余
}
public override void OnStateFixedUpdate()
{
Move.Move(Input.MoveInput.x * Cfg.RunSpeed);
}
```
**修复后**
```csharp
public override void OnStateUpdate()
{
// 仅做状态转换检查,不施加速度
if (!Move.IsGrounded) { ... return; }
if (Buffer.ConsumeJump()) { ... return; }
if (Mathf.Abs(Input.MoveInput.x) < 0.1f) { ... return; }
}
public override void OnStateFixedUpdate()
{
Move.Move(Input.MoveInput.x * Cfg.RunSpeed); // 物理移动统一在此
}
```
---
## 五、本版代码亮点汇总(新增 14 条)
| 编号 | 模块 | 亮点 |
|------|------|------|
| #31 | Camera | `BlendProfileSO` per-room每个房间独立相机混合配置 |
| #32 | Camera | `RoomCamera` Priority 驱动切换Cinemachine Brain 零侵入 |
| #33 | Localization | 三级回退链当前语言→English→key任何情况不抛异常 |
| #34 | Localization | 双事件模式(静态 `OnLanguageChanged` + 接口),两种订阅方式共存 |
| #35 | Localization | `ISaveable` 持久化语言偏好,不使用 PlayerPrefs |
| #36 | Skills | `FormSkillSO[] _activeSkills` 固定数组快照,避免 Update 遍历 Dictionary GC |
| #37 | Player States | Animancer 归一化时间事件驱动 HitBox连击段数动态读取AttackState 零硬编码 |
| #38 | Player States | `AerialDashState` 独立次数计数,支持多段空中冲刺扩展 |
| #39 | Enemies | `EnemyQuotaManager` swap-remove O(1) 注销 + 每 10 帧重排 BT 配额 |
| #40 | Enemies | `LootResolver` 加权随机 + 难度缩放,`CollectibleSpawner` 池优先 + 回退 |
| #41 | World | `WorldStateRegistry` 泛化分类 API + `OnStateChanged` 响应式广播 |
| #42 | World | `DirectionalDestructible` 模式匹配方向检测,继承 OCP 扩展 |
| #43 | Dialogue | `NarrativeNPC` 优先级版本列表 + AND/NOT 标志条件,零代码扩展对话 |
| #44 | Progression | `AchievementCondition` 多态 SO + `GetProgress()` 进度 API 完整 |
---
## 六、历史亮点回顾v1v11 累计 30 条)
> 见 [FrameworkReview_2026_May_v11.md](FrameworkReview_2026_May_v11.md) 第五节。
---
## 七、历史问题修复汇总TD-01 至 TD-18
| ID | 严重程度 | 版本 | 状态 |
|----|---------|------|------|
| TD-01 | 高 | v5 | ✅ |
| TD-02 | 中 | v5 | ✅ |
| TD-03 | 高 | v6 | ✅ |
| TD-04 | 中 | v7 | ✅ |
| TD-05 | 低 | v7 | ✅ |
| TD-06 | 中 | v10 | ✅ |
| TD-07 | 高 | v10 | ✅ |
| TD-08 | 中 | v10 | ✅ |
| TD-09 | 低 | v10 | ✅ |
| TD-10 | 中 | v10 | ✅ |
| TD-11 | 低 | v10 | ✅ |
| TD-12 | 低 | v10 | ✅ |
| TD-13 | 高 | v11 | ✅ |
| TD-14 | 低 | v11 | ✅ |
| TD-15 | 低 | v11 | ✅LiquidType 枚举迁移) |
| TD-16 | 低 | v11 | ✅(字符串比较→枚举比较) |
| TD-17 | 中 | v11 | ✅ |
| TD-18 | 中 | v12 | ✅ |
---
## 八、遗留待覆盖模块
以下模块尚未精读(规模较小,估计代码质量与已读部分一致):
- `Camera/CameraBlendProfileSO.cs``CameraTriggerZone.cs``ICameraService.cs``RoomVisibleArea.cs`
- `Skills/FormSkillSO.cs``SkillModifierRegistry.cs``SkillSlotNames.cs`
- `VFX/VFXCatalogSO.cs`
- `Enemies/AI/`BT Task 集合)、`Enemies/Boss/`Boss Patterns
- `World/BreadcrumbTracker.cs``MagicWall.cs``DeathShade.cs``RoomTransition.cs``SavePoint.cs``AbilityGate.cs``AbilityUnlock.cs`
- `Progression/RegionDefinitionSO.cs`
- `Progression/Achievement/`(其余 5 个条件类)
---
## 九、总结
v12 评审精读了 Camera / Localization / Spells / Skills / Player States 全量 / Enemies 核心 / World 核心 / Dialogue / Progression 成就系统共约 40+ 文件,发现并修复 1 个中等问题TD-18 RunState 物理双重施速)。
框架整体达到成熟商业 2D Action RPG 代码品质:
- **架构**模块边界清晰ServiceLocator + EventChannel 解耦充分,单向程序集依赖无循环
- **性能**:关键热路径(玩家状态 Update / 敌人 AI 配额 / 技能快照)均已优化,无明显 GC 热点
- **可扩展性**SO 多态AchievementCondition / CharmEffect / SpellSO / LootTableSO覆盖核心变化点设计师友好
- **编辑器友好**Gizmo 可视化HazardZone / WorldMarker / DirectionalDestructible+ `[RequireComponent]` + `Debug.Assert` 覆盖完整
- **使用便利性**InteractableNPC 三钩子 / WorldStateRegistry 语义化 API / CollectibleSpawner 静态入口,扩展体验优秀
综合评分:**9.35 / 10**(无已知未修复问题)