25 KiB
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. 评分总览
| 维度 | 本次得分(/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> 泛型事件基类实现完整,超过大多数独立参考实现:
// 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 属性,防止意外的
=覆盖 EventSubscriptionRAII 句柄 +CompositeDisposable,与 UniRx 风格一致,订阅生命周期零泄漏风险- Editor-only
_subscriberCount+EventBusMonitor256 条环形日志,帧号/载荷/监听器数量齐全 - 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 ✅ 强
// 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.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
_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 查找 | 零 GC,O(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.1),goto 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 种具体效果(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 数据验证构建钩子 ✅ 强
// 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 便捷属性 ✅ 强
// 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.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<Type, State> | 架构相似,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
// 在 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 | 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