26 KiB
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)
// 静态字典,Unity 主线程单线程访问,无锁竞争
private static readonly Dictionary<Type, object> _services = new();
public static T Get<T>() // 缺失时抛出异常,快速失败
public static T GetOrDefault<T>() // 缺失时返回 null,防御性访问
public static void RegisterIfAbsent<T>(...) // 幂等注册
#if UNITY_EDITOR
public static void OverrideForTest<T>(...) // 测试注入点
#endif
- 设计优点:单责任(查找/注册),
Get<T>()快速失败语义明确,GetOrDefault<T>()防御性访问分开,Editor 测试注入不污染运行时。 - 局限:静态状态在 Domain Reload 后需要手动清理(Unity 在
[InitializeOnLoad]中已有对应处理);构造图对新人不可见。
对比:Hades 的引擎使用类似的 Service Locator(而非 DI 容器),同规模项目此方案已足够。
1.3 SO Event Channel 系统
// BaseEventChannelSO<T>
private event Action<T> _action; // 私有 backing field,防止外部 = 覆盖
public event Action<T> Action { add => _action += value; remove => _action -= value; }
// 订阅返回 IDisposable,支持 CompositeDisposable.AddTo() 生命周期绑定
public EventSubscription Subscribe(Action<T> 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<Type, PlayerStateBase> _statesO(1) 查找,无反射。
游戏状态机(GameStateMachine)
- 纯 C# 类(不挂 MonoBehaviour),
Dictionary<GameStateId, IGameState>+ValidNextStates白名单,TransitionTo()失败返回error字符串而非抛异常。
敌人状态机(EnemyBase._stateObjs)
Dictionary<EnemyStateType, IEnemyState>POCO 实现,状态由子类在 Awake 填充,基类不假定具体状态集。
弹反状态机(ParrySystem)
- 枚举
ParryPhase { Inactive, Startup, Active, EndLag, CounterWindow }清晰定义相变,Update驱动计时,IsParrying/IsInCounterWindow布尔属性暴露只读状态。
潜在问题:
GameStateMachine.TransitionTo中_current.ValidNextStates.Contains()若ValidNextStates为IEnumerable(线性查找),在频繁转换时存在微小 O(n) 开销。建议内部改用HashSet<GameStateId>。
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<IObjectPoolService>(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<GameStateId> 实现 |
二、性能
2.1 热路径零分配设计
| 类型 | 技术 | 效果 |
|---|---|---|
DamageInfo |
struct + From() 工厂 |
无堆分配 |
EventSubscription |
readonly struct IDisposable |
无堆分配 |
HurtBox 8 步管线 |
纯 struct/值类型操作 | 每帧命中无 GC 压力 |
HitBox 命中去重 |
HashSet<Collider2D> |
O(1) per check |
MaterialPropertyBlock |
StatusEffectManager 渲染 | 不产生材质实例 |
Newtonsoft.Json Formatting.None |
SaveManager | 减小序列化字符串体积 |
ValidTransitions 白名单 |
#if UNITY_EDITOR 只在 Editor 执行 |
运行时无额外开销 |
2.2 BatchLOSSystem 时间切片
[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<ILOSRequester>,Register时内部Contains()为 O(n)。当场景内存在 >30 个敌人时,批量注册阶段(关卡加载)会产生 O(n²) 开销。建议改用HashSet<ILOSRequester>或(List + HashSet)双结构。
2.3 VFXPool(P3-10 修复后)
Play(vfxRef, position, ...)
├── TryDequeue() 命中 → PlayImmediate(同帧播放,无 Addressables 等待)
└── 未命中 → PlayLoadAsync(异步加载 + 实例化)
修复前每次 Play() 即使池中已有实例也要经过一帧协程等待。修复后池命中路径 0 帧延迟,与 Celeste 的预热粒子池策略一致。
2.4 GlobalObjectPool
WarmupAsync()预热,避免运行时 Addressables.InstantiateAsync 延迟。Dictionary<string, Queue<PooledObject>>池 +Dictionary<string, LinkedList<PooledObject>>活跃链表:O(1) 入队/出队 + O(1) 强制回收(LinkedList.Remove 为 O(1) 已知节点)。MaxCount > 0限制池 + 活跃对象总量,防止无限扩张。
2.5 性能待改进项
| 问题 | 位置 | 严重度 | 建议 |
|---|---|---|---|
BatchLOSSystem._requesters.Contains() O(n) |
Register() |
中 | 改用 HashSet<ILOSRequester> |
EquipmentManager.UsedNotches 每次调用 LINQ Sum() |
属性 getter | 低 | 维护 _usedNotches 缓存字段,装备/卸下时增减 |
AnalyticsManager.Track() 创建 new Dictionary<string,object> |
每次调用 | 低(非热路径) | 在 Gameplay 密集调用点使用静态预分配 |
DamageInfo 非 readonly struct |
DamageInfo.cs |
低 | 标记为 readonly struct,Builder 内部操作局部变量 |
EventBusMonitor.Queue<EventRecord> 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 修复后)
// 修复前:静态 switch,新效果必须修改 StatusEffectManager 源码
// 修复后:
private readonly Dictionary<DamageType, Func<StatusEffect>> _effectFactories = new();
public void RegisterEffectFactory(DamageType type, Func<StatusEffect> factory)
=> _effectFactories[type] = factory;
// Boss 模块可在运行时注册自定义效果
StatusEffectManager.RegisterEffectFactory(DamageType.Dark, () => new DarkCurseEffect());
对外扩展点与运行时动态注册并存,是 Hades 中 Boon 效果系统类似的做法。
3.3 SaveMigrator 版本迁移链
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 平台抽象层
// IPlatformService 接口
// SteamPlatformService:#if UNITY_STANDALONE && STEAMWORKS_NET
// ConsolePlatformService(预留)
// MockPlatformService(测试用)
多平台切换不需要修改任何业务代码。与 Hades / Cuphead 多平台发布策略一致。
3.5 WorldStateRegistry 泛化 API
// 泛化版本(直接使用)
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<CharmSO> _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<EventRecord> 而非固定大小数组,每次超出 256 条时 Dequeue() 有轻微 GC(Editor only,可接受)。
4.3 BossSkillSequenceWindow
甘特图实时渲染 Boss 技能相位:
| 相位 | 颜色 | 含义 |
|---|---|---|
| Windup | 黄色 | 前摇 |
| Active | 红色 | 伤害判定窗口 |
| Recovery | 灰色 | 后摇 |
| VulnerabilityWindow | 绿色覆盖 | 被弹反可反击窗口 |
| DurationNormalized < 0.1 | 警告红 | 阶段过短,设计器警告 |
拖放 BossSkillSO / SkillSequenceSO 即可加载,EditorGUIUtility.PingObject 点击高亮资产——这是 Unity 原生编辑器工具的最佳实践写法。
4.4 AddressKeyValidator
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 修复后)
// 修复前:字符串启发式判断严重性
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 修复后)
// 修复前:反射读取 private 字段(字符串 fieldName,脆弱)
// 修复后:typed lambda getter
(System.Func<HurtBox, object> 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 契约清晰度
优秀范例:
// TryEquipCharm:null = 成功,string = 错误原因(优于 bool + out string)
public string TryEquipCharm(CharmSO charm) { ... }
// ConsumeJump/ConsumeAttack/ConsumeDash:读取即消耗,避免调用者手动清零
public bool ConsumeJump() { ... }
// GetOrDefault:明确声明可能返回 null,不同于 Get 的快速失败语义
public static T GetOrDefault<T>() { ... }
// ValidationResult.Error / ValidationResult.Warning:工厂方法减少直接 new
public static ValidationResult Error(string msg) => new(ValidationSeverity.Error, msg);
需改进的 API:
// HitBox.OnHitConfirmed 是 public field,非 event keyword
// 外部可以用 = 覆盖所有订阅者
public Action<DamageInfo> OnHitConfirmed; // ❌
// 应改为:
public event Action<DamageInfo> OnHitConfirmed; // ✅
// PlayerController.GetCurrentPoiseLevel() 始终返回 PoiseLevel.None
// 调用者无法区分"玩家本身无霸体设计"和"功能未实现"
public PoiseLevel GetCurrentPoiseLevel() => PoiseLevel.None; // ❌ 误导性
5.3 订阅生命周期管理
// 推荐写法(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 设计
// 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<IObjectPoolService>() // GlobalObjectPool ✓
GlobalObjectPool.Instance // GlobalObjectPool ✓(同一对象,两条路)
SaveManager.Instance // SaveManager(不通过 ServiceLocator)
VFXPool.Instance // VFXPool(不通过 ServiceLocator)
ClashResolver → ServiceLocator.GetOrDefault<ClashResolver>() // ✓
AudioManager → ???(已移除旧 .Instance,但新路径需确认)
团队成员面对混用时难以判断"我该用哪个",也会使单元测试的 Mock 替换复杂化。
5.6 使用便利性待改进项
| 问题 | 建议 |
|---|---|
HitBox.OnHitConfirmed 为 public field |
改为 public event Action<DamageInfo> |
混用 .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 |
七、综合建议
高优先级(影响可维护性)
- 统一服务访问模式:
SaveManager、VFXPool注册到ServiceLocator,.Instance添加[Obsolete]。 HitBox.OnHitConfirmed改为event:消除外部覆盖风险,影响范围小。AnalyticsManager添加 namespace:BaseGames.Support.Analytics,5 分钟可完成。BatchLOSSystem._requesters改 HashSet:场景大规模加载时性能优化。
中优先级(影响代码质量)
PlayerController.GetCurrentPoiseLevel()实现或标记未完成。DamageInfo标记为readonly struct,Builder 内使用局部变量。IGameState.ValidNextStates改为IReadOnlySet<GameStateId>。EquipmentManager.UsedNotches缓存计算结果,避免每次调用 LINQSum()。
低优先级(技术债偿还)
- SaveMigrator 版本字符串改为常量
const string V1_0 = "1.0",消除 magic string。 - SkillSlot 字符串统一到
SkillSlotNames常量类。 - 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)