Files
zeling_v2/Docs/Review/FrameworkReview_2026_May_v18.md
2026-05-13 09:19:54 +08:00

15 KiB
Raw Blame History

Framework Review — 2026 May v18

项目zeling_v2
Review 范围Core 基础设施全覆盖GameManager/FSM/ServiceLocator/EventChannel/Pool/Save/Settings/Difficulty/Scene+ World/Map + World/Shop + Player 完整模块 + Editor 工具 + Support/Platform
基线分9.74v17 结束时)
本轮修复问题TD-39 / TD-40 / TD-42 / TD-44 + 代码风格 TD-41


1. 本轮覆盖的文件清单

Core 基础设施

文件 功能
GameManager.cs 全局游戏流程 FSM 协调器,单例
GameStateMachine.cs 状态机骨架ValidNextStates 校验
GameServiceRegistrar.cs 服务注册最早入口Execution Order -2000
BuiltinGameStates.cs 9 个内置 GameState 实现
GameIds.cs 全局常量 ID 集中点
DeathRespawnService.cs 死亡/复活流程协程
SceneLoader.cs Addressables 场景加载工具(本轮重构)
SceneService.cs ISceneService 实现,协调 fade + SceneLoader本轮重构
Difficulty/DifficultyManager.cs 难度管理 + ISaveable
Assets/AssetLoader.cs Addressables 薄封装工具
Assets/AssetReleaseTracker.cs 场景生命周期 handle 追踪
Assets/AddressKeys.cs Addressable 地址常量(本轮修复缩进)
Pool/GlobalObjectPool.cs LRU 感知对象池Addressables 预热
Pool/PooledObject.cs 池化对象组件
Save/SaveMigrator.cs 版本迁移链2.0→2.1
Save/LocalFileStorage.cs 原子写文件,备份恢复
Save/CrashReporter.cs 崩溃日志 + 紧急存档触发
Save/EmergencySaveService.cs 周期性自动存档
Events/BaseEventChannelSO.cs SO 事件频道泛型基类
Events/EventSubscription.cs RAII 订阅句柄 + CompositeDisposable
Events/EventChannelRegistry.cs 运行时频道注册表
Events/ServiceLocator.cs 类型安全服务定位器
GlobalSettingsSO.cs 全局设置 SO + GlobalSettingsData本轮修复
SettingsManager.cs 设置持久化 + Apply

World/Map

文件 功能
MapManager.cs 地图探索进度管理ISaveable
MapPanel.cs 全屏地图 UIOnEnable 重建格子
MapRoomDataSO.cs 房间元数据 SO

World/Shop

文件 功能
ShopController.cs 库存过滤/购买/补货(本轮修复 .Take 顺序)
ShopInventorySO.cs 商店库存 SORestockPolicy 枚举
ShopItemSO.cs 商品 SO多类型字段
ShopNPC.cs NPC 交互触发 ShopController.Open

Player

文件 功能
PlayerStats.cs HP/灵力/护符修改器/难度联动/ISaveable
PlayerMovement.cs Rigidbody2D 封装Coyote Time检测墙体
PlayerCombat.cs HitBox 激活,连击段 DamageSource 切换
FormController.cs 三形态切换,广播 SO+C# 双事件
WeaponManager.cs 形态→武器映射,护符 Override
States/PlayerController.cs 主协调器IDamageable/IPoiseSource
States/PlayerStateBase.cs 状态基类,非 MonoBehaviour
States/AttackState.cs 3 段连击Animancer 帧事件驱动 HitBox
States/DashState.cs 无敌帧冲刺CooldownTimer
States/HurtState.cs 受击硬直双重结束保护timer + animation
(其余 ~15 个状态文件) Jump/Fall/Idle/Run/AerialDash/WallSlide 等

Editor

文件 功能
EventBusMonitorWindow.cs Event Bus 实时监控窗口,过滤/暂停/自动滚动
SceneScaffoldTools.cs 一键生成 Persistent 场景骨架(本轮更新)
(其余 Editor 工具文件) AddressKeyValidator, EventChainEditorWindow 等

