18 KiB
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=0,Cinemachine 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 | 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/Exit,OnEnd 动画事件驱动退出 |
9.5 |
| DeadState | 冻结物理 + 关重力 + 禁 HurtBox,Exit 时全部恢复,"不自动转出"由 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-18:RunState 双重施加水平速度
| 属性 | 内容 |
|---|---|
| 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() 中执行。 |
| 修复状态 | ✅ 已修复 |
修复前:
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);
}
修复后:
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 完整 |
六、历史亮点回顾(v1–v11 累计 30 条)
七、历史问题修复汇总(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.csSkills/FormSkillSO.cs、SkillModifierRegistry.cs、SkillSlotNames.csVFX/VFXCatalogSO.csEnemies/AI/(BT Task 集合)、Enemies/Boss/(Boss Patterns)World/BreadcrumbTracker.cs、MagicWall.cs、DeathShade.cs、RoomTransition.cs、SavePoint.cs、AbilityGate.cs、AbilityUnlock.csProgression/RegionDefinitionSO.csProgression/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(无已知未修复问题)