Files
zeling_v2/Docs/Review/MasterReview_2025_PostFix.md
2026-05-12 15:34:08 +08:00

25 KiB
Raw Blame History

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. 评分总览
  2. 架构设计
  3. 性能
  4. 可扩展性
  5. 编辑器友好性
  6. 使用便利性 DX
  7. 剩余问题优先级表
  8. 与商业基准的对标分析
  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 AbyssHaiku the Robot)。


2. 架构设计

2.1 SO Event Channel 系统

BaseEventChannelSO<T> 泛型事件基类实现完整,超过大多数独立参考实现:

// 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+ 具体频道覆盖全模块(BossPhaseEventChannelSOShopPurchaseEventChannelSOParryInfoEventChannelSO 等),系统间完全解耦

商业对比:达到 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.EnemiesEnemyNavAgent 实现位于 BaseGames.Enemies.Navigation,运行时依赖倒置
  • 测试/替换某模块不影响其他层,支持后续按需热重载或 DLC 接入

2.3 ServiceLocator

// 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

// 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.ValidTransitionsEditor-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 状态为枚举,不可无侵入扩展

EnemyStateTypeControlled/Hurt/Stagger/Dead)为简单枚举,新增状态类型需修改枚举定义,与玩家 POCO FSM 扩展性不对等。对于 Boss 专属状态(如 EnragedStunned)扩展不够灵活。

⚠️ P3AntiSoftlockSystem.Start() 使用 FindFirstObjectByType

_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 状态效果双结构 优秀

// 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 视线检测节流 优秀

// 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 统一

// 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 泛化

// 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 存档版本迁移管道

// 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 运行时注册

// AddressKeyRegistry.cs — DLC/扩展包运行时注册额外 key
AddressKeyRegistry.TryRegister("DLC_WeaponScythe", "DLC/WPN_Scythe");
// GlobalObjectPool 内部调用 Resolve 解析,兼容静态常量调用方

ForceRegister 供热更/测试覆盖,Unregister 支持 DLC 卸载,Resolve 对未注册 key 回退原值(向后兼容)。这是 Addressables 驱动的内容管理系统中少见的完善实现。

4.4 装备/护符体系

ICharmEffect 接口 + 6 种具体效果(StatModifierEffectAttackSpeedEffectOnHitEffectWeaponOverrideEffectSkillSlotOverrideEffectSoulSpellEffect)覆盖属性、攻击速度、命中触发、武器替换等场景;SkillModifierRegistry 为技能的数值修改提供独立注册点;EquipmentConfigSO 初始 Notch 容量可配。新增护符类型只需实现 ICharmEffect,无需修改 EquipmentManager

4.5 成就条件体系

12 种具体条件实现(DefeatedBossConditionParryCountConditionNoHealRunConditionTimedBossKillConditionMapExplorationCondition 等),通过抽象 AchievementCondition 统一接口,AchievementSO 数据驱动,设计器可自由组合条件而不改代码。

4.6 可扩展性薄弱项

项目 当前状态 问题
EnemyStateType 枚举 Controlled/Hurt/Stagger/Dead 新增状态(如 Boss 专属 Enraged)需改枚举
DashState 魔法数值 0.18f / 20f / 3f / 0.4f 硬编码 Config 为 null 时行为不可配置

5. 编辑器友好性

5.1 SO 数据验证构建钩子

// 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发现错误立即中止防止数据配置错误进入发布版本菜单项支持手动一键验证。AddressKeyValidatorcallbackOrder = 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 覆盖

  • WorldMarkerGizmos 可视化世界标记位置
  • HitBox / HurtBox:判定盒/受击盒在 Scene 视图中可见(红/绿色)
  • CameraTriggerZone:相机触发区域边界可视化
  • RoomVisibleArea:房间可见区域 Gizmos

5.5 编辑器友好性薄弱项

项目 问题
HurtBox 注入方法 SetShieldable/SetParrySystem/SetPoiseSource 代码注入Inspector 中无法观察到注入状态,调试时需查看代码
PlayerController SerializeField 约 15 个(已从 18 减至 15对于新成员仍有认知负担建议将战斗组件拆分到 PlayerCombatInitializerPlayerComponentHub

6. 使用便利性 DX

6.1 PlayerStateBase 便捷属性

// 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 模式 优秀

// 典型用法——统一 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 具名化

// 旧已删除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

// 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 三段广播

// 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.18fConfig 为 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 结构对等,具体功能待实现
速通支持 内置计时器 SpeedrunTimerISaveableunscaledDeltaTime 功能完整,字段复用是小缺陷
防软锁 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

// 在 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 魔法数值添加警告

// DashState.cs — OnStateEnter
if (Cfg == null)
    Debug.LogWarning("[DashState] MovementConfig 未配置,使用默认数值,建议在 PlayerController Inspector 绑定。", _owner);
_timer = Cfg != null ? Cfg.DashDuration : 0.18f;

9.4 SpeedrunTimer 专用存档字段

// StatsSaveData.cs — 添加字段
public float SpeedrunTime;
// SpeedrunTimer.OnSave / OnLoad 改用此字段

9.5 AntiSoftlockSystem 改为事件驱动

// 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