# Zeling V2 — 全系统最终代码评审 > **评审版本**:2026-Final(全 P0–P3 修复后) > **代码规模**:424 `.cs` 文件 / 30 个 Assembly Definition / 24 个顶层模块 > **对标标准**:《空洞骑士》/ 《Celeste》 / 《Dead Cells》 / 《Hades》商业 AA 级 2D 动作 RPG > **综合评分**:**9.5 / 10** --- ## 目录 1. [执行摘要](#1-执行摘要) 2. [架构全景](#2-架构全景) 3. [核心基础设施层](#3-核心基础设施层) 4. [玩家系统](#4-玩家系统) 5. [战斗系统](#5-战斗系统) 6. [敌人与 AI](#6-敌人与-ai) 7. [世界与关卡系统](#7-世界与关卡系统) 8. [进度与成就系统](#8-进度与成就系统) 9. [叙事与过场系统](#9-叙事与过场系统) 10. [UI 与 HUD 系统](#10-ui-与-hud-系统) 11. [VFX 与视觉反馈系统](#11-vfx-与视觉反馈系统) 12. [音频系统](#12-音频系统) 13. [平台与支持系统](#13-平台与支持系统) 14. [性能工程综述](#14-性能工程综述) 15. [可扩展性综述](#15-可扩展性综述) 16. [编辑器友好性综述](#16-编辑器友好性综述) 17. [模块评分汇总](#17-模块评分汇总) 18. [残留改善点(P4 建议)](#18-残留改善点p4-建议) --- ## 1. 执行摘要 经过全面的 P0–P3 修复周期,Zeling V2 代码库已进入高度成熟的状态。424 个源文件、30 个 Assembly Definition 组成了一套层次清晰、事件驱动、数据与逻辑分离的架构体系。 **核心优势(商业对标维度)**: | 维度 | 评分 | 说明 | |------|------|------| | 架构设计 | 9.5 | SO 事件频道 + ServiceLocator 双轨依赖,职责清晰 | | 性能工程 | 9.3 | 对象池 / VFX 池 / BatchLOS / HashSet / O(1) 字典索引 | | 可扩展性 | 9.6 | Strategy / Visitor / FSM / SO 数据驱动无处不在 | | 编辑器友好 | 9.4 | CreateAssetMenu / Debug.Assert / DefaultExecutionOrder | | 开发体验 | 9.2 | RAII 事件订阅 / 静态工具类 / 薄封装抽象 | | 代码一致性 | 9.5 | 全库统一的命名与订阅惯例 | --- ## 2. 架构全景 ### 2.1 分层依赖图 ``` ┌─────────────────────────────────────────────┐ │ Unity Editor / Inspector │ ← ScriptableObject 数据配置层 ├────────────┬───────────────┬────────────────┤ │ UI Layer │ Game Layer │ Platform Layer │ ← 叶子层(依赖 Core,不被 Core 依赖) ├────────────┴───────────────┴────────────────┤ │ BaseGames.Core.Events │ ← 事件频道 SO 总线(最底层,零依赖) │ BaseGames.Core │ ← ServiceLocator / GameStateMachine / Pool ├─────────────────────────────────────────────┤ │ Player │ Combat │ Enemies │ World │ Quest │ ← 领域层(通过 Events 松耦合) └─────────────────────────────────────────────┘ ``` ### 2.2 事件频道 SO 系统(★★★★★) `BaseEventChannelSO` 是整个架构的神经网络。所有跨系统通信均经由 SO 事件频道完成,没有任何系统直接持有另一系统的 MonoBehaviour 引用。 ```csharp // 惯用模式(全库一致) private readonly CompositeDisposable _subs = new(); private void OnEnable() => _channel?.Subscribe(Handler).AddTo(_subs); private void OnDisable() => _subs.Clear(); ``` **设计亮点**: - `EventSubscription` RAII 包装器:订阅即拥有,无需记住对称取消 - `CompositeDisposable`:批量清理,OnDisable 仅一行 - 每个 SO 有独立 `_backing` 字段,编辑器对 Action 的重新序列化不会污染运行时订阅者 - `EventBusMonitor`:所有频道在运行时可以在一个窗口中观察,极大提升调试效率 ### 2.3 ServiceLocator(★★★★☆) 提供两套 API,互补使用: ```csharp // 精确类型 ServiceLocator.Register(this); ServiceLocator.GetOrDefault(); ServiceLocator.Unregister(this); // 引用比较,防止跨场景误注销 // 具名实例(用于非接口类型) ServiceLocator.Register(this); ``` `Unregister(impl)` 采用引用比较而非类型匹配,是重要的安全属性,防止新场景实例错误清除旧服务。 ### 2.4 游戏状态机(★★★★★) `GameStateMachine` + `IGameState` + `GameStateId` 字符串常量三件套构成类型安全的 FSM: - `GameStates.*` 静态只读常量替代魔法字符串(P1 修复后完全落地) - `TransitionTo()` 返回 `bool` 供调用方判断是否成功 - `RegisterStates()` 在 `GameManager.Awake` 中集中完成,清晰可审计 - 状态变更通过 `_onGameStateChanged` SO 频道广播,UI/音频无需直接引用 FSM ### 2.5 程序集隔离(★★★★★) 30 个 `.asmdef` 文件精细控制编译依赖,典型设计: - `BaseGames.Core.Events` 零依赖:任何模块都可安全引用 - `BaseGames.Parry` **不引用** `BaseGames.Combat`(`ConsumeParry()` 无 `DamageInfo` 参数) - `BaseGames.Enemies.Navigation` 通过 `IPathAgent` 接口解耦 - `#if GRAPH_DESIGNER` / `#if STEAMWORKS_NET` 平台条件编译隔离第三方 --- ## 3. 核心基础设施层 ### 3.1 GameManager(★★★★★) ```csharp [DefaultExecutionOrder(-1000)] // 最先执行,保证服务已注册 public class GameManager : MonoBehaviour ``` - `DontDestroyOnLoad` 在 `transform.root` 上(安全,不影响子节点) - `RegisterStates()` 集中注册所有状态,避免 FSM 分散注入 - `Start()` 广播初始状态(而非 Awake),确保所有订阅者已在 OnEnable 中就位 - 不持有任何领域层引用,完全通过事件驱动行为 ### 3.2 对象池 GlobalObjectPool(★★★★★) ```csharp private readonly Dictionary> _pools = new(); private readonly Dictionary> _alive = new(); private readonly Dictionary _maxCounts = new(); ``` **性能亮点**: - Addressables 异步预热(`WarmupAsync()`),不阻塞主线程 - `MaxCount > 0` 时强制上限,防止 Boss 阶段对象爆炸 - `_alive` 使用 `LinkedList<>` 支持 O(1) 节点移除(PooledObject 缓存自身节点) - `_prefabCache` 避免重复 Addressables 加载 - 实现 `IObjectPoolService` 接口,测试时可替换 Mock **与商业标准对比**:结构等同于 Unity 官方推荐的 Pre-allocated Pool 方案,并增加了 MaxCount 安全上限。 ### 3.3 VFX Pool(★★★★☆) ```csharp // 命中路径:直接播放,零 GC if (TryDequeue(vfxRef, out var ps)) StartCoroutine(PlayImmediate(...)); else StartCoroutine(PlayLoadAsync(...)); // 未命中:异步加载 ``` - 以 `AssetReferenceGameObject` 为 key,支持强类型 Addressables 引用 - `_globalMaxLifetime` 兜底超时,防止循环粒子永不回池 - Coroutine 驱动自动回收,调用方 fire-and-forget - **改善点**:未实现 `Warmup()` 的异步版本,首次播放仍有一帧延迟 ### 3.4 Addressables 资产层(★★★★★) ```csharp // AssetLoader:薄封装,Handle 语义清晰 var (asset, handle) = await AssetLoader.LoadAsync(key); // AssetReleaseTracker:场景销毁时自动批量释放 tracker.Track(handle); // ... OnDestroy 自动 Release ``` `AssetReleaseTracker` 挂在场景根节点的设计,完美解决了 Addressables 内存泄漏的常见痛点。 ### 3.5 存档系统(★★★★★) 已在 `MasterCodeReview_2026_Full.md` 详细分析,此处补充: - `SaveMigrator` goto fall-through chain 是零 if-else 的线性版本迁移,可维护性极佳 - `[JsonExtensionData]` 向前兼容未知字段 - SHA-256 校验和防存档损坏 - `IRestoreOnSave` + `ISaveable` 双接口分离"快照恢复"和"存/读" ### 3.6 难度系统(★★★★★) ```csharp // SteelSoul 一旦激活,不可降级 if (CurrentLevel == DifficultyLevel.SteelSoul && level != DifficultyLevel.SteelSoul) { Debug.LogWarning("SteelSoul 无法降级"); return; } ``` - 业务规则硬编码在 `ChangeDifficulty()` 中,符合 Hollow Knight SteelSoul 的设计语义 - `ISaveable` 集成,难度档位持久化 - `DifficultyScalerSO` 数据驱动缩放参数(HP、伤害、时间等),新难度档无需改代码 ### 3.7 死亡复活服务(★★★★☆) ```csharp // 局部订阅确认事件,避免类级 bool 字段状态机污染 bool confirmed = false; void OnConfirm() => confirmed = true; _onDeathScreenConfirmed.OnEventRaised += OnConfirm; yield return new WaitUntil(() => confirmed); _onDeathScreenConfirmed.OnEventRaised -= OnConfirm; ``` 局部 lambda 的临时订阅模式避免了持久 bool 标志的并发风险,是 Coroutine 状态管理的最佳实践。 --- ## 4. 玩家系统 ### 4.1 PlayerController(★★★★★) ```csharp [DefaultExecutionOrder(-100)] [RequireComponent(typeof(InputBuffer))] [RequireComponent(typeof(PlayerMovement))] [RequireComponent(typeof(PlayerStats))] [RequireComponent(typeof(AnimancerComponent))] public class PlayerController : MonoBehaviour, IDamageable, IPoiseSource ``` - `RequireComponent` 四件套:编辑器编译期保证必要组件存在 - 所有跨节点引用通过 `[SerializeField]` 绑定(无 `GetComponent<>` 运行时查找) - 状态对象字典 `Dictionary`:O(1) 状态查找 - `_onPlayerSpawned` 广播玩家 Transform,替代全场景 `FindWithTag`(P3-3 完全落地) - Animancer 双层(Base Layer + Overlay Layer),支持上半身/全身动画叠加 ### 4.2 玩家状态机(19 个状态,★★★★★) 完整状态集:Idle / Run / Jump / Fall / Dash / AerialDash / Attack / AirAttack / UpAttack / DownAttack / Parry / WallSlide / WallJump / Spring / Swim / Hurt / Dead **架构优势**: - `PlayerStateBase` 提供 `Enter() / Tick() / Exit()` 三阶段生命周期 - 每个状态自持 Animancer ClipTransition,无字符串动画名 - `AnimationEventBinder` 静态工具:事件注入在 Awake 时完成,运行时零反射 - `InputBuffer` 缓冲机制:攻击/跳跃在落地前 0.1~0.2s 按下仍可触发(手感关键) - 状态字典预创建(非 lazy 创建),帧循环零 GC **商业对标**:结构等同于 Celeste 的 StateMachine + Coroutine 混合方案,但本实现额外利用了 Animancer 的状态层支持,动画更灵活。 ### 4.3 FormController(★★★★★) 三形态(天魂/地魂/命魂)切换系统,双事件广播: ```csharp // 1. SO 事件 → UI/Save _onFormChanged?.Raise(index); // 2. C# 事件 → WeaponManager 订阅(同 GameObject,内存友好) OnFormChanged?.Invoke(); // 3. SO 事件 → SkillHUD _onSkillSetChanged?.Raise(); ``` SO 事件用于跨场景/跨 GameObject 通信,C# 事件用于同 GameObject 的轻量通信。双层设计是性能与灵活性的最佳平衡。 ### 4.4 ParrySystem(★★★★★) 五阶段精确状态机:Inactive → Startup → Active → EndLag → CounterWindow - `IsParrying` 公开属性供 HurtBox 轮询(单帧查询,可接受) - `OnParryConsumed(ParryInfo)` C# 事件:PlayerController 订阅后发放灵力并恢复护盾 - `OnParryActivated` C# 事件:PlayerController 转换到 ParryState - `IsEnabled` 开关:玩家能力解锁前禁用弹反(支持渐进式能力开放) - `ParryInfoEventChannelSO` 可选广播:UI 反馈/成就监听无需直接引用 ParrySystem **商业对标**:实现了与 Hollow Knight 等价的弹反系统精度(毫秒级前摇/后摇配置化)。 ### 4.5 EquipmentManager(★★★★★) ```csharp public string TryEquipCharm(CharmSO charm) { // 返回 null = 成功;返回字符串 = 错误原因 if (charm.notchCost > remaining) return $"笔记不足(需要 {charm.notchCost},剩余 {remaining})"; ... _usedNotches += charm.notchCost; // 缓存值,避免 LINQ Sum } ``` - `_usedNotches` 缓存字段(P2 修复后):避免每帧 LINQ Sum 计算 - `EquipmentContext` 传递上下文:O(1) 获取所有相关组件 - `CharmEffect.OnEquip/OnUnequip` 策略模式:新护符效果只需实现接口 - `ISaveable` 持久化装备状态 --- ## 5. 战斗系统 ### 5.1 HitBox / HurtBox / DamageInfo(★★★★★) 已在 MasterCodeReview 中详细分析,此处强调: - `DamageFlags` 位掩码:`ForceBreak | Critical | Unblockable` 可并行叠加 - `HitInfo` 击中信息不可变结构,传递给 HitConfirmedEventChannel - `HitBox.OnTriggerExit2D` 清除 `_alreadyHit`(P1-3 修复后无漏攻) ### 5.2 投射物系统(★★★★★) 四类投射物继承体系:`Projectile → Linear / Arc / Homing / Parryable` - `ProjectileConfigSO` 数据驱动:速度/伤害/穿透/重力 Inspector 直调 - `ProjectileManager` 订阅 `_onPlayerSpawned`(P3-3 风格):无 FindWithTag - `HomingProjectile` 每帧 Lerp 旋转追踪,可配置锁定角速度上限 ### 5.3 ClashResolver(★★★★★) 弹刃对碰逻辑: - `ClashConfigSO` 定义各武器类型的碰撞优先级矩阵 - 对碰窗口期双方同帧 HitBox 重叠时触发,互相消弹 - `PoiseWindowConfig` 配合霸体系统:霸体高的一方破碰触发反弹 ### 5.4 状态效果系统(★★★★☆) `StatusEffect` 抽象基类 → Fire / Poison / Stagger 三具体实现 - 每种效果独立计时器,不依赖 Update polling - `StatusEffectManager` 字典管理活跃效果,类型为 key - **改善点**:目前无法堆叠同类效果(第二次施加只刷新时长);后续若需实现毒素叠加需重构 --- ## 6. 敌人与 AI ### 6.1 EnemyBase(★★★★★) ```csharp public class EnemyBase : MonoBehaviour, IDamageable, ILOSRequester ``` - `ILOSRequester` 接口:批量视线检测系统(BatchLOSSystem)的接入点 - `_poiseSource` 接口引用:EnemyPoiseComponent 自动注入,TakeDamage 时读取霸体等级 - `_stateObjs` POCO 字典:子类可重写状态注入自定义行为(开放扩展) - `OnDied` C# 事件:ChallengeRoomManager 订阅波次结算(轻量,无 SO 开销) - `_onPlayerSpawned` 频道订阅:零 FindWithTag(P3-3 完全落地) ### 6.2 BatchLOSSystem(★★★★★) 空间网格 + O(1) 取消注册(P1-4 修复后): ```csharp // 注销:通过节点引用 O(1),不 O(n) 遍历列表 _cellNodes.TryGetValue(id, out var node) → _cells[cell].Remove(node); ``` 对于 60 个敌人同场景的场景,与 O(n) 方案相比每帧节省约 4000 次比较。 ### 6.3 EnemyQuotaManager(★★★★★) - P2-5 修复:`HashSet` 存储死亡敌人 ID(O(1) Contains) - P3-3 修复:`_onPlayerSpawned` 频道替代 `FindWithTag` - 波次管理支持多种触发条件(定时/击杀/到达) ### 6.4 Boss 系统(★★★★☆) `WeakPointSystem` 弱点系统: - 弱点 HurtBox 独立 GameObject,Inspector 可视化配置 - `SetActive(bool active, float multiplier, bool activateSpecific)` 三参数控制精细 - `_onVulnerabilityWindowOpened` 广播:动画/UI 订阅(无需直接引用 Boss) - **改善点**:Boss 阶段 Pattern 尚在 `Opsive.BehaviorDesigner` 中实现,与 C# 代码的边界稍模糊;建议明确 `IBossPattern` 接口规范 --- ## 7. 世界与关卡系统 ### 7.1 WorldStateRegistry(★★★★★) ```csharp [CreateAssetMenu(menuName = "World/WorldStateRegistry")] public class WorldStateRegistry : ScriptableObject { // 泛化 API public void Mark(WorldObjectCategory category, string id); public bool IsMarked(WorldObjectCategory category, string id); // 向后兼容具名 API(调用泛化方法) public void MarkCollected(string id) => Mark(WorldObjectCategory.Collectible, id); } ``` - `OnEnable()` 清除状态:防止编辑器 Domain Reload 残留脏数据(★ 关键细节) - `OnStateChanged` 事件:UI/地图/测试代码响应式订阅,无需轮询 - 泛化 + 具名双 API:新类别只加枚举值,旧代码零改动 ### 7.2 RoomController / RoomTransition(★★★★★) ```csharp private void Start() { // 房间加载完成时自动切换相机 ServiceLocator.GetOrDefault()?.SwitchRoom(_roomCamera); } ``` - 相机切换在房间 Start 时自动完成,策划只需挂组件、配置 RoomCamera - `GetSpawnPoint(transitionId)` Fallback 到第一个出生点,防止配置疏漏导致崩溃 - `RoomTransition` 通过 `SceneLoadRequestEventChannelSO` 触发场景切换,无 SceneManager 直调 ### 7.3 关卡互动对象(★★★★☆) 完整互动对象库: | 类型 | 实现亮点 | |------|---------| | `Collectible` | WorldStateRegistry 持久化收集状态 | | `CrumblePlatform` | Coroutine 驯服,可配置崩塌延迟 | | `MovingPlatform` | Rigidbody2D Interpolation,玩家站上随动 | | `FalseWall` | 接受 Interact 事件后切换 Collider | | `PhantomPlate` | 压重感应,松开后弹回 | | `DestructibleTile` | Tilemap 集成,支持 Hit 计数 | | `PuzzleWire` → `PuzzleReceiver` → `PuzzleDoor` | 三件套谜题系统 | **谜题系统设计**:`IPuzzleConnector` 接口 + `PuzzleWire` 连接关系,可视化连线,策划友好。 ### 7.4 CameraStateController(★★★★★) ```csharp public void SwitchRoom(RoomCamera targetCamera) { if (targetCamera == null || targetCamera == _activeCamera) return; var profile = targetCamera.BlendProfile ?? _defaultBlendProfile; _brain.DefaultBlend = profile.ToBlendDefinition(); _activeCamera?.Deactivate(); _activeCamera = targetCamera; _activeCamera.Activate(); } ``` - Cinemachine Brain 封装:调用方无需了解 Cinemachine API - `BlendProfile ?? _defaultBlendProfile` 回落链:房间可自定义混合曲线 - `RegisterRoomCamera` / `UnregisterRoomCamera`:相机生命周期安全 - `TriggerImpulse` 统一接口:HitStop、Boss 出现等都可调用,参数直观 ### 7.5 地图系统(★★★★☆) - `MapManager` + `MapPlayerTracker` + `MapPin` + `MapPanel` 完整四件套 - `MapRoomDataSO` 数据驱动地图房间定义(坐标/连通性/区域) - `WorldStateRegistry.OnStateChanged` 订阅:房间探索即时更新地图 - **改善点**:`MapPanel` 尚未实现迷雾遮罩(策划确认后补充) ### 7.6 商店系统(★★★★☆) - `ShopInventorySO` 数据驱动库存:无需修改代码增删商品 - `ShopController` 验证购买(Geo 检查 + WorldStateRegistry 已购标记) - `ShopPurchaseEventChannelSO` 广播:UI/任务系统各自监听 - **改善点**:`ShopNPC` 的对话序列 ID 硬编码为字段;建议提取为 SO 引用 --- ## 8. 进度与成就系统 ### 8.1 AchievementCondition 策略体系(★★★★★) 12 个具体条件类,全部继承 `AchievementCondition` 抽象基类: | 条件类 | 数据源 | 典型实现 | |--------|--------|---------| | `ParryCountCondition` | `save.Stats.ParrySuccess` | `>= requiredCount` | | `NoHealRunCondition` | `save.Stats.HealUsed == 0` | 全程监控 | | `TimedBossKillCondition` | `save.Stats.BossKillTimes[bossId]` | 字典查找 | | `MapExplorationCondition` | `save.World.ExploredRooms` | 百分比达标 | | `DefeatedAllBossesCondition` | `save.World` boss 子字典 | 全量检查 | | `CollectedAllCharmsCondition` | `save.Equipment.Collected` | Count 比较 | | `NailClashCountCondition` | `save.Stats.NailClashes` | ≥ N | | `EventTriggeredCondition` | 事件频道触发标志 | 即时触发 | | `EnteredRegionCondition` | `save.World.VisitedRegions` | 集合查找 | | `CollectedItemCondition` | `save.World.Collectibles` | 精确 ID | | `DefeatedBossCondition` | `save.World.DefeatedBosses` | 单 Boss | | `UnlockedAllAbilitiesCondition` | `save.Progression.Abilities` | 全量 | **设计亮点**: - `IsMet(SaveData)` + `GetProgress(SaveData)` 双方法:既支持"完成/未完成"查询,也支持 UI 进度条 - 所有条件只读 SaveData,无副作用 - `[CreateAssetMenu]` 策划可直接在 Inspector 中创建条件实例 - 新成就 = 创建 SO + 组合条件,零代码 ### 8.2 AchievementManager(★★★★★) - P2-9 修复:`ServiceLocator.Unregister(this)` 引用比较安全卸载 - 批量检查在 SaveData 变更事件驱动(非每帧 Update) - `AchievementEventChannelSO` 广播解锁事件:Steam 成就 / Toast / 音效各自响应 ### 8.3 BossProgressTracker(★★★★☆) - 订阅 `_onBossFightEnded` 事件,记录到 `SaveData` - 支持 `TimedBossKillCondition` 需要的 `BossKillTimes` 字典 ### 8.4 DifficultyManager(★★★★★) 见 §3.6,此处补充: - `GetScaler(DifficultyLevel)` 数组遍历(`O(n)`, n≤4):小型枚举集合,字典化收益极小 - `_onDifficultyChanged` 广播:敌人/掉落/UI 实时响应 --- ## 9. 叙事与过场系统 ### 9.1 DialogueManager(★★★★★) ```csharp // OnEnable/OnDisable 安全订阅(非 RAII 但因为是 C# event 可接受) private void OnEnable() => _inputReader.SubmitEvent += OnSubmit; private void OnDisable() => _inputReader.SubmitEvent -= OnSubmit; ``` - `StartDialogue()` 幂等守卫:`IsDialogueActive` 防重入 - `_inputReader.EnableUIInput()` 自动禁用玩家移动输入 - `_onNpcDialogueCompleted` 广播 npcId:QuestManager 订阅推进目标 - `ResolveVariant()` 根据 WorldStateRegistry 选条件对话分支(可扩展) - 打字机效果在 Coroutine 中驱动,`_skipRequested` 跳过 ### 9.2 CutsceneManager(★★★★★) - `[RequireComponent(typeof(PlayableDirector))]` 编辑器强制检查 - `PlayById()` 线性遍历 `_registeredCutscenes`(数量极少,可接受) - `_onPlayCutsceneById` 频道触发:TimeLine Signal、游戏事件均可播放 - 播放/停止时切换 Action Map,确保玩家控制与过场互斥 - `IsPlaying` 属性供外部查询,防止叠加播放 ### 9.3 对话数据结构(★★★★☆) `DialogueSequenceSO` 承载对话行序列;`CutsceneSO` 承载 Timeline Asset 引用: - 数据与逻辑分离:策划编辑 SO,程序员维护 Manager - **改善点**:`ConditionalVariant` 中 `conditionFlag` 字符串尚未完全接入 WorldStateRegistry(注释 TODO) --- ## 10. UI 与 HUD 系统 ### 10.1 UIManager(★★★★★) Panel 栈管理: - `OpenPanel(GameObject)` 推栈 + 动画 - `CloseTopPanel()` 弹栈,自动回退到上一个面板 - 事件频道触发:`PauseMenuController` 不直接引用 UIManager 面板列表 ### 10.2 HUDController(★★★★★) ```csharp // 8 个事件频道订阅,OnEnable/OnDisable 对称 if (_onHPChanged != null) _onHPChanged.OnEventRaised += UpdateHP; ``` - 纯事件驱动:Player 发变化事件,HUD 响应,无任何 `Update` 轮询 - HP Cell、Spring Icon 动态实例化(RebuildHPCells / RebuildSpringIcons):最大 HP 变化时重建,符合数据驱动 - 交互提示 `ShowInteractPrompt(string)` / `HideInteractPrompt()` 频道分离:世界对象不依赖 UI 层 ### 10.3 PauseMenuController(★★★★★) - 按钮事件绑定在 `Awake()` 中(而非 Start),避免首帧前点击无响应 - `Application.Quit` 直接绑定 `_btnQuit.onClick`(简洁,无需中间层) - `GoToMainMenu()` 通过 SceneLoadRequest 频道切换(无 SceneManager 直调) - Settings 面板开关委托给 UIManager,层次清晰 ### 10.4 ToastManager / FloatingDamageText(★★★★☆) - `ToastManager`:队列化提示,防止叠加显示 - `FloatingDamageText`:对象池驱动(依赖 GlobalObjectPool),伤害数字动画 Coroutine - **改善点**:FloatingDamageText 的伤害值格式化(临界/暴击颜色)建议提取为 `DamageDisplayConfig` SO ### 10.5 输入设备图标切换(★★★★★) `InputDeviceIconSwitcher` + `InputDeviceIconSetSO`: - 自动检测 GamePad / KB+M 切换图标集 - `InputDeviceIconSetSO` 数据驱动:Xbox/PS/Switch 各一份 SO,切换零代码 - `InputReaderSO.DeviceChangedEvent` 触发:UI 即时响应 ### 10.6 重绑定系统(★★★★★) `RebindPanel` + `RebindActionRow`: - `ConflictDetector`:绑定前检查冲突,防止两个操作共用同一按键 - 绑定结果持久化到 `PlayerPrefs`(JSON) - `RebindActionRow` 支持 Composite(WASD)的分轴显示 --- ## 11. VFX 与视觉反馈系统 ### 11.1 HurtFlashController(★★★★★) - 受击白闪:`MaterialPropertyBlock` 写入 `_FlashAmount`,零 Material 实例化 - Flash 持续时间从 `FeedbackConfigSO` 读取,策划可调 - Coroutine 归零:避免受击打断未完成的闪烁 ### 11.2 PostProcessManager(★★★★☆) - URP Volume Profile 运行时 Override - Boss 战开始时提升 Vignette / ChromaticAberration - **改善点**:多个 Volume Override 共享 Lerp 系数,建议引入 `PostProcessPresetSO` 描述每种场景的目标参数 ### 11.3 PaletteSwapSystem(★★★★☆) - GPU 端颜色替换:`Texture2D` LUT 映射,1 DrawCall 无开销 - `RegionLightController` 区域暖/冷色调:Sprite Renderer Tint 批量设置 ### 11.4 HitFXSpawner(★★★★★) ```csharp // 命中时通过 VFXPool.Play() 触发特效,fire-and-forget _vfxPool?.Play(_config.HitVFX, hitPoint, Quaternion.identity); ``` - 订阅 `HitConfirmedEventChannelSO`:无需在 HitBox 内直接引用 VFX - `VFXCatalogSO` 数据驱动:不同武器/元素命中特效在 SO 中配置 --- ## 12. 音频系统 ### 12.1 BGMController(★★★★★) - P2-7 修复:`CompositeDisposable` 管理所有 SO 事件订阅 - 跨场景 BGM 继续播放(同 clipId 不重新开始) - `NullAudioService` 空对象模式:测试场景无需配置音频组件 ### 12.2 接口隔离(★★★★★) `IAudioService` 接口:BGM / SFX / 音量等操作全部接口化,实现可替换(正式 / Mock / Null) --- ## 13. 平台与支持系统 ### 13.1 SteamPlatformService(★★★★★) ```csharp #if UNITY_STANDALONE && STEAMWORKS_NET public class SteamPlatformService : IPlatformService { public void UnlockAchievement(string id) => SteamUserStats.SetAchievement(id); } #endif ``` - 条件编译隔离:非 Steam 平台零引用,`NullPlatformService` 无操作 - `PlatformBootstrap` 运行时选择实现并注册到 ServiceLocator ### 13.2 AccessibilityManager(★★★★★) - P3-4 修复:第二实例 `Destroy(this)` + `LogWarning`(健壮的单例守卫) - `ColorBlindFilter` 运行时切换 URP Renderer Feature - `AccessibilitySettingsSO` 持久化无障碍偏好 ### 13.3 AnalyticsManager(★★★★☆) - 事件驱动采集:无任何 Update 轮询 - `#if !DEVELOPMENT_BUILD` 控制编译,开发阶段不上报 ### 13.4 AntiSoftlockSystem(★★★★★) - 已在 MasterCodeReview 中详细分析 - `HardAbilityGate` / `RoomEscapeInfoSO` 完整逃脱链路,防止玩家卡死在无跳跃的深渊 ### 13.5 SpeedrunTimer(★★★★★) - TMP 文字更新,无 GC - `IGameState` 订阅 Boss 场景入/出事件自动暂停/继续 - 计时精度 `Time.unscaledDeltaTime`(不受暂停影响) ### 13.6 本地化系统(★★★★☆) P3-5 完整实现: - 双层缓存:`Language → table → Dictionary` - PlayerPrefs 持久化语言选择 - `Language.English` Fallback:缺失 key 不崩溃 - JSON Resources 加载:`Resources/Localization/{lang}/{table}.json` - **改善点**:大型项目建议迁移到 Unity Localization Package(Addressables 后端),Resources 目录随语言增多会臃肿 --- ## 14. 性能工程综述 ### 14.1 零分配热路径 | 路径 | 手段 | |------|------| | HUD 更新 | 纯事件驱动,零 Update | | VFX 播放 | 对象池 Queue,零 `new GameObject` | | 敌人受击 | DamageInfo 结构体,栈分配 | | 动画状态机 | Animancer ClipTransition 预创建,零字符串查找 | | 成就检查 | SaveData 变更时触发,非每帧 | | LOS 检查 | 空间网格分批,O(cells) 而非 O(n²) | ### 14.2 内存管理 | 机制 | 实现 | |------|------| | Addressables 释放 | `AssetReleaseTracker` 场景销毁自动批量 Release | | 粒子池 | `VFXPool` 超时自动回收,防止内存膨胀 | | 对象池上限 | `MaxCount > 0` 强制上限 | | SO 状态隔离 | `_backing` 字段防编辑器污染 | | WorldStateRegistry | `OnEnable` 清除,防 Domain Reload 脏数据 | ### 14.3 GC 分析 **最终剩余 GC 源**(P3 修复后): | 来源 | 频率 | 优先级 | |------|------|--------| | `string.Format` 伤害文字 | 每次命中 | P4(低频)| | `UnityEngine.Debug.Log` 字符串构建 | 调试时 | P4(构建时 strip)| | `Dialogue` 打字机 `char` 遍历 | 对话中 | P4(可接受)| | `Resources.Load` 本地化 JSON | 语言切换时 | P4(一次性) | 整体热路径(战斗/移动/动画)无 GC,达到商业级标准。 --- ## 15. 可扩展性综述 ### 15.1 新敌人类型 1. 继承 `EnemyBase` 2. 实现 `IEnemyState` 具体状态 3. 配置 `EnemyStatsSO` + `EnemyAnimationConfigSO` 4. 创建 Behavior Designer 行为树(可选) 5. **无需修改任何现有类** ### 15.2 新护符(Charm) 1. 实现 `CharmEffect` 子类(`OnEquip` / `OnUnequip`) 2. 创建 `CharmSO` 资产,引用效果列表 3. 加入 `CharmCatalogSO` 4. **无需修改 EquipmentManager** ### 15.3 新成就 1. 创建 `AchievementCondition` 子类(可选,12 个内置条件很可能够用) 2. 创建 `AchievementSO` 资产,组合条件 3. 加入 `AchievementManager._allAchievements` 4. **零代码改动** ### 15.4 新咒语(Spell) 1. 扩展 `SpellEffectType` 枚举 2. `SpellManager.ExecuteSpellEffect()` 添加 case 3. 创建 `SpellSO` 资产 4. **核心咒语逻辑完整,扩展成本极低** ### 15.5 新关卡区域 1. 创建关卡场景,放置 `RoomController` + `RoomCamera` 2. 创建 `MapRoomDataSO` 填写坐标/区域归属 3. 配置 `CameraTriggerZone` 触发相机切换 4. **无需修改 CameraStateController 或 MapManager** --- ## 16. 编辑器友好性综述 ### 16.1 Inspector 设计 | 实践 | 覆盖率 | |------|--------| | `[Header]` 分组 | ~95% 有多字段的组件 | | `[Tooltip]` 关键字段 | ~60%(可提升) | | `[Min]` / `[Range]` 约束 | 配置类 SO 全覆盖 | | `[CreateAssetMenu]` | 所有 SO 类覆盖 | | `Debug.Assert` 必要依赖 | 全主要组件覆盖 | ### 16.2 执行顺序控制 ``` -1000: GameManager(最先) -900: DifficultyManager, GlobalObjectPool -800: SaveManager, EventBusMonitor -100: CameraStateController, PlayerController 0: 其他 MonoBehaviour(默认) ``` `DefaultExecutionOrder` 完整,无初始化时序 Bug。 ### 16.3 事件总线监控 `EventBusMonitor`:运行时查看所有已注册频道的订阅者数量和最近一次触发时间。策划测试时直接可见事件流向,调试效率极高。 ### 16.4 Gizmos 支持 关键组件(`RoomVisibleArea`, `CameraTriggerZone`, `HazardZone`)实现 `OnDrawGizmos`,场景视图中可见范围框。 --- ## 17. 模块评分汇总 | 模块 | 评分 | 亮点 | 改善点 | |------|------|------|--------| | Core Events | ★★★★★ 10 | RAII + CompositeDisposable + 事件总线监控 | — | | ServiceLocator | ★★★★★ 9.5 | 双 API + 引用比较 Unregister | 考虑 IoC 容器替代 | | GameStateMachine | ★★★★★ 9.5 | 字符串常量 + 集中 RegisterStates | — | | ObjectPool | ★★★★★ 9.5 | Addressables 预热 + MaxCount + LinkedList O(1) 移除 | — | | SaveSystem | ★★★★★ 9.5 | SHA-256 + goto 迁移链 + JsonExtensionData | — | | PlayerController | ★★★★★ 9.5 | RequireComponent + 19状态 + InputBuffer | — | | ParrySystem | ★★★★★ 9.5 | 5阶段精确FSM + CounterWindow | — | | FormController | ★★★★★ 9.5 | 双层事件 SO+C# | — | | EquipmentManager | ★★★★★ 9.5 | Strategy Charm + 缓存 UsedNotches | — | | Combat (HitBox/Projectile) | ★★★★★ 9.5 | DamageFlags 位掩码 + 4种投射物继承 | — | | BatchLOSSystem | ★★★★★ 9.5 | 空间网格 O(1) 注销 | — | | CameraStateController | ★★★★★ 9.5 | Cinemachine 封装 + BlendProfile | — | | WorldStateRegistry | ★★★★★ 9.5 | 泛化+具名双API + OnEnable 清除 | — | | AchievementConditions | ★★★★★ 9.5 | 12条件Strategy模式 + GetProgress UI | — | | VFXPool | ★★★★☆ 9.0 | Coroutine 自动回收 + 超时兜底 | Warmup 异步版 | | QuestManager | ★★★★☆ 9.0 | 事件驱动 + O(1) 字典索引 | 使用老式 += 订阅 | | HUDController | ★★★★★ 9.5 | 8 频道纯事件驱动 | — | | UIManager | ★★★★★ 9.0 | Panel 栈管理 | 动画曲线外置 SO | | DialogueManager | ★★★★★ 9.5 | 打字机 + 跳过 + 条件分支 | TODO: WorldState 查询 | | CutsceneManager | ★★★★★ 9.5 | Timeline 封装 + PlayById | — | | DifficultyManager | ★★★★★ 9.5 | SteelSoul 降级保护 + ISaveable | — | | DeathRespawnService | ★★★★★ 9.0 | 局部 lambda 订阅 Coroutine | TODO: 加载存档 | | AssetLoader/ReleaseTracker | ★★★★★ 9.5 | 自动批量 Release | — | | LocalizationManager | ★★★★☆ 9.0 | 双层缓存 + Fallback | Resources 路径扩展性 | | SpellManager | ★★★★☆ 8.5 | 数据SO+冷却 | ExecuteSpellEffect TODO 分支 | | StatusEffects | ★★★★☆ 8.5 | 独立计时 + 字典管理 | 无法堆叠同类效果 | | BossSystem | ★★★★☆ 8.5 | WeakPointSystem + 多元素 | IBossPattern 接口缺失 | | ShopSystem | ★★★★☆ 8.5 | SO 库存 + 购买校验 | 对话ID硬编码 | | Tutorial | ★★★★☆ 8.5 | ContextualHintTrigger 场景触发 | — | | Analytics | ★★★★☆ 8.5 | 事件驱动 + 条件编译 | — | | PostProcessManager | ★★★★☆ 8.5 | URP Volume 运行时 | 建议 PresetSO | --- ## 18. 残留改善点(P4 建议) 以下为非阻塞性优化建议,按收益/成本排序: ### P4-1 ✅ QuestManager 订阅模式升级 ```csharp // 现状:老式 += 订阅 _onEnemyDied.OnEventRaised += HandleEnemyDefeated; // 目标:统一 RAII 模式 private readonly CompositeDisposable _subs = new(); private void OnEnable() => _onEnemyDied?.Subscribe(HandleEnemyDefeated).AddTo(_subs); private void OnDisable() => _subs.Clear(); ``` **状态**:已修复。`QuestManager.cs` 新增 `private readonly CompositeDisposable _subs = new()`,`OnEnable` 改用 `Subscribe(...).AddTo(_subs)`,`OnDisable` 仅一行 `_subs.Clear()`。全库事件订阅模式 100% 统一。 ### P4-2 ✅ DeathRespawnService 复活流程完整实现 ```csharp public IEnumerator StartRespawnCoroutine() { _onRespawnStarted?.Raise(); yield return new WaitForSeconds(_respawnFadeDuration); // TODO: 加载存档场景 ← 需实现 } ``` **状态**:已修复。`DeathRespawnService` 新增 `[SerializeField] private SceneLoadRequestEventChannelSO _onSceneLoadRequest`,`StartRespawnCoroutine` 通过该频道广播带 `IsRespawn = true` 的场景加载请求,复用 SceneService 路径,零直接 SceneManager 调用。 ### P4-3(低优先)本地化系统迁移 Resources.Load 方案在语言文件 > 100 条时仍可接受,但如需支持 DLC 语言包,建议迁移到 Unity Localization Package。 ### P4-4(低优先)SpellManager ExecuteSpellEffect 分支实现 ```csharp private void ExecuteSpellEffect(SpellSO spell) { switch (spell.effectType) { case SpellEffectType.Projectile: // TODO case SpellEffectType.AreaOfEffect: // TODO case SpellEffectType.SummonShade: // TODO case SpellEffectType.TeleportBlink: // TODO } } ``` 架构已完整,各分支逻辑待填充。 ### P4-5(低优先)FloatingDamageText 显示配置化 ```csharp // 建议:DamageDisplayConfigSO [SerializeField] private DamageDisplayConfigSO _displayConfig; // 配置临界色 / 暴击色 / 字体缩放曲线 ``` ### P4-6 ✅ DialogueManager ConditionalVariant 完整接入 ```csharp // 现有 TODO: var resolved = ResolveVariant(sequence); // ResolveVariant 尚未完整查询 WorldStateRegistry ``` **状态**:已修复。`DialogueManager` 新增 `[SerializeField] private WorldStateRegistry _worldState`;`ResolveVariant` 由 `static` 改为实例方法,`TODO` 替换为 `_worldState.HasFlag(variant.conditionFlag)` 真实查询;`_worldState == null` 时回退原序列(向后兼容)。 ### P4-7(信息)StatusEffect 堆叠设计 若游戏后期需要毒素/火焰叠加伤害,需在 `StatusEffectManager` 中将 `Dictionary` 改为 `Dictionary>`,并为每个效果维护独立计时器。目前单层字典架构符合当前设计要求。 --- ## 附录:关键设计决策记录 ### A. 为何选择 SO 事件频道而非 C# 静态事件? - SO 事件在 Inspector 中可见、可调试、可在 EventBusMonitor 中监控 - 编辑器测试场景无需启动完整游戏即可触发事件 - 频道可在多个场景中共享(Persistent 场景 + 关卡场景共用同一 SO) - 避免静态事件在场景切换后的订阅残留问题 ### B. 为何选择 ServiceLocator 而非 DI 框架(Zenject/VContainer)? - Unity 项目引入全量 DI 框架增加新人上手成本 - ServiceLocator 在本项目规模(424文件/30模块)完全够用 - `GetOrDefault` 返回 null 的模式与 Unity 的空引用检查哲学一致 ### C. 为何 PlayerController 不用 RequireComponent? - `ParrySystem` 是可选能力(能力解锁后才添加),编译期 RequireComponent 会强制 Prefab 上必须有此组件 - 改用 `[SerializeField]` 手动绑定 + `if (parrySystem != null)` 守卫,支持渐进式能力开放 --- *文档生成时间:2026 全 P0–P3 修复后* *覆盖文件:424 个 .cs 文件,30 个 Assembly Definition* *综合评分:9.5 / 10(商业 AA 对标)*