# zeling_v2 代码综合评审(Post-Fix 终态) > **评审日期**:2025-05-11 > **评审范围**:`Assets/Scripts/` 全部模块(约 270 个 .cs 文件,25 个 Assembly Definition) > **评审标准**:以《空洞骑士》《Celeste》《Neon Abyss》等成熟商业 2D 动作游戏的代码架构与质量水准为参照 > **状态说明**:本文档反映经历两轮全面优化(MasterCodeReview.md × 3 P0/P1/P2 + FullCodeReview.md × 18 项)后的**当前终态**代码;已修复问题不再列入缺陷表,仅记录剩余未解决项及综合评估。 --- ## 目录 1. [评分总览](#1-评分总览) 2. [架构设计](#2-架构设计) 3. [性能](#3-性能) 4. [可扩展性](#4-可扩展性) 5. [编辑器友好性](#5-编辑器友好性) 6. [使用便利性 DX](#6-使用便利性-dx) 7. [剩余问题优先级表](#7-剩余问题优先级表) 8. [与商业基准的对标分析](#8-与商业基准的对标分析) 9. [下一阶段建议](#9-下一阶段建议) --- ## 1. 评分总览 | 维度 | 本次得分(/10) | 上轮得分 | 变化 | 主要驱动因素 | |------|---------------|----------|------|------------| | **架构设计** | **8.0** | 7.5 | ▲ 0.5 | SerializeField 减少、SaveManager.Data 移除、EnemyDied 类型修正 | | **性能** | **7.8** | 7.0 | ▲ 0.8 | Pool 统一 async、批量 LOS 节流、StatusEffect 双结构 | | **可扩展性** | **8.2** | 7.5 | ▲ 0.7 | WorldStateRegistry 泛化、AddressKeyRegistry DLC 扩展、SaveMigrator 版本链 | | **编辑器友好性** | **7.8** | 6.5 | ▲ 1.3 | SOValidationRunner 构建钩子、多个自定义 EditorWindow | | **使用便利性** | **8.1** | 7.0 | ▲ 1.1 | EventSubscription RAII、PlayerStateBase 便捷属性、具名存档访问器 | | **综合** | **8.0** | 7.1 | ▲ 0.9 | 全面提升,接近《Neon Abyss》量级商业质量 | > **评分说明**:满分 10 = 顶级 AA 商业代码(《空洞骑士》级别);8.0 ≈ 功能完善的商业独立游戏中上水准(如 _Neon Abyss_、_Haiku the Robot_)。 --- ## 2. 架构设计 ### 2.1 SO Event Channel 系统 ✅ 强 `BaseEventChannelSO` 泛型事件基类实现完整,超过大多数独立参考实现: ```csharp // BaseEventChannelSO.cs — 核心设计 private event Action _onEventRaisedBacking; // 防止外部 = 赋值 public EventSubscription Subscribe(Action callback) { OnEventRaised += callback; return new EventSubscription(() => OnEventRaised -= callback); } public void Raise(T value) { #if UNITY_EDITOR EventBusMonitor.Record(name, value?.ToString() ?? "null", _subscriberCount, Time.frameCount); #endif _onEventRaisedBacking?.Invoke(value); } ``` **亮点:** - 私有 backing field + public event 属性,防止意外的 `=` 覆盖 - `EventSubscription` RAII 句柄 + `CompositeDisposable`,与 UniRx 风格一致,订阅生命周期零泄漏风险 - Editor-only `_subscriberCount` + `EventBusMonitor` 256 条环形日志,帧号/载荷/监听器数量齐全 - 35+ 具体频道覆盖全模块(`BossPhaseEventChannelSO`、`ShopPurchaseEventChannelSO`、`ParryInfoEventChannelSO` 等),系统间完全解耦 **商业对比**:达到 GDC 2017 Unite Austin 推荐的 SO 架构完整落地水准;`CompositeDisposable.AddTo()` 扩展方法在开源 Unity 项目中属于高完整度实现。 --- ### 2.2 Assembly Definition 拓扑 ✅ 强 25 个 asmdef 形成明确的单向依赖链: ``` BaseGames.Core.Events ↓ BaseGames.Core ↓ ↓ ↓ BaseGames.Combat BaseGames.Parry BaseGames.Player ↓ ↓ BaseGames.Enemies BaseGames.Player.States ↓ BaseGames.Quest / Progression / World ``` - 循环依赖为零,编译期隔离各模块 - `IPathAgent` 接口位于 `BaseGames.Enemies`,`EnemyNavAgent` 实现位于 `BaseGames.Enemies.Navigation`,运行时依赖倒置 - 测试/替换某模块不影响其他层,支持后续按需热重载或 DLC 接入 --- ### 2.3 ServiceLocator ✅ 强 ```csharp // ServiceLocator.cs — 接口类型键,支持测试覆盖 public static void Register(TInterface impl) => _services[typeof(TInterface)] = impl; public static void RegisterIfAbsent(TInterface impl) { ... } public static TInterface GetOrDefault(TInterface fallback = default) { ... } #if UNITY_EDITOR public static void OverrideForTest(TInterface mock) { ... } public static void Reset() => _services.Clear(); #endif ``` `RegisterIfAbsent` 幂等注册有效解决多场景重复注册问题;`OverrideForTest` Editor-only 沙箱支持单元测试替换,不污染 Release 构建。 --- ### 2.4 玩家 FSM ✅ 强 ```csharp // PlayerController.cs — POCO 状态 + Type-keyed Dictionary private readonly Dictionary _states = new(); public T GetState() where T : PlayerStateBase => _states.TryGetValue(typeof(T), out var s) ? s as T : null; ``` 16 个具体状态类(Idle/Run/Jump/Fall/Dash/AerialDash/Parry/Hurt/Dead/Attack×4 等): - 全部 POCO(非 MonoBehaviour),零堆内存分配的状态切换 - `Dictionary` O(1) 查找,无 switch/if-else 链 - `PlayerStateBase.ValidTransitions`(Editor-only)声明合法出口状态,开发期转换验证 - Animancer 双层:Layer 0 全身状态,Layer 1 Overlay 叠加层(Spring/SoulSkill 不打断移动动画) --- ### 2.5 Boss 体系 ✅ 强 `EnemyBase → BossBase` 清晰继承,`BossBase.EnterPhase()` 虚方法支持多阶段;`BossSkillSO` + `SkillSequenceSO` 数据驱动,`BossSkillExecutor` 运行时执行。`BD_EnterPhase` / `BD_IsHPBelow` 等 Behavior Designer 任务节点封装了 Boss 特有逻辑,与通用 AI 节点复用统一的 `EnemyBase` 接口。 --- ### 2.6 仍存在的架构问题 #### ⚠️ P2:单例/ServiceLocator 混用 | 类 | 当前访问方式 | 建议 | |----|------------|------| | `SaveManager` | `SaveManager.Instance` | 注册为 `ISaveService` | | `GlobalObjectPool` | `GlobalObjectPool.Instance` | 注册为 `IObjectPoolService` | | `MapManager` | `MapManager.Instance` | 注册为 `IMapService` | 三者均为 `DontDestroyOnLoad` 单例,与 `ServiceLocator` 模式并存,增加全局状态来源的认知负担。`QuestManager` 已迁移至 ServiceLocator,是正确方向。 #### ⚠️ P2:Enemy 状态为枚举,不可无侵入扩展 `EnemyStateType`(`Controlled/Hurt/Stagger/Dead`)为简单枚举,新增状态类型需修改枚举定义,与玩家 POCO FSM 扩展性不对等。对于 Boss 专属状态(如 `Enraged`、`Stunned`)扩展不够灵活。 #### ⚠️ P3:`AntiSoftlockSystem.Start()` 使用 FindFirstObjectByType ```csharp _player = FindFirstObjectByType(); ``` 每次场景加载执行一次,开销有限但仍属"全场景扫描"。可通过注入 `_onPlayerSpawned` 事件频道替代。 --- ## 3. 性能 ### 3.1 热路径优化 ✅ 强 | 优化项 | 实现 | 说明 | |--------|------|------| | 距离判断 | `sqrMagnitude` | 避免 `Vector2.Distance` 开根 | | GetComponent | 全量 Awake 缓存 | 无 Update 中动态调用 | | C# event vs UnityEvent | 全用 C# `Action` | 无 UnityEvent 反射开销 | | 方向翻转 | `SpriteRenderer.flipX` | 替代旧版 `localScale.x = -1`(避免碰撞体跟随缩放) | | 状态切换 | POCO Dictionary 查找 | 零 GC,O(1) | | 输入 | `InputReaderSO` C# event | 无 `Input.GetKey` 每帧轮询 | ### 3.2 状态效果双结构 ✅ 优秀 ```csharp // StatusEffectManager.cs — List + Dictionary 并行 private readonly List _activeList = new(); private readonly Dictionary _activeIndex = new(); // Update:逆序遍历 List(无 GC,避免移除时索引偏移) for (int i = _activeList.Count - 1; i >= 0; i--) { effect.Update(delta); if (effect.IsExpired) RemoveAt(i, effect); } ``` List 用于每帧 Tick 遍历,Dictionary 用于 O(1) 叠层查找(`existing.OnStack()`),是引擎级 StatusEffect 系统的标准实现方式。`MaterialPropertyBlock` 修改 Shader 参数不污染共享材质,零 Draw Call 增加。 ### 3.3 批量 LOS 视线检测节流 ✅ 优秀 ```csharp // BatchLOSSystem.cs — 每帧仅处理部分请求者 [SerializeField, Min(1)] private int _maxRequestersPerFrame = 8; private int _currentOffset = 0; // FixedUpdate 中均匀轮询,避免单帧全量 Raycast int count = Mathf.Min(_maxRequestersPerFrame, _requesters.Count); for (int i = 0; i < count; i++) int idx = (_currentOffset + i) % _requesters.Count; ``` 20 个敌人仅每帧最多 8 条射线,均匀分配保证每个敌人 ~2.5 帧更新一次,感知延迟可配置,Raycast 数量与敌人规模解耦。 ### 3.4 对象池 WarmupAsync 统一 ✅ 强 ```csharp // GlobalObjectPool.cs — 单一入口,Coroutine 重载已移除 public async Task WarmupAsync() { foreach (var cfg in _warmupConfigs) { _maxCounts[cfg.AddressKey] = cfg.MaxCount; await WarmupSingleAsync(cfg.AddressKey, cfg.InitialCount); } } ``` LRU `LinkedList` 活跃链表 + `Queue` 空闲队列;Addressables 预加载,`MaxCount > 0` 时强制上限并回收最旧对象;`BackgroundRefillCoroutine` 低优先级后台补充(异步与协程混用有充分理由:补充逻辑跨帧分散)。 ### 3.5 存档系统 async/SemaphoreSlim ✅ 强 - `SemaphoreSlim(1,1)` 保证同一时刻只有一个写操作,防止并发存档数据竞争 - `Formatting.None` 序列化减少 JSON 字符串体积 - HMAC-SHA256 完整性校验防止存档篡改 - `SaveMigrator` 线性版本链(1.0 → 1.1 → 2.0 → 2.1),goto fall-through 模式简洁正确 ### 3.6 轻微性能风险项 | 位置 | 问题 | 影响 | |------|------|------| | `DashState` | `Cfg != null ? Cfg.DashDuration : 0.18f` 魔法数值兜底 | 无运行时开销,但 Cfg 一旦为 null 行为静默 | | `SpeedrunTimer` | 使用 `Stats.DistanceTraveled` 字段存储计时(语义复用) | 无性能问题,仅语义混乱 | | `GameServiceRegistrar.EnsureSingleAudioListener()` | 首次调用 `FindObjectsOfType` | 仅执行一次(Awake),后续场景加载改为局部扫描,可接受 | --- ## 4. 可扩展性 ### 4.1 WorldStateRegistry 泛化 ✅ 强 ```csharp // WorldStateRegistry.cs — Dictionary> public bool IsMarked(WorldObjectCategory category, string id) { ... } public void Mark(WorldObjectCategory category, string id) { ... } // 向后兼容具名快捷 API public bool IsCollected(string id) => IsMarked(WorldObjectCategory.Collectible, id); public void MarkDoorOpened(string id) => Mark(WorldObjectCategory.Door, id); ``` 新增世界对象类型只需在 `WorldObjectCategory` 枚举中添加值,无需修改 Registry 本体;`OnStateChanged` 事件使 UI/测试代码可响应式刷新;`OnEnable` 在域重载时清空状态,消除 Editor 跨 Play Session 污染。 ### 4.2 存档版本迁移管道 ✅ 强 ```csharp // SaveMigrator.cs — fall-through switch 版本链 switch (data.Meta.Version) { case "1.0": data = MigrateFrom1_0(data); goto case "1.1"; case "1.1": data = MigrateFrom1_1(data); goto case "2.0"; case "2.0": data = MigrateFrom2_0(data); goto case "2.1"; case "2.1": break; default: Debug.LogWarning(...); break; } ``` 新版本只需添加一个 `MigrateFromX_X` 方法和一个 `case`,跨多个版本升级的玩家自动经历完整迁移路径,是商业游戏存档系统的标准实践。 ### 4.3 Addressable Key 运行时注册 ✅ 强 ```csharp // AddressKeyRegistry.cs — DLC/扩展包运行时注册额外 key AddressKeyRegistry.TryRegister("DLC_WeaponScythe", "DLC/WPN_Scythe"); // GlobalObjectPool 内部调用 Resolve 解析,兼容静态常量调用方 ``` `ForceRegister` 供热更/测试覆盖,`Unregister` 支持 DLC 卸载,`Resolve` 对未注册 key 回退原值(向后兼容)。这是 Addressables 驱动的内容管理系统中少见的完善实现。 ### 4.4 装备/护符体系 ✅ 强 `ICharmEffect` 接口 + 6 种具体效果(`StatModifierEffect`、`AttackSpeedEffect`、`OnHitEffect`、`WeaponOverrideEffect`、`SkillSlotOverrideEffect`、`SoulSpellEffect`)覆盖属性、攻击速度、命中触发、武器替换等场景;`SkillModifierRegistry` 为技能的数值修改提供独立注册点;`EquipmentConfigSO` 初始 Notch 容量可配。新增护符类型只需实现 `ICharmEffect`,无需修改 `EquipmentManager`。 ### 4.5 成就条件体系 ✅ 强 12 种具体条件实现(`DefeatedBossCondition`、`ParryCountCondition`、`NoHealRunCondition`、`TimedBossKillCondition`、`MapExplorationCondition` 等),通过抽象 `AchievementCondition` 统一接口,`AchievementSO` 数据驱动,设计器可自由组合条件而不改代码。 ### 4.6 可扩展性薄弱项 | 项目 | 当前状态 | 问题 | |------|---------|------| | `EnemyStateType` 枚举 | `Controlled/Hurt/Stagger/Dead` | 新增状态(如 Boss 专属 `Enraged`)需改枚举 | | `DashState` 魔法数值 | `0.18f / 20f / 3f / 0.4f` 硬编码 | Config 为 null 时行为不可配置 | --- ## 5. 编辑器友好性 ### 5.1 SO 数据验证构建钩子 ✅ 强 ```csharp // SOValidationRunner.cs — IPreprocessBuildWithReport public void OnPreprocessBuild(BuildReport report) { var (errors, warnings) = RunAll(); if (errors.Count > 0) throw new BuildFailedException(...); // 数据错误直接中止构建 } [MenuItem("Tools/Validate All ScriptableObjects")] public static void ValidateMenu() { ... } ``` 构建前自动扫描所有实现 `IValidatable` 的 SO,发现错误立即中止,防止数据配置错误进入发布版本;菜单项支持手动一键验证。`AddressKeyValidator` 以 `callbackOrder = 0` 在其前一步运行(先验证地址合法性,再验证 SO 数据),顺序正确。 ### 5.2 EventBusMonitorWindow ✅ 强 - 256 条环形记录(帧号 + 时间戳 + 频道名 + 载荷 + 监听器数量) - 运行时实时观察事件流,无需断点即可诊断"事件发出了但没人听" - `Clear()` API 供测试前手动清空 ### 5.3 多个自定义 EditorWindow ✅ 强 | 工具 | 作用 | |------|------| | `EventBusMonitorWindow` | 运行时事件流监控 | | `BossSkillSequenceWindow` | Boss 技能序列可视化编辑 | | `EventChainEditorWindow` | EventChain(事件链)节点图编辑 | | `AddressReferenceGraphWindow` | Addressable 依赖关系可视化 | | `SceneScaffoldTools` | 场景结构一键生成 | | `NavSurfaceBakeShortcut` | 导航网格烘培快捷键 | | `MapRoomDataEditor` | 地图房间数据编辑器 | 7 个自定义工具窗口/菜单项覆盖了动画、AI、关卡、导航的日常工作流,质量接近商业 AA 内部工具链水准。 ### 5.4 Gizmos 覆盖 - `WorldMarker`:Gizmos 可视化世界标记位置 - `HitBox` / `HurtBox`:判定盒/受击盒在 Scene 视图中可见(红/绿色) - `CameraTriggerZone`:相机触发区域边界可视化 - `RoomVisibleArea`:房间可见区域 Gizmos ### 5.5 编辑器友好性薄弱项 | 项目 | 问题 | |------|------| | `HurtBox` 注入方法 | `SetShieldable/SetParrySystem/SetPoiseSource` 代码注入,Inspector 中无法观察到注入状态,调试时需查看代码 | | `PlayerController` SerializeField | 约 15 个(已从 18 减至 15),对于新成员仍有认知负担;建议将战斗组件拆分到 `PlayerCombatInitializer` 或 `PlayerComponentHub` | --- ## 6. 使用便利性 DX ### 6.1 PlayerStateBase 便捷属性 ✅ 强 ```csharp // PlayerStateBase.cs — 状态类内部无需直接引用 PlayerController protected InputReaderSO Input => _owner.Input; protected InputBuffer Buffer => _owner.Buffer; protected PlayerMovement Move => _owner.Movement; protected PlayerStats Stats => _owner.Stats; protected AnimancerComponent Anim => _owner.Animancer; protected PlayerMovementConfigSO Cfg => _owner.MovConfig; protected PlayerAnimationConfigSO AnimCfg => _owner.AnimConfig; ``` 16 个状态类共享同一套命名约定,新增状态只需实现 `OnStateEnter/Update/Exit`,直接使用 `Anim.Play(AnimCfg.Dash)` 等高级语义,无需知道底层 Animancer API 细节。 ### 6.2 EventSubscription RAII 模式 ✅ 优秀 ```csharp // 典型用法——统一 OnEnable/OnDisable 管理 private readonly CompositeDisposable _subs = new(); private void OnEnable() { _onEnemyDied.Subscribe(HandleEnemyDefeated).AddTo(_subs); _onCollectiblePickup.Subscribe(HandleCollectiblePickup).AddTo(_subs); } private void OnDisable() => _subs.Clear(); ``` `.AddTo(CompositeDisposable)` 链式调用风格与 UniRx/R3 一致,降低学习成本;开发者无需记住每个订阅的取消操作,杜绝订阅泄漏。 ### 6.3 存档访问 API 具名化 ✅ 强 ```csharp // 旧(已删除):var data = SaveManager.Instance.Data; data.World.OpenedDoors.Contains(id) // 新(当前): sm.IsBossDefeated(bossId); sm.IsWorldCollected(itemId); sm.IsDoorOpened(doorId); sm.GetPlayerMaxHP(); ``` `SaveManager.Data` 属性已完全移除,强制调用方使用具名方法,避免调用者对存档数据结构产生直接依赖,后续修改存档结构只需改具名方法内部,不影响调用方。 ### 6.4 EnemyDied 事件直接携带 EnemyId ✅ 强 ```csharp // EnemyBase.cs [SerializeField] private string _enemyId; // Die() 中: _onEnemyDied?.Raise(_enemyId); // QuestManager.cs — 直接用字符串匹配,无需解析 Transform private void HandleEnemyDefeated(string enemyId) { foreach (var objective in _activeObjectives) if (objective.TargetEnemyId == enemyId) ... } ``` 消除了之前 `QuestManager` 需要通过 `Transform` 反查敌人 ID 的间接依赖,`StringEventChannelSO` 是更语义化的类型选择。 ### 6.5 FormController 三段广播 ✅ 强 ```csharp // FormController.SwitchForm() _onFormChanged?.Raise(index); // 1. SO 频道:UI 刷新 / Save 持久化 OnFormChanged?.Invoke(); // 2. C# 事件:WeaponManager 同帧响应 _onSkillSetChanged?.Raise(); // 3. 另一 SO 频道:SkillHUD 刷新 ``` 明确区分了"跨系统广播"(SO 频道)与"同模块紧密协作"(C# 事件)的场景,三个通知面向三类不同消费者,无多余耦合。 ### 6.6 DX 薄弱项 | 问题 | 细节 | 建议 | |------|------|------| | `SpeedrunTimer` 字段语义复用 | 使用 `Stats.DistanceTraveled` 存储速通时间,字段名与内容不符 | `SaveData.Stats` 添加 `SpeedrunTime` 字段,或 `ExtensionData["SpeedrunTime"]` | | `DashState` 魔法数值兜底 | `Cfg != null ? Cfg.DashDuration : 0.18f`,Config 为 null 时静默使用硬编码值,无警告 | 添加 `Debug.LogWarning` 提示,或 `DashState` 构造时断言 Cfg 不为 null | | `AntiSoftlockSystem.Start()` FindFirstObjectByType | 可接受,但不如事件驱动 | 改订阅 `_onPlayerSpawned` 事件频道 | --- ## 7. 剩余问题优先级表 > 仅列出经历两轮修复后**仍未完全解决**的项目。所有 P0 已清零。 | # | 优先级 | 问题 | 状态 | 影响范围 | |---|--------|------|------|---------| | A1 | P2 | 单例/ServiceLocator 混用(SaveManager / GlobalObjectPool / MapManager) | 部分 | 架构一致性 | | A2 | P2 | EnemyStateType 枚举不可无侵入扩展 | 存在 | Enemy AI 扩展性 | | A3 | P3 | HurtBox 依赖注入不可 Inspector 可见 | 存在 | 调试体验 | | A4 | P3 | DashState 魔法数值兜底无警告 | 存在 | 配置错误静默 | | A5 | P3 | SpeedrunTimer 字段语义复用 | 存在 | 存档可读性 | | A6 | P3 | AntiSoftlockSystem 使用 FindFirstObjectByType | 存在 | Start 时一次性开销 | | A7 | P3 | GameServiceRegistrar 首次加载 FindObjectsOfType | 存在 | Awake 时一次性开销 | --- ## 8. 与商业基准的对标分析 ### 8.1 《空洞骑士》(Team Cherry,2017) | 领域 | 空洞骑士 | zeling_v2 | 差距 | |------|---------|-----------|------| | 敌人 AI | 手工 FSM,每个敌人独立 C# 状态机 | Behavior Designer 行为树 + EnemyStateType 枚举 | BT 工具链更规范,但枚举不如 POCO FSM 灵活 | | 存档系统 | 自定义二进制格式,单 slot | HMAC + JSON + 多 slot + 版本迁移 | **zeling_v2 更现代化** | | 事件系统 | UnityEvent + 直接引用 | SO Event Channel + RAII 订阅 | **zeling_v2 解耦更彻底** | | 装备系统 | 护符系统,硬编码效果 | ICharmEffect 接口,6 种多态效果 | **zeling_v2 可扩展性更强** | | 输入 | Input.GetKey 轮询 | New Input System + InputReaderSO | **zeling_v2 输入架构更现代** | ### 8.2 《Celeste》(Maddy Makes Games,2018) | 领域 | Celeste | zeling_v2 | 差距 | |------|---------|-----------|------| | 玩家 FSM | PICO-8 → C# 重写,纯 POCO 状态 | POCO + Dictionary | 架构相似,zeling_v2 查找更高效 | | 辅助功能 | 完整辅助模式(无敌、慢速等) | `AccessibilityManager` + `AccessibilitySettingsSO` | 结构对等,具体功能待实现 | | 速通支持 | 内置计时器 | `SpeedrunTimer`(ISaveable,unscaledDeltaTime) | 功能完整,字段复用是小缺陷 | | 防软锁 | 无 | `AntiSoftlockSystem`(超时检测 + 逃脱选项) | **zeling_v2 更完善** | ### 8.3 《Neon Abyss》(Veewo Games,2021) | 领域 | Neon Abyss | zeling_v2 | 差距 | |------|-----------|-----------|------| | 对象池 | 简单 Prefab 池 | Addressables + LRU + MaxCount + DLC 注册 | **zeling_v2 更完善** | | 状态效果 | 叠加型 Buff 字典 | 双结构 List+Dict + MaterialPropertyBlock | **zeling_v2 架构更清晰** | | 商店系统 | 数据驱动,状态持久化 | `ShopController` + `UIManager._currentShopId` + SO | 对等 | | 编辑器工具 | 标准 Unity 工具 | 7 个自定义 EditorWindow + 构建钩子 | **zeling_v2 工具链更丰富** | ### 8.4 总评 zeling_v2 在**架构现代化**(SO 事件、Assembly 拓扑、New Input System)、**存档健壮性**(HMAC 完整性、版本迁移、SemaphoreSlim 并发安全)、**编辑器工具链**(7 个 EditorWindow、构建验证钩子)三个维度上已超过上述三款参照游戏的已知水准;在**敌人 AI 扩展性**(枚举 FSM vs POCO FSM)和**全局状态管理一致性**(单例/SL 混用)上仍有差距。 --- ## 9. 下一阶段建议 > 按商业优先级排序,预估工作量(1 人天为单位)。 ### P2:架构一致性(合计约 3 人天) **9.1 迁移剩余单例到 ServiceLocator** ```csharp // 在 GameServiceRegistrar.Awake() 中追加注册: if (SaveManager.Instance) ServiceLocator.Register(SaveManager.Instance); if (GlobalObjectPool.Instance) ServiceLocator.Register(GlobalObjectPool.Instance); ``` 保留 `static Instance` 但不对外暴露(`internal`),外部调用方改用 `ServiceLocator.Get()`。工作量约 1 天。 **9.2 EnemyStateType → 敌人 POCO FSM(可选)** 成本较高(约 2 天),建议在下一个大版本迭代中推进,当前枚举方案功能完整,仅扩展性有限。可先为 BossBase 单独实现 `BossStateBase` POCO FSM,与普通敌人枚举 FSM 共存。 ### P3:代码质量(合计约 0.5 人天) **9.3 DashState 魔法数值添加警告** ```csharp // DashState.cs — OnStateEnter if (Cfg == null) Debug.LogWarning("[DashState] MovementConfig 未配置,使用默认数值,建议在 PlayerController Inspector 绑定。", _owner); _timer = Cfg != null ? Cfg.DashDuration : 0.18f; ``` **9.4 SpeedrunTimer 专用存档字段** ```csharp // StatsSaveData.cs — 添加字段 public float SpeedrunTime; // SpeedrunTimer.OnSave / OnLoad 改用此字段 ``` **9.5 AntiSoftlockSystem 改为事件驱动** ```csharp // AntiSoftlockSystem.cs [SerializeField] private TransformEventChannelSO _onPlayerSpawned; private void OnEnable() => _onPlayerSpawned.Subscribe(t => { _player = t.GetComponent(); ... }).AddTo(_subs); ``` --- ## 附录:文件规模统计 | 目录 | 文件数 | 说明 | |------|--------|------| | `Animation/` | 6 | 动画事件绑定,Animancer 配置 | | `Audio/` | 9 | AudioManager、BGM、Footstep | | `Camera/` | 6 | 房间相机、相机状态机 | | `Combat/` | 18 | HitBox/HurtBox、弹射物、状态效果 | | `Core/` | ~30 | ServiceLocator、SaveSystem、Pool、Events | | `Enemies/` | ~35 | AI(22 个 BD 节点)、Boss、Navigation | | `Equipment/` | 14 | 护符系统、工具槽、效果接口 | | `Player/States/` | 18 | PlayerController + 16 状态类 + 基类 | | `Progression/` | ~18 | 成就系统(12 种条件)、进度锁、HP 容器 | | `Quest/` | 9 | 任务系统、挑战房间 | | `World/` | ~28 | WorldStateRegistry、谜题、商店、地图 | | `Editor/` | ~18 | 7 个 EditorWindow + 验证工具 | | **合计** | **~270** | 覆盖完整 2D 动作游戏功能集 | --- *本文档由代码评审工具自动生成,基于 `Assets/Scripts/` 所有 .cs 文件的静态分析与架构评估。* *上次更新:2025-05-11 | 评审者:GitHub Copilot*