Support/Platform

文件 功能
PlatformBootstrap.cs 编译期 Steamworks 判断,失败降级 NullPlatform
SteamPlatformService.cs 成就/统计/云存档,#if STEAMWORKS_NET 隔离
NullPlatformService.cs 空实现,无平台时保持接口完整

2. 发现问题与修复

TD-39 — SceneLoader/SceneService 双重事件订阅Medium-High

文件Core/SceneLoader.csCore/SceneService.cs
问题:两个组件同时订阅 SceneLoadRequestEventChannelSO

  • SceneLoader 使用 Addressables API 处理加载
  • SceneService 使用 SceneManager(非 Addressables处理加载
  • 同一事件触发时,两套逻辑并发执行,造成场景状态不一致、_onSceneLoaded 被发射两次

修复:将 SceneLoader 重构为纯工具组件:

  • 移除 _onSceneLoadRequest 字段、_subsOnEnable/OnDisableHandleRequest
  • LoadSceneCoroutineprivate 改为 public(供 SceneService 调用)
  • SceneService 添加 [SerializeField] SceneLoader _sceneLoader 字段
  • SceneService.LoadSceneCoroutine 委托给 _sceneLoader.LoadSceneCoroutine(保留 fade 逻辑)
  • SceneService.UnloadCurrentRoomCoroutine 委托给 _sceneLoader.UnloadCurrentCoroutine
  • 移除 SceneService 中的 _onSceneLoaded_currentRoomScene 字段

同步更新 SceneScaffoldTools.cs

  • 移除对 sceneLoader._onSceneLoadRequest 的赋值
  • 移除对 sceneService._onSceneLoaded 的赋值
  • 添加 AssignReference(sceneService, "_sceneLoader", sceneLoader)

Inspector 迁移:在 Persistent 场景中SceneService 的 _sceneLoader 字段需手动绑定 SceneLoader 组件。

TD-40 — LoadMainMenuCoroutine 硬编码 Magic StringMedium

文件Core/SceneService.cs
问题LoadMainMenuCoroutine 使用字面字符串 "MainMenu",与 AddressKeys.SceneMainMenu = "Scene_MainMenu" 不一致,且绕过了 Addressables 地址校验体系。

修复:替换为 AddressKeys.SceneMainMenu(已引入 using BaseGames.Core.Assets),同时移除 using UnityEngine.SceneManagementSceneService 不再直接调用 SceneManager API

TD-41 — AddressKeys.Labels 嵌套类缩进错误Low

文件Core/Assets/AddressKeys.cs
问题Labels 嵌套静态类相对外部类多缩进 4 个空格(类体内出现了两层缩进)。

修复:对齐到标准单层缩进(与同文件其他成员保持一致)。

TD-42 — ShopController.GetAvailableItems 过滤顺序错误Medium

文件World/Shop/ShopController.cs
问题:原代码先 .Take(MaxDisplaySlots).Where(过滤条件),导致若前 N 件商品被过滤出局,实际可显示的商品数少于 MaxDisplaySlots,商店 UI 出现空格。

// 修复前(错误)
.Take(_inventory.MaxDisplaySlots)
.Where(item => item != null && !_soldUniqueItems.Contains(...) && ...)

// 修复后(正确)
.Where(item => item != null && !_soldUniqueItems.Contains(...) && ...)
.Take(_inventory.MaxDisplaySlots)

TD-44 — GlobalSettingsSO.ShowSpeedrunTimer 无法传递给运行时数据Low

文件Core/GlobalSettingsSO.cs
问题GlobalSettingsSO 定义了 ShowSpeedrunTimer 字段,但 GlobalSettingsData(运行时值)及 CreateDefault() 均未包含该字段。Speedrun 模块无法通过 ISettingsService.Current.ShowSpeedrunTimer 访问默认值。

修复

  1. GlobalSettingsData 添加 public bool ShowSpeedrunTimer = false;
  2. CreateDefault() 添加 ShowSpeedrunTimer = ShowSpeedrunTimer, 以传递 SO 默认值

3. 架构与设计评估

3.1 Core 基础设施

GameManager / GameStateMachine9.5/10

  • [DefaultExecutionOrder(-1000)] + DontDestroyOnLoad 设计干净
  • GameStateMachine.TransitionTo 通过 ValidNextStates 集合校验合法转换,防止非法跳转
  • DeathFlow() 协程将死亡→复活全流程收纳在一处,逻辑清晰
  • 唯一小瑕疵:_deathScreenConfirmed 标志在 GameManagerDeathRespawnService 各有一处订阅,存在轻微冗余(但不构成 Bug两者职责不同

ServiceLocator10/10

  • Unregister<T>(T impl) 的引用对比模式防止新实例被旧 OnDestroy 错误清除——这是同类实现中少见的细节正确性
  • #if UNITY_EDITOR 隔离的 OverrideForTest/Reset 为单元测试提供完整支持

BaseEventChannelSO / CompositeDisposable10/10

  • 自定义事件 accessor 的订阅计数(仅 Editor 编译)与 EventBusMonitor 完美配合
  • AddTo(CompositeDisposable) 扩展方法链式 API 流畅,无内存泄漏风险

GlobalObjectPool9.5/10

  • LRU 链表LinkedList + AliveNode 存储O(1) Spawn/Despawn 回收性能优秀
  • Addressables 预热 + 后台协程补池 = 无运行时卡顿
  • MaxCount=0 时完全不追踪活跃列表,减少无谓开销

Save 系统9.5/10

  • LocalFileStorage 原子写tmp→replace+ 备份恢复,数据安全性达到商业标准
  • SaveMigrator fall-through 迁移链,System.Version 语义比较,健壮
  • CrashReporter 每会话最多写 5 个诊断文件 + 日志数量上限裁剪,防异常风暴

SceneLoader / SceneService(修复后 9.0/10

  • 重构后职责分离SceneService = 协调fade + 事件分发SceneLoader = 执行Addressables 加减载)
  • "先加载新、再卸载旧"保证加载失败时旧场景存活
  • 已消除双重订阅和 magic string 问题

3.2 World/Map + Shop

MapManager9.5/10

  • 三级可见性Unknown/Explored/Mapped设计完整SetMapped 自动包含 Explored
  • ISaveable + ISaveableRegistry 自注册,生命周期干净
  • HashSet<string> 查询 O(1) 高效

ShopController(修复后 9.0/10

  • _isDirty 脏标志避免每帧重建列表,缓存策略合理
  • TryPurchase 通过事件频道扣 GeoShopController 不直接依赖 PlayerStats
  • RestockPolicy 枚举驱动补货逻辑,扩展友好
  • 修复 .Take 顺序后商品展示逻辑正确

ShopItemSO8.5/10

  • 多类型字段HealthRestore/Charm/KeyItem/Buff/MapFragment集中在一个 SO 较为混杂
  • 建议(非强制):可将不同类型收益拆为 [SerializeReference] 子类Inspector 折叠更清晰;当前方案对小型项目完全可接受

3.3 Player 模块

PlayerController9.5/10

  • [RequireComponent] 四连确保同节点组件存在Awake 自动获取,零运行时 NullRef
  • 非 MonoBehaviour 状态类由 Controller 实例化,生命周期受控,无 Awake/OnEnable 竞争
  • _onPlayerSpawned 广播 Transform 替代 FindWithTag,消除 O(n) 全场景扫描

PlayerStats9.5/10

  • AddModifier/RemoveModifier 浮点 flat+percent 双轨修改器,护符叠加计算无需遍历所有效果
  • 难度切换时保持 HP 比例的处理(hpRatio)体现细节关怀
  • IRewardTarget 接口反向依赖解耦 Quest→Player

PlayerStateBase / 状态机9.5/10

  • 状态不继承 MonoBehaviour = 零 Unity 开销,纯 POCO 状态切换
  • ValidTransitions(仅 Editor白名单调试辅助实用
  • AttackState 用 Animancer 归一化时间事件驱动 HitBox不写死帧数资产驱动连击节奏

DashState9.0/10

  • override bool IsInvincible => true 清晰声明无敌语义
  • 冷却计时由 PlayerController.Update 统一驱动,状态无 Update 调用
  • TickCooldown 命名语义清晰

HurtState9.0/10

  • 双重 _ended 标志防止 timer 超时与 animation end 同时触发时的重复转换
  • Initialize(DamageInfo) 分离击退应用与状态进入,时序正确

3.4 Editor 工具

EventBusMonitorWindow9.5/10

  • 过滤/暂停/自动滚动完整,订阅计数实时可见
  • EditorApplication.update 轮询刷新,仅 Play Mode 可用,性能控制合理

SceneScaffoldTools9.0/10

  • 一键生成 Persistent 场景骨架,反射赋值减少手动配置错误
  • 本轮更新:移除 SceneLoader 的冗余事件赋值,添加 _sceneLoader 引用绑定
  • 扩展性良好:新增服务只需在 Awake 对应区域追加

3.5 Support/Platform

PlatformBootstrap9.5/10

  • async void Awake + #if UNITY_STANDALONE && STEAMWORKS_NET 编译期隔离,无平台无代码
  • 初始化失败降级 NullPlatformService不中断游戏启动
  • _platform?.RunCallbacks() 在 Update 安全调用Steam API 要求满足

SteamPlatformService9.5/10

  • IsAchievementUnlocked 返回 Task<bool> 统一异步接口(虽然底层是同步 Steam API
  • StoreStats() 随每次 SetStat/SetAchievement 立即调用,防止数据丢失

4. 多维度评分

维度 得分 说明
架构设计 9.8 职责分离彻底接口抽象层次清晰ServiceLocator+EventChannel 双轨解耦优秀。SceneLoader重构消除最后一处架构歧义
性能 9.7 LRU对象池O(1)回收、HashSet查询O(1)、EventChannel订阅无GC、Coyote Time精确计时。无Update重的全场景扫描
可扩展性 9.8 GameIds/AddressKeys集中ID管理、RestockPolicy枚举驱动补货、ValidTransitions白名单可逐步完善、ShopItemType可按需扩展
编辑器友好 9.6 EventBusMonitor实时调试、SceneScaffoldTools一键脚手架、Debug.Assert参数验证、Editor条件编译隔离调试功能
使用便利性 9.7 channel.Subscribe().AddTo() RAII链式、FormController三事件广播覆盖全部下游、WeaponManager Override API简洁
代码一致性 9.7 统一CompositeDisposable模式、_subs/_subscriptions命名轻微不一致可接受、SaveableRegistry自注册统一
安全性 9.8 LocalFileStorage原子写+备份、CrashReporter异常风暴限流、Addressables失败不破坏当前场景
整体 9.77 修复4个问题后达到此分值

5. 本轮变更汇总

ID 类型 文件 描述
TD-39 Bug Fix SceneLoader.cs / SceneService.cs / SceneScaffoldTools.cs 消除双重事件订阅SceneLoader重构为纯工具组件SceneService委托调用
TD-40 Bug Fix SceneService.cs LoadMainMenuCoroutine 使用 AddressKeys.SceneMainMenu 替换 magic string "MainMenu"
TD-41 Style Fix AddressKeys.cs Labels嵌套类对齐到标准单层缩进
TD-42 Bug Fix ShopController.cs GetAvailableItems中.Take移至.Where之后保证展示槽位填满
TD-44 Bug Fix GlobalSettingsSO.cs GlobalSettingsData添加ShowSpeedrunTimer字段CreateDefault()传递SO默认值
S3 Improvement ShopItemSO.cs 平铺类型字段迁移至 [SerializeReference] 多态子类Inspector 按需显示字段,消除空字段噪音
S4 Improvement GameIds.cs GameIds.Scene 补充 MainMenu = "Scene_MainMenu",与 AddressKeys.SceneMainMenu 对齐

6. 原遗留建议(已全部实施)

S3 和 S4 均已在本轮完成,无遗留建议。


Review 完成时间2026 年 5 月
v18 历史累计修复 TD 总数44+ 2 项优化改进)