多轮审查和修复

This commit is contained in:
2026-05-12 15:34:08 +08:00
parent f55d2a57c3
commit ebbbb7332e
805 changed files with 838724 additions and 1905 deletions

View File

@@ -0,0 +1,560 @@
# 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<T>` 泛型事件基类实现完整,超过大多数独立参考实现:
```csharp
// BaseEventChannelSO.cs — 核心设计
private event Action<T> _onEventRaisedBacking; // 防止外部 = 赋值
public EventSubscription Subscribe(Action<T> 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>(TInterface impl)
=> _services[typeof(TInterface)] = impl;
public static void RegisterIfAbsent<TInterface>(TInterface impl) { ... }
public static TInterface GetOrDefault<TInterface>(TInterface fallback = default) { ... }
#if UNITY_EDITOR
public static void OverrideForTest<TInterface>(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<Type, PlayerStateBase> _states = new();
public T GetState<T>() 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<Type, PlayerStateBase>` 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是正确方向。
#### ⚠️ P2Enemy 状态为枚举,不可无侵入扩展
`EnemyStateType``Controlled/Hurt/Stagger/Dead`)为简单枚举,新增状态类型需修改枚举定义,与玩家 POCO FSM 扩展性不对等。对于 Boss 专属状态(如 `Enraged``Stunned`)扩展不够灵活。
#### ⚠️ P3`AntiSoftlockSystem.Start()` 使用 FindFirstObjectByType
```csharp
_player = FindFirstObjectByType<PlayerController>();
```
每次场景加载执行一次,开销有限但仍属"全场景扫描"。可通过注入 `_onPlayerSpawned` 事件频道替代。
---
## 3. 性能
### 3.1 热路径优化 ✅ 强
| 优化项 | 实现 | 说明 |
|--------|------|------|
| 距离判断 | `sqrMagnitude` | 避免 `Vector2.Distance` 开根 |
| GetComponent | 全量 Awake 缓存 | 无 Update 中动态调用 |
| C# event vs UnityEvent | 全用 C# `Action<T>` | 无 UnityEvent 反射开销 |
| 方向翻转 | `SpriteRenderer.flipX` | 替代旧版 `localScale.x = -1`(避免碰撞体跟随缩放) |
| 状态切换 | POCO Dictionary 查找 | 零 GCO(1) |
| 输入 | `InputReaderSO` C# event | 无 `Input.GetKey` 每帧轮询 |
### 3.2 状态效果双结构 ✅ 优秀
```csharp
// StatusEffectManager.cs — List + Dictionary 并行
private readonly List<StatusEffect> _activeList = new();
private readonly Dictionary<StatusEffectType, StatusEffect> _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<PooledObject>` 活跃链表 + `Queue<PooledObject>` 空闲队列Addressables 预加载,`MaxCount > 0` 时强制上限并回收最旧对象;`BackgroundRefillCoroutine` 低优先级后台补充(异步与协程混用有充分理由:补充逻辑跨帧分散)。
### 3.5 存档系统 async/SemaphoreSlim ✅ 强
- `SemaphoreSlim(1,1)` 保证同一时刻只有一个写操作,防止并发存档数据竞争
- `Formatting.None` 序列化减少 JSON 字符串体积
- HMAC-SHA256 完整性校验防止存档篡改
- `SaveMigrator` 线性版本链1.0 → 1.1 → 2.0 → 2.1goto fall-through 模式简洁正确
### 3.6 轻微性能风险项
| 位置 | 问题 | 影响 |
|------|------|------|
| `DashState` | `Cfg != null ? Cfg.DashDuration : 0.18f` 魔法数值兜底 | 无运行时开销,但 Cfg 一旦为 null 行为静默 |
| `SpeedrunTimer` | 使用 `Stats.DistanceTraveled` 字段存储计时(语义复用) | 无性能问题,仅语义混乱 |
| `GameServiceRegistrar.EnsureSingleAudioListener()` | 首次调用 `FindObjectsOfType<AudioListener>` | 仅执行一次Awake后续场景加载改为局部扫描可接受 |
---
## 4. 可扩展性
### 4.1 WorldStateRegistry 泛化 ✅ 强
```csharp
// WorldStateRegistry.cs — Dictionary<WorldObjectCategory, HashSet<string>>
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 Cherry2017
| 领域 | 空洞骑士 | 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 Games2018
| 领域 | Celeste | zeling_v2 | 差距 |
|------|---------|-----------|------|
| 玩家 FSM | PICO-8 → C# 重写,纯 POCO 状态 | POCO + Dictionary<Type, State> | 架构相似zeling_v2 查找更高效 |
| 辅助功能 | 完整辅助模式(无敌、慢速等) | `AccessibilityManager` + `AccessibilitySettingsSO` | 结构对等,具体功能待实现 |
| 速通支持 | 内置计时器 | `SpeedrunTimer`ISaveableunscaledDeltaTime | 功能完整,字段复用是小缺陷 |
| 防软锁 | 无 | `AntiSoftlockSystem`(超时检测 + 逃脱选项) | **zeling_v2 更完善** |
### 8.3 《Neon Abyss》Veewo Games2021
| 领域 | 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<ISaveService>(SaveManager.Instance);
if (GlobalObjectPool.Instance)
ServiceLocator.Register<IObjectPoolService>(GlobalObjectPool.Instance);
```
保留 `static Instance` 但不对外暴露(`internal`),外部调用方改用 `ServiceLocator.Get<ISaveService>()`。工作量约 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<PlayerController>(); ... }).AddTo(_subs);
```
---
## 附录:文件规模统计
| 目录 | 文件数 | 说明 |
|------|--------|------|
| `Animation/` | 6 | 动画事件绑定Animancer 配置 |
| `Audio/` | 9 | AudioManager、BGM、Footstep |
| `Camera/` | 6 | 房间相机、相机状态机 |
| `Combat/` | 18 | HitBox/HurtBox、弹射物、状态效果 |
| `Core/` | ~30 | ServiceLocator、SaveSystem、Pool、Events |
| `Enemies/` | ~35 | AI22 个 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*