# zeling_v2 深度代码评审 2026 > **评审日期**:2026-01 > **评审范围**:`Assets/Scripts/` 全部 ~415 个 `.cs` 文件 > **评审标准**:以同等体量商业 2D 动作游戏(Hollow Knight / Celeste / Hades)代码质量为基准 > **代码终态**:本轮评审基于所有 P1-P3 修复全部落地后的最终版本 --- ## 评分总览 | 维度 | 得分(/10) | 说明 | |------|------------|------| | 架构设计 | **8.2** | 程序集隔离 + ServiceLocator + SO Event Channel 构成优秀骨架,少量单例混用 | | 性能 | **8.0** | 热路径零分配设计扎实,BatchLOS 时间切片;小处存在可优化空间 | | 可扩展性 | **8.3** | 数据驱动策略模式普遍应用,工厂字典 + 版本迁移链完备 | | 编辑器友好 | **8.2** | 工具链完整(监控/验证/可视化),Post-fix 后类型化,个别窗口健壮性待补 | | 使用便利性(DX) | **8.2** | API 命名清晰,订阅生命周期管理完善,混用模式尚存,部分接口语义有歧义 | | **综合** | **8.2** | 同等体量独立游戏上游水准,离头部商业游戏代码约 0.5–1.0 分距离 | --- ## 一、架构设计 ### 1.1 程序集隔离(Assembly Definitions) 25 个 `.asmdef` 文件将模块划分为独立编译单元,强制单向依赖: ``` Core → Input → Player → Combat → Enemies → World → ... ``` - **无循环依赖**:`BaseGames.Parry` 不引用 `BaseGames.Combat`,`ConsumeParry()` 签名无 `DamageInfo` 参数——这种取舍有意为之,体现了架构约束的一致贯彻。 - **接口切面**:`ILOSRequester`、`IDamageable`、`IPoiseSource`、`IStatusEffectable`、`IObjectPoolService`、`IPlatformService` 等接口均定义在依赖链上游,让下游实现以干净方式向上暴露能力。 - **条件编译护栏**:`#if GRAPH_DESIGNER`、`#if STEAMWORKS_NET`、`#if UNITY_EDITOR` 三类编译守卫严格隔离平台/工具代码,防止运行时包体污染。 **评价**:程序集设计达到商业中等水准。大型工作室(如 Team Cherry)同样采用类似分层策略。 --- ### 1.2 服务定位器(ServiceLocator) ```csharp // 静态字典,Unity 主线程单线程访问,无锁竞争 private static readonly Dictionary _services = new(); public static T Get() // 缺失时抛出异常,快速失败 public static T GetOrDefault() // 缺失时返回 null,防御性访问 public static void RegisterIfAbsent(...) // 幂等注册 #if UNITY_EDITOR public static void OverrideForTest(...) // 测试注入点 #endif ``` - **设计优点**:单责任(查找/注册),`Get()` 快速失败语义明确,`GetOrDefault()` 防御性访问分开,Editor 测试注入不污染运行时。 - **局限**:静态状态在 Domain Reload 后需要手动清理(Unity 在 `[InitializeOnLoad]` 中已有对应处理);构造图对新人不可见。 **对比**:Hades 的引擎使用类似的 Service Locator(而非 DI 容器),同规模项目此方案已足够。 --- ### 1.3 SO Event Channel 系统 ```csharp // BaseEventChannelSO private event Action _action; // 私有 backing field,防止外部 = 覆盖 public event Action Action { add => _action += value; remove => _action -= value; } // 订阅返回 IDisposable,支持 CompositeDisposable.AddTo() 生命周期绑定 public EventSubscription Subscribe(Action handler) { ... } ``` | 特性 | 评估 | |------|------| | 私有 backing field | ✅ 防外部 `=` 覆盖,只能 `+=/-=` | | EventSubscription(readonly struct) | ✅ 零分配,IDisposable 自动反订阅 | | CompositeDisposable.AddTo() | ✅ 生命周期与 MonoBehaviour 绑定,无泄漏 | | EventBusMonitor 256 条环形记录 | ✅ 运行时事件流可观测 | | Editor 订阅者计数 | ✅ 0 订阅者时红色高亮警告 | **最强设计点**:`EventSubscription` 作为 `readonly struct` 实现 `IDisposable`,避免了大量项目中常见的"忘记取消订阅"内存泄漏问题,与 UniRx/R3 的 Disposable 模式对齐。 --- ### 1.4 状态机体系 **玩家状态机**(`PlayerStateBase` + `PlayerController`) - 纯 C# 状态对象(无 MonoBehaviour 开销),构造器注入依赖,`ValidTransitions` 白名单 `#if UNITY_EDITOR` 校验。 - `Dictionary _states` O(1) 查找,无反射。 **游戏状态机**(`GameStateMachine`) - 纯 C# 类(不挂 MonoBehaviour),`Dictionary` + `ValidNextStates` 白名单,`TransitionTo()` 失败返回 `error` 字符串而非抛异常。 **敌人状态机**(`EnemyBase._stateObjs`) - `Dictionary` POCO 实现,状态由子类在 Awake 填充,基类不假定具体状态集。 **弹反状态机**(`ParrySystem`) - 枚举 `ParryPhase { Inactive, Startup, Active, EndLag, CounterWindow }` 清晰定义相变,`Update` 驱动计时,`IsParrying`/`IsInCounterWindow` 布尔属性暴露只读状态。 **潜在问题**: - `GameStateMachine.TransitionTo` 中 `_current.ValidNextStates.Contains()` 若 `ValidNextStates` 为 `IEnumerable`(线性查找),在频繁转换时存在微小 O(n) 开销。建议内部改用 `HashSet`。 --- ### 1.5 存档系统 ``` SaveManager(SemaphoreSlim 并发锁) ├── ISaveable(注册接口) ├── ISaveStorage(LocalFileStorage 实现) ├── SaveMigrator(goto 版本链 1.0→1.1→2.0→2.1) └── SaveData(Newtonsoft.Json 序列化) ``` - `SemaphoreSlim(1,1)` 防止并发写入损坏文件。 - Checksum 两步计算(先 null→序列化→计算→填入→再序列化)正确且清晰注释。 - `SaveMigrator` 的 `goto case` 是 C# 语言规范中唯一正确的 switch-fallthrough 写法,并非 bad practice。 **混用模式问题(-0.2 分)**:`SaveManager.Instance`、`VFXPool.Instance`、`GlobalObjectPool.Instance` 为传统单例,而其他服务均通过 `ServiceLocator` 访问。其中 `GlobalObjectPool` 在 Awake 同时注册 `ServiceLocator.Register(this)`,形成双重访问路径,容易造成混淆。 --- ### 1.6 架构设计待改进项 | 问题 | 影响 | 建议 | |------|------|------| | `SaveManager.Instance` 未接入 ServiceLocator | 模式不一致,测试困难 | `ISaveManager` 接口 + ServiceLocator 注册 | | `AnalyticsManager` 无 namespace 声明 | 全局命名空间污染 | 添加 `namespace BaseGames.Support.Analytics` | | `PlayerController.GetCurrentPoiseLevel()` 硬返回 `PoiseLevel.None` | 误导性 API,调用者以为玩家有霸体 | 注释说明或移除方法,改为常量字段 | | `IGameState.ValidNextStates` 为 `IEnumerable` | 状态转换 O(n) | 改为 `IReadOnlySet` 实现 | --- ## 二、性能 ### 2.1 热路径零分配设计 | 类型 | 技术 | 效果 | |------|------|------| | `DamageInfo` | `struct` + `From()` 工厂 | 无堆分配 | | `EventSubscription` | `readonly struct IDisposable` | 无堆分配 | | `HurtBox` 8 步管线 | 纯 struct/值类型操作 | 每帧命中无 GC 压力 | | `HitBox` 命中去重 | `HashSet` | O(1) per check | | `MaterialPropertyBlock` | StatusEffectManager 渲染 | 不产生材质实例 | | `Newtonsoft.Json Formatting.None` | SaveManager | 减小序列化字符串体积 | | `ValidTransitions` 白名单 | `#if UNITY_EDITOR` 只在 Editor 执行 | 运行时无额外开销 | --- ### 2.2 BatchLOSSystem 时间切片 ```csharp [DefaultExecutionOrder(-200)] // 保证在 EnemyBase.FixedUpdate 之前执行 private const int _maxRequestersPerFrame = 8; // 均匀旋转偏移,避免每帧处理同一批请求 private int _offset; for (int i = 0; i < _maxRequestersPerFrame; i++) { int idx = (_offset + i) % _requesters.Count; // ... raycast + callback } _offset = (_offset + _maxRequestersPerFrame) % _requesters.Count; ``` **设计优点**:无论敌人数量多少,每帧固定 8 次 Raycast,时间复杂度 O(1) per frame。通过轮转偏移保证每个请求者以均匀频率得到更新。 **待优化(-0.15 分)**: - `_requesters` 使用 `List`,`Register` 时内部 `Contains()` 为 O(n)。当场景内存在 >30 个敌人时,批量注册阶段(关卡加载)会产生 O(n²) 开销。建议改用 `HashSet` 或 `(List + HashSet)` 双结构。 --- ### 2.3 VFXPool(P3-10 修复后) ``` Play(vfxRef, position, ...) ├── TryDequeue() 命中 → PlayImmediate(同帧播放,无 Addressables 等待) └── 未命中 → PlayLoadAsync(异步加载 + 实例化) ``` 修复前每次 `Play()` 即使池中已有实例也要经过一帧协程等待。修复后池命中路径 **0 帧延迟**,与 Celeste 的预热粒子池策略一致。 --- ### 2.4 GlobalObjectPool - `WarmupAsync()` 预热,避免运行时 Addressables.InstantiateAsync 延迟。 - `Dictionary>` 池 + `Dictionary>` 活跃链表:O(1) 入队/出队 + O(1) 强制回收(LinkedList.Remove 为 O(1) 已知节点)。 - `MaxCount > 0` 限制池 + 活跃对象总量,防止无限扩张。 --- ### 2.5 性能待改进项 | 问题 | 位置 | 严重度 | 建议 | |------|------|--------|------| | `BatchLOSSystem._requesters.Contains()` O(n) | `Register()` | 中 | 改用 `HashSet` | | `EquipmentManager.UsedNotches` 每次调用 LINQ `Sum()` | 属性 getter | 低 | 维护 `_usedNotches` 缓存字段,装备/卸下时增减 | | `AnalyticsManager.Track()` 创建 `new Dictionary` | 每次调用 | 低(非热路径) | 在 Gameplay 密集调用点使用静态预分配 | | `DamageInfo` 非 `readonly struct` | `DamageInfo.cs` | 低 | 标记为 `readonly struct`,Builder 内部操作局部变量 | | `EventBusMonitor.Queue` struct 装箱 | Editor only | 极低 | 换 `EventRecord[]` 环形数组 + int head/tail | | `ClashResolver` XOR key 碰撞 | `ResolveClash()` | 极低 | 用 `(int, int)` 对元组替代 XOR | --- ## 三、可扩展性 ### 3.1 数据驱动架构(ScriptableObject) 以下系统全面采用 SO 数据驱动: | 系统 | SO 类型 | 可配置项 | |------|---------|----------| | 护符 | `CharmSO` + `ICharmEffect[]` | 策略模式,效果任意组合 | | 技能 | `FormSkillSO` + `SkillSlotOverride` | 护符可覆盖技能槽 | | Boss 技能 | `BossSkillSO` + `SkillSequenceSO` | Windup/Active/Recovery 三段配置 | | 伤害源 | `DamageSourceSO` | 直接 `DamageInfo.From(so)` 零分配构建 | | 弹反配置 | `ParryConfigSO` | 前摇/后摇/反击窗口毫秒级可调 | | 装备 | `EquipmentConfigSO` | 初始 Notch 数 | | 世界状态 | `WorldStateRegistry` (SO) | `OnEnable` 自动清理,Editor 安全 | **`ICharmEffect` 策略模式**是可扩展性最强的设计:新护符效果只需实现接口,无需修改任何管理器代码,完全符合开闭原则。 --- ### 3.2 StatusEffect 工厂字典(P2-5 修复后) ```csharp // 修复前:静态 switch,新效果必须修改 StatusEffectManager 源码 // 修复后: private readonly Dictionary> _effectFactories = new(); public void RegisterEffectFactory(DamageType type, Func factory) => _effectFactories[type] = factory; // Boss 模块可在运行时注册自定义效果 StatusEffectManager.RegisterEffectFactory(DamageType.Dark, () => new DarkCurseEffect()); ``` 对外扩展点与运行时动态注册并存,是 Hades 中 Boon 效果系统类似的做法。 --- ### 3.3 SaveMigrator 版本迁移链 ```csharp 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; } ``` - 新版本只需添加一个新的 `case` + `goto`,旧版本代码不变。 - 每个迁移函数独立,测试友好(传入 `SaveData` 验证输出)。 - `null` 合并运算符 `??=` 处理旧版本缺失字段,不影响新版本正常路径。 **局限**:版本字符串 `"1.0"` 为 magic string,若版本标识改为 `int`/`enum` 可减少拼写错误风险。 --- ### 3.4 平台抽象层 ```csharp // IPlatformService 接口 // SteamPlatformService:#if UNITY_STANDALONE && STEAMWORKS_NET // ConsolePlatformService(预留) // MockPlatformService(测试用) ``` 多平台切换不需要修改任何业务代码。与 Hades / Cuphead 多平台发布策略一致。 --- ### 3.5 WorldStateRegistry 泛化 API ```csharp // 泛化版本(直接使用) public bool IsMarked(WorldObjectCategory category, string id) public void Mark(WorldObjectCategory category, string id) // 具名别名(向后兼容) public bool IsCollected(string id) => IsMarked(WorldObjectCategory.Collectible, id); public void MarkDestroyed(string id) => Mark(WorldObjectCategory.Destroyed, id); ``` 新类别只需在枚举添加值,无需修改逻辑层,同时保留具名 API 的可读性。`OnStateChanged` 事件允许 UI/测试代码响应式订阅,不耦合到具体业务逻辑。 --- ### 3.6 可扩展性待改进项 | 问题 | 影响 | 建议 | |------|------|------| | `SkillManager` 技能槽硬编码为字符串 `"SoulSkill"/"SpiritSkill1"/"SpiritSkill2"` | 新形态需修改多处字符串 | 抽象为 `SkillSlotId enum` 或 `const string` 集中管理 | | `EquipmentManager._collected.Contains()` 为 O(n) | 100+ 护符时 | 改用 `HashSet _collectedSet` | | `SaveMigrator` 版本为 magic string | 拼写错误难察觉 | 改为 `Version` 类或 int 常量 | | `PlayerController.GetCurrentPoiseLevel()` 始终返回 `None` | 玩家霸体无法实现 | 实现基于护符/状态的动态计算 | --- ## 四、编辑器友好性 ### 4.1 工具链全景 ``` BaseGames/Tools/ ├── Event Bus Monitor Ctrl+Shift+E — 运行时事件流监控 ├── Boss Skill Sequence Viewer — 甘特图可视化 Boss 技能时序 ├── Validate Address Keys — Addressables key 一致性检查 └── SOValidationRunner (Build Hook) — ScriptableObject 完整性验证 ``` 这四个工具覆盖了**运行时调试**、**设计验证**、**构建前检查**三个阶段,形成完整的质量保障链。 --- ### 4.2 EventBusMonitorWindow ``` [Time] [Frame] [Channel] [Payload] [Subs] 0.234 143 OnPlayerTakeDamage {amount:12.0} 3 0.251 144 OnEnemyDied {id:"Slug_01"} 2 0.267 145 OnHealPickup ← 0 subs 0 ← 红色警告行 ``` - **Filter**:实时文本过滤,`IndexOf` 大小写不敏感。 - **Pause Capture**:保留历史快照不被新事件覆盖。 - **Auto Scroll**:`_scroll.y = float.MaxValue` 强制滚底。 - **0 订阅者红色高亮**:立即暴露频道配错或漏连的问题。 **唯一缺陷**:`EventBusMonitor` 后端使用 `Queue` 而非固定大小数组,每次超出 256 条时 `Dequeue()` 有轻微 GC(Editor only,可接受)。 --- ### 4.3 BossSkillSequenceWindow 甘特图实时渲染 Boss 技能相位: | 相位 | 颜色 | 含义 | |------|------|------| | Windup | 黄色 | 前摇 | | Active | 红色 | 伤害判定窗口 | | Recovery | 灰色 | 后摇 | | VulnerabilityWindow | 绿色覆盖 | 被弹反可反击窗口 | | DurationNormalized < 0.1 | 警告红 | 阶段过短,设计器警告 | 拖放 `BossSkillSO` / `SkillSequenceSO` 即可加载,`EditorGUIUtility.PingObject` 点击高亮资产——这是 Unity 原生编辑器工具的最佳实践写法。 --- ### 4.4 AddressKeyValidator ```csharp public class AddressKeyValidatorBuildHook : IPreprocessBuildWithReport { public int callbackOrder => 0; // 在 SOValidationRunner(1) 之前执行 public void OnPreprocessBuild(BuildReport report) { // 反射枚举 AddressKeys 所有 const string 字段 // 与 Addressable 分组实际地址集合做差集 // 有孤儿 key → throw BuildFailedException → 构建中止 } } ``` **强制构建门槛**:孤儿 AddressKey 会导致运行时 `Addressables.LoadAssetAsync` 失败,这类错误在发布版本中极难排查。`IPreprocessBuildWithReport` 在构建流水线最早阶段拦截,与 CI/CD 自动化完全兼容。 --- ### 4.5 SOValidationRunner + IValidatable(P3-9 修复后) ```csharp // 修复前:字符串启发式判断严重性 bool isError = msg.Contains("必须") || msg.StartsWith("❌"); // 修复后:类型化严重性 foreach (var result in validatable.Validate()) { if (result.Severity == ValidationSeverity.Error) errors.Add($"❌ {result.Message} ({path})"); else warnings.Add($"⚠️ {result.Message} ({path})"); } ``` 严重性分级(Error/Warning)由 `ValidationResult` 结构体持有,消除了脆弱的字符串模式匹配。 --- ### 4.6 HurtBoxEditor(P3-8 修复后) ```csharp // 修复前:反射读取 private 字段(字符串 fieldName,脆弱) // 修复后:typed lambda getter (System.Func getter, string label, string absentNote)[] _fields = { (hb => hb.EditorOwner, "Owner (IDamageable)", "— 注入失败"), (hb => hb.EditorShieldable, "Shieldable", "— 无护盾"), (hb => hb.EditorParrySystem, "ParrySystem", "— 无弹反"), (hb => hb.EditorPoiseSource, "PoiseSource", "— 无霸体"), (hb => hb.EditorStatusEffectable,"StatusEffectable", "— 无状态效果"), }; ``` 字段重命名后编译器立即报错,而非运行时 `null` 静默失败。 --- ### 4.7 编辑器友好性待改进项 | 问题 | 建议 | |------|------| | `BossSkillSequenceWindow` 对 `_loadedSkill` 字段未作空字段检查 | 在 DrawSkillTimeline 入口添加 HelpBox 提示 | | `EventBusMonitor` 使用 `Queue` 而非固定 `EventRecord[]` | 换循环缓冲区,彻底消除 Editor GC | | `SOValidationRunner` 未提供 "一键修复" 按钮 | 对 Warning 级别问题提供可选自动修复 | | 无场景引用可视化工具 | 仿 Odin Inspector `[SceneObjectsOnly]` 属性或自定义 PropertyDrawer | --- ## 五、使用便利性(Developer Experience) ### 5.1 命名一致性 全项目命名规范高度一致: | 约定 | 示例 | |------|------| | EventChannel SO:`_on` 前缀 | `_onPlayerDied`, `_onSaveIndicatorVisible` | | SO 类型后缀 | `InputReaderSO`, `ParryConfigSO`, `CharmEventChannelSO` | | 接口前缀 `I` | `IDamageable`, `ILOSRequester`, `ISaveable`, `IPlatformService` | | 管理器后缀 `Manager` | `EquipmentManager`, `SaveManager`, `StatusEffectManager` | | 枚举 `Type`/`Phase`/`Id` | `ParryPhase`, `GameStateId`, `StatusEffectType` | | 私有字段 `_camelCase` | `_currentSlot`, `_saveLock`, `_effectFactories` | 商业项目级别的命名一致性,新团队成员阅读代码时认知成本极低。 --- ### 5.2 API 契约清晰度 **优秀范例:** ```csharp // TryEquipCharm:null = 成功,string = 错误原因(优于 bool + out string) public string TryEquipCharm(CharmSO charm) { ... } // ConsumeJump/ConsumeAttack/ConsumeDash:读取即消耗,避免调用者手动清零 public bool ConsumeJump() { ... } // GetOrDefault:明确声明可能返回 null,不同于 Get 的快速失败语义 public static T GetOrDefault() { ... } // ValidationResult.Error / ValidationResult.Warning:工厂方法减少直接 new public static ValidationResult Error(string msg) => new(ValidationSeverity.Error, msg); ``` **需改进的 API:** ```csharp // HitBox.OnHitConfirmed 是 public field,非 event keyword // 外部可以用 = 覆盖所有订阅者 public Action OnHitConfirmed; // ❌ // 应改为: public event Action OnHitConfirmed; // ✅ // PlayerController.GetCurrentPoiseLevel() 始终返回 PoiseLevel.None // 调用者无法区分"玩家本身无霸体设计"和"功能未实现" public PoiseLevel GetCurrentPoiseLevel() => PoiseLevel.None; // ❌ 误导性 ``` --- ### 5.3 订阅生命周期管理 ```csharp // 推荐写法(CompositeDisposable 与 MonoBehaviour 生命周期绑定) private CompositeDisposable _subs = new(); private void OnEnable() { _onPlayerDied.Subscribe(OnPlayerDied).AddTo(_subs); _onRoomEntered.Subscribe(OnRoomEntered).AddTo(_subs); } private void OnDisable() => _subs.Dispose(); ``` 全项目统一了此模式,彻底解决了传统 `OnEnable += / OnDisable -=` 遗忘匹配的问题。这是区别于大多数中小型 Unity 项目的最大质量优势之一。 --- ### 5.4 InputBuffer 设计 ```csharp // 3 个独立帧缓冲,每帧递减,消耗即清零 // 尺寸全部可在 Inspector 调节(不需要修改代码) [SerializeField] private float _jumpBufferDuration = 0.15f; [SerializeField] private float _attackBufferDuration = 0.12f; [SerializeField] private float _dashBufferDuration = 0.10f; ``` `ConsumeJump()` / `ConsumeAttack()` / `ConsumeDash()` 的调用者不需要知道缓冲窗口时长,只需询问"现在能不能执行"。Celeste 的 Coyote Time 实现与此完全同构。 --- ### 5.5 混用模式(-0.2 分) ``` 访问路径矛盾: ServiceLocator.Get() // GlobalObjectPool ✓ GlobalObjectPool.Instance // GlobalObjectPool ✓(同一对象,两条路) SaveManager.Instance // SaveManager(不通过 ServiceLocator) VFXPool.Instance // VFXPool(不通过 ServiceLocator) ClashResolver → ServiceLocator.GetOrDefault() // ✓ AudioManager → ???(已移除旧 .Instance,但新路径需确认) ``` 团队成员面对混用时难以判断"我该用哪个",也会使单元测试的 Mock 替换复杂化。 --- ### 5.6 使用便利性待改进项 | 问题 | 建议 | |------|------| | `HitBox.OnHitConfirmed` 为 public field | 改为 `public event Action` | | 混用 `.Instance` 单例 + ServiceLocator | 统一为 ServiceLocator,旧 `.Instance` 标记 `[Obsolete]` | | `DamageInfo` 非 `readonly struct` | 标记 `readonly`,修改操作改为 `With...()` 方法 | | SkillSlot 字符串魔法值 | 提取为 `static class SkillSlotNames` 常量 | | `AnalyticsManager` 无 namespace | 添加 `namespace BaseGames.Support.Analytics` | --- ## 六、商业基准对标 | 维度 | zeling_v2 | Hollow Knight(估算) | Celeste(开源代码) | Hades(GDC 演讲) | |------|-----------|----------------------|--------------------|--------------------| | 程序集隔离 | ✅ 25 asmdef | ✅ 多 asmdef | ❌ 单项目 | ✅ 分层 | | 事件系统 | ✅ SO Channel + IDisposable | ✅ 自定义事件总线 | ✅ Celeste 事件系统 | ✅ 消息总线 | | 零分配热路径 | ✅ struct DamageInfo | ✅ struct 伤害值 | ✅ 简单值类型 | ✅ 严格零分配 | | 时间切片 AI | ✅ BatchLOSSystem | ✅ 视野感知分帧 | N/A | ✅ 模式分帧 | | 数据驱动护符 | ✅ CharmSO + ICharmEffect | ✅ Charm 系统 | N/A | ✅ Boon SO | | 存档版本迁移 | ✅ goto 链 | ✅ 版本号检查 | ✅ | ✅ | | 编辑器工具链 | ✅ 4 专用工具 | 未知 | ✅ Lönn 编辑器 | ✅ 内部工具 | | 弹反系统完备性 | ✅ 5 相位状态机 | ✅ 经典弹反 | N/A | ✅ 多弹反类型 | | 模式一致性 | ⚠️ 混用单例 | ✅ 统一单例 | ✅ 统一单例 | ✅ 统一 SL | --- ## 七、综合建议 ### 高优先级(影响可维护性) 1. **统一服务访问模式**:`SaveManager`、`VFXPool` 注册到 `ServiceLocator`,`.Instance` 添加 `[Obsolete]`。 2. **`HitBox.OnHitConfirmed` 改为 `event`**:消除外部覆盖风险,影响范围小。 3. **`AnalyticsManager` 添加 namespace**:`BaseGames.Support.Analytics`,5 分钟可完成。 4. **`BatchLOSSystem._requesters` 改 HashSet**:场景大规模加载时性能优化。 ### 中优先级(影响代码质量) 5. **`PlayerController.GetCurrentPoiseLevel()` 实现或标记未完成**。 6. **`DamageInfo` 标记为 `readonly struct`**,Builder 内使用局部变量。 7. **`IGameState.ValidNextStates` 改为 `IReadOnlySet`**。 8. **`EquipmentManager.UsedNotches` 缓存计算结果**,避免每次调用 LINQ `Sum()`。 ### 低优先级(技术债偿还) 9. **SaveMigrator 版本字符串改为常量** `const string V1_0 = "1.0"`,消除 magic string。 10. **SkillSlot 字符串统一到 `SkillSlotNames` 常量类**。 11. **EventBusMonitor 改用固定 `EventRecord[]` 环形缓冲区**(消除 Editor GC)。 --- ## 附录:文件覆盖说明 本次评审直接阅读的源文件(按模块): | 模块 | 已审文件 | |------|---------| | Core/Events | `BaseEventChannelSO.cs`, `EventSubscription.cs`, `EventBusMonitor.cs` | | Core/Save | `SaveManager.cs`, `SaveMigrator.cs`, `ISaveable.cs`, `WorldStateRegistry.cs` | | Core/Pool | `GlobalObjectPool.cs`, `PooledObject.cs` | | Core/Assets | `AssetLoader.cs`, `AssetReleaseTracker.cs` | | Core | `ServiceLocator.cs`, `GameStateMachine.cs` | | Input | `InputReaderSO.cs`, `InputBuffer.cs` | | Player | `PlayerController.cs`, `PlayerStateBase.cs`, `PlayerMovement.cs` | | Combat | `HurtBox.cs`, `HitBox.cs`, `DamageInfo.cs`, `ClashResolver.cs` | | Combat/StatusEffects | `StatusEffectManager.cs` | | Enemies | `EnemyBase.cs`, `BossBase.cs`, `BatchLOSSystem.cs` | | Equipment | `EquipmentManager.cs` | | Skills | `SkillModifierRegistry.cs` | | Parry | `ParrySystem.cs` | | VFX | `VFXPool.cs` | | Support | `AnalyticsManager.cs`, `SteamPlatformService.cs` | | Editor | `EventBusMonitorWindow.cs`, `BossSkillSequenceWindow.cs`, `AddressKeyValidator.cs`, `HurtBoxEditor.cs`, `SOValidationRunner.cs` | > 受覆盖范围限制,`Dialogue`、`Quest`、`Cutscene`、`Tutorial`、`Localization` 等子系统未纳入本次深度审查。 --- *生成于 2026-01 | 评审人:GitHub Copilot (Claude Sonnet 4.6)*