38 KiB
Zeling V2 — 全系统最终代码评审
评审版本:2026-Final(全 P0–P3 修复后)
代码规模:424.cs文件 / 30 个 Assembly Definition / 24 个顶层模块
对标标准:《空洞骑士》/ 《Celeste》 / 《Dead Cells》 / 《Hades》商业 AA 级 2D 动作 RPG
综合评分:9.5 / 10
目录
- 执行摘要
- 架构全景
- 核心基础设施层
- 玩家系统
- 战斗系统
- 敌人与 AI
- 世界与关卡系统
- 进度与成就系统
- 叙事与过场系统
- UI 与 HUD 系统
- VFX 与视觉反馈系统
- 音频系统
- 平台与支持系统
- 性能工程综述
- 可扩展性综述
- 编辑器友好性综述
- 模块评分汇总
- 残留改善点(P4 建议)
1. 执行摘要
经过全面的 P0–P3 修复周期,Zeling V2 代码库已进入高度成熟的状态。424 个源文件、30 个 Assembly Definition 组成了一套层次清晰、事件驱动、数据与逻辑分离的架构体系。
核心优势(商业对标维度):
| 维度 | 评分 | 说明 |
|---|---|---|
| 架构设计 | 9.5 | SO 事件频道 + ServiceLocator 双轨依赖,职责清晰 |
| 性能工程 | 9.3 | 对象池 / VFX 池 / BatchLOS / HashSet / O(1) 字典索引 |
| 可扩展性 | 9.6 | Strategy / Visitor / FSM / SO 数据驱动无处不在 |
| 编辑器友好 | 9.4 | CreateAssetMenu / Debug.Assert / DefaultExecutionOrder |
| 开发体验 | 9.2 | RAII 事件订阅 / 静态工具类 / 薄封装抽象 |
| 代码一致性 | 9.5 | 全库统一的命名与订阅惯例 |
2. 架构全景
2.1 分层依赖图
┌─────────────────────────────────────────────┐
│ Unity Editor / Inspector │ ← ScriptableObject 数据配置层
├────────────┬───────────────┬────────────────┤
│ UI Layer │ Game Layer │ Platform Layer │ ← 叶子层(依赖 Core,不被 Core 依赖)
├────────────┴───────────────┴────────────────┤
│ BaseGames.Core.Events │ ← 事件频道 SO 总线(最底层,零依赖)
│ BaseGames.Core │ ← ServiceLocator / GameStateMachine / Pool
├─────────────────────────────────────────────┤
│ Player │ Combat │ Enemies │ World │ Quest │ ← 领域层(通过 Events 松耦合)
└─────────────────────────────────────────────┘
2.2 事件频道 SO 系统(★★★★★)
BaseEventChannelSO<T> 是整个架构的神经网络。所有跨系统通信均经由 SO 事件频道完成,没有任何系统直接持有另一系统的 MonoBehaviour 引用。
// 惯用模式(全库一致)
private readonly CompositeDisposable _subs = new();
private void OnEnable()
=> _channel?.Subscribe(Handler).AddTo(_subs);
private void OnDisable()
=> _subs.Clear();
设计亮点:
EventSubscriptionRAII 包装器:订阅即拥有,无需记住对称取消CompositeDisposable:批量清理,OnDisable 仅一行- 每个 SO 有独立
_backing字段,编辑器对 Action 的重新序列化不会污染运行时订阅者 EventBusMonitor:所有频道在运行时可以在一个窗口中观察,极大提升调试效率
2.3 ServiceLocator(★★★★☆)
提供两套 API,互补使用:
// 精确类型
ServiceLocator.Register<ICameraService>(this);
ServiceLocator.GetOrDefault<ICameraService>();
ServiceLocator.Unregister<ICameraService>(this); // 引用比较,防止跨场景误注销
// 具名实例(用于非接口类型)
ServiceLocator.Register<DifficultyManager>(this);
Unregister(impl) 采用引用比较而非类型匹配,是重要的安全属性,防止新场景实例错误清除旧服务。
2.4 游戏状态机(★★★★★)
GameStateMachine + IGameState + GameStateId 字符串常量三件套构成类型安全的 FSM:
GameStates.*静态只读常量替代魔法字符串(P1 修复后完全落地)TransitionTo()返回bool供调用方判断是否成功RegisterStates()在GameManager.Awake中集中完成,清晰可审计- 状态变更通过
_onGameStateChangedSO 频道广播,UI/音频无需直接引用 FSM
2.5 程序集隔离(★★★★★)
30 个 .asmdef 文件精细控制编译依赖,典型设计:
BaseGames.Core.Events零依赖:任何模块都可安全引用BaseGames.Parry不引用BaseGames.Combat(ConsumeParry()无DamageInfo参数)BaseGames.Enemies.Navigation通过IPathAgent接口解耦#if GRAPH_DESIGNER/#if STEAMWORKS_NET平台条件编译隔离第三方
3. 核心基础设施层
3.1 GameManager(★★★★★)
[DefaultExecutionOrder(-1000)] // 最先执行,保证服务已注册
public class GameManager : MonoBehaviour
DontDestroyOnLoad在transform.root上(安全,不影响子节点)RegisterStates()集中注册所有状态,避免 FSM 分散注入Start()广播初始状态(而非 Awake),确保所有订阅者已在 OnEnable 中就位- 不持有任何领域层引用,完全通过事件驱动行为
3.2 对象池 GlobalObjectPool(★★★★★)
private readonly Dictionary<string, Queue<PooledObject>> _pools = new();
private readonly Dictionary<string, LinkedList<PooledObject>> _alive = new();
private readonly Dictionary<string, int> _maxCounts = new();
性能亮点:
- Addressables 异步预热(
WarmupAsync()),不阻塞主线程 MaxCount > 0时强制上限,防止 Boss 阶段对象爆炸_alive使用LinkedList<>支持 O(1) 节点移除(PooledObject 缓存自身节点)_prefabCache避免重复 Addressables 加载- 实现
IObjectPoolService接口,测试时可替换 Mock
与商业标准对比:结构等同于 Unity 官方推荐的 Pre-allocated Pool 方案,并增加了 MaxCount 安全上限。
3.3 VFX Pool(★★★★☆)
// 命中路径:直接播放,零 GC
if (TryDequeue(vfxRef, out var ps))
StartCoroutine(PlayImmediate(...));
else
StartCoroutine(PlayLoadAsync(...)); // 未命中:异步加载
- 以
AssetReferenceGameObject为 key,支持强类型 Addressables 引用 _globalMaxLifetime兜底超时,防止循环粒子永不回池- Coroutine 驱动自动回收,调用方 fire-and-forget
- 改善点:未实现
Warmup()的异步版本,首次播放仍有一帧延迟
3.4 Addressables 资产层(★★★★★)
// AssetLoader:薄封装,Handle 语义清晰
var (asset, handle) = await AssetLoader.LoadAsync<T>(key);
// AssetReleaseTracker:场景销毁时自动批量释放
tracker.Track(handle);
// ... OnDestroy 自动 Release
AssetReleaseTracker 挂在场景根节点的设计,完美解决了 Addressables 内存泄漏的常见痛点。
3.5 存档系统(★★★★★)
已在 MasterCodeReview_2026_Full.md 详细分析,此处补充:
SaveMigratorgoto fall-through chain 是零 if-else 的线性版本迁移,可维护性极佳[JsonExtensionData]向前兼容未知字段- SHA-256 校验和防存档损坏
IRestoreOnSave+ISaveable双接口分离"快照恢复"和"存/读"
3.6 难度系统(★★★★★)
// SteelSoul 一旦激活,不可降级
if (CurrentLevel == DifficultyLevel.SteelSoul && level != DifficultyLevel.SteelSoul)
{
Debug.LogWarning("SteelSoul 无法降级");
return;
}
- 业务规则硬编码在
ChangeDifficulty()中,符合 Hollow Knight SteelSoul 的设计语义 ISaveable集成,难度档位持久化DifficultyScalerSO数据驱动缩放参数(HP、伤害、时间等),新难度档无需改代码
3.7 死亡复活服务(★★★★☆)
// 局部订阅确认事件,避免类级 bool 字段状态机污染
bool confirmed = false;
void OnConfirm() => confirmed = true;
_onDeathScreenConfirmed.OnEventRaised += OnConfirm;
yield return new WaitUntil(() => confirmed);
_onDeathScreenConfirmed.OnEventRaised -= OnConfirm;
局部 lambda 的临时订阅模式避免了持久 bool 标志的并发风险,是 Coroutine 状态管理的最佳实践。
4. 玩家系统
4.1 PlayerController(★★★★★)
[DefaultExecutionOrder(-100)]
[RequireComponent(typeof(InputBuffer))]
[RequireComponent(typeof(PlayerMovement))]
[RequireComponent(typeof(PlayerStats))]
[RequireComponent(typeof(AnimancerComponent))]
public class PlayerController : MonoBehaviour, IDamageable, IPoiseSource
RequireComponent四件套:编辑器编译期保证必要组件存在- 所有跨节点引用通过
[SerializeField]绑定(无GetComponent<>运行时查找) - 状态对象字典
Dictionary<Type, PlayerStateBase>:O(1) 状态查找 _onPlayerSpawned广播玩家 Transform,替代全场景FindWithTag(P3-3 完全落地)- Animancer 双层(Base Layer + Overlay Layer),支持上半身/全身动画叠加
4.2 玩家状态机(19 个状态,★★★★★)
完整状态集:Idle / Run / Jump / Fall / Dash / AerialDash / Attack / AirAttack / UpAttack / DownAttack / Parry / WallSlide / WallJump / Spring / Swim / Hurt / Dead
架构优势:
PlayerStateBase提供Enter() / Tick() / Exit()三阶段生命周期- 每个状态自持 Animancer ClipTransition,无字符串动画名
AnimationEventBinder静态工具:事件注入在 Awake 时完成,运行时零反射InputBuffer缓冲机制:攻击/跳跃在落地前 0.1~0.2s 按下仍可触发(手感关键)- 状态字典预创建(非 lazy 创建),帧循环零 GC
商业对标:结构等同于 Celeste 的 StateMachine + Coroutine 混合方案,但本实现额外利用了 Animancer 的状态层支持,动画更灵活。
4.3 FormController(★★★★★)
三形态(天魂/地魂/命魂)切换系统,双事件广播:
// 1. SO 事件 → UI/Save
_onFormChanged?.Raise(index);
// 2. C# 事件 → WeaponManager 订阅(同 GameObject,内存友好)
OnFormChanged?.Invoke();
// 3. SO 事件 → SkillHUD
_onSkillSetChanged?.Raise();
SO 事件用于跨场景/跨 GameObject 通信,C# 事件用于同 GameObject 的轻量通信。双层设计是性能与灵活性的最佳平衡。
4.4 ParrySystem(★★★★★)
五阶段精确状态机:Inactive → Startup → Active → EndLag → CounterWindow
IsParrying公开属性供 HurtBox 轮询(单帧查询,可接受)OnParryConsumed(ParryInfo)C# 事件:PlayerController 订阅后发放灵力并恢复护盾OnParryActivatedC# 事件:PlayerController 转换到 ParryStateIsEnabled开关:玩家能力解锁前禁用弹反(支持渐进式能力开放)ParryInfoEventChannelSO可选广播:UI 反馈/成就监听无需直接引用 ParrySystem
商业对标:实现了与 Hollow Knight 等价的弹反系统精度(毫秒级前摇/后摇配置化)。
4.5 EquipmentManager(★★★★★)
public string TryEquipCharm(CharmSO charm)
{
// 返回 null = 成功;返回字符串 = 错误原因
if (charm.notchCost > remaining)
return $"笔记不足(需要 {charm.notchCost},剩余 {remaining})";
...
_usedNotches += charm.notchCost; // 缓存值,避免 LINQ Sum
}
_usedNotches缓存字段(P2 修复后):避免每帧 LINQ Sum 计算EquipmentContext传递上下文:O(1) 获取所有相关组件CharmEffect.OnEquip/OnUnequip策略模式:新护符效果只需实现接口ISaveable持久化装备状态
5. 战斗系统
5.1 HitBox / HurtBox / DamageInfo(★★★★★)
已在 MasterCodeReview 中详细分析,此处强调:
DamageFlags位掩码:ForceBreak | Critical | Unblockable可并行叠加HitInfo击中信息不可变结构,传递给 HitConfirmedEventChannelHitBox.OnTriggerExit2D清除_alreadyHit(P1-3 修复后无漏攻)
5.2 投射物系统(★★★★★)
四类投射物继承体系:Projectile → Linear / Arc / Homing / Parryable
ProjectileConfigSO数据驱动:速度/伤害/穿透/重力 Inspector 直调ProjectileManager订阅_onPlayerSpawned(P3-3 风格):无 FindWithTagHomingProjectile每帧 Lerp 旋转追踪,可配置锁定角速度上限
5.3 ClashResolver(★★★★★)
弹刃对碰逻辑:
ClashConfigSO定义各武器类型的碰撞优先级矩阵- 对碰窗口期双方同帧 HitBox 重叠时触发,互相消弹
PoiseWindowConfig配合霸体系统:霸体高的一方破碰触发反弹
5.4 状态效果系统(★★★★☆)
StatusEffect 抽象基类 → Fire / Poison / Stagger 三具体实现
- 每种效果独立计时器,不依赖 Update polling
StatusEffectManager字典管理活跃效果,类型为 key- 改善点:目前无法堆叠同类效果(第二次施加只刷新时长);后续若需实现毒素叠加需重构
6. 敌人与 AI
6.1 EnemyBase(★★★★★)
public class EnemyBase : MonoBehaviour, IDamageable, ILOSRequester
ILOSRequester接口:批量视线检测系统(BatchLOSSystem)的接入点_poiseSource接口引用:EnemyPoiseComponent 自动注入,TakeDamage 时读取霸体等级_stateObjsPOCO 字典:子类可重写状态注入自定义行为(开放扩展)OnDiedC# 事件:ChallengeRoomManager 订阅波次结算(轻量,无 SO 开销)_onPlayerSpawned频道订阅:零 FindWithTag(P3-3 完全落地)
6.2 BatchLOSSystem(★★★★★)
空间网格 + O(1) 取消注册(P1-4 修复后):
// 注销:通过节点引用 O(1),不 O(n) 遍历列表
_cellNodes.TryGetValue(id, out var node) → _cells[cell].Remove(node);
对于 60 个敌人同场景的场景,与 O(n) 方案相比每帧节省约 4000 次比较。
6.3 EnemyQuotaManager(★★★★★)
- P2-5 修复:
HashSet<string>存储死亡敌人 ID(O(1) Contains) - P3-3 修复:
_onPlayerSpawned频道替代FindWithTag - 波次管理支持多种触发条件(定时/击杀/到达)
6.4 Boss 系统(★★★★☆)
WeakPointSystem 弱点系统:
- 弱点 HurtBox 独立 GameObject,Inspector 可视化配置
SetActive(bool active, float multiplier, bool activateSpecific)三参数控制精细_onVulnerabilityWindowOpened广播:动画/UI 订阅(无需直接引用 Boss)- 改善点:Boss 阶段 Pattern 尚在
Opsive.BehaviorDesigner中实现,与 C# 代码的边界稍模糊;建议明确IBossPattern接口规范
7. 世界与关卡系统
7.1 WorldStateRegistry(★★★★★)
[CreateAssetMenu(menuName = "World/WorldStateRegistry")]
public class WorldStateRegistry : ScriptableObject
{
// 泛化 API
public void Mark(WorldObjectCategory category, string id);
public bool IsMarked(WorldObjectCategory category, string id);
// 向后兼容具名 API(调用泛化方法)
public void MarkCollected(string id) => Mark(WorldObjectCategory.Collectible, id);
}
OnEnable()清除状态:防止编辑器 Domain Reload 残留脏数据(★ 关键细节)OnStateChanged事件:UI/地图/测试代码响应式订阅,无需轮询- 泛化 + 具名双 API:新类别只加枚举值,旧代码零改动
7.2 RoomController / RoomTransition(★★★★★)
private void Start()
{
// 房间加载完成时自动切换相机
ServiceLocator.GetOrDefault<ICameraService>()?.SwitchRoom(_roomCamera);
}
- 相机切换在房间 Start 时自动完成,策划只需挂组件、配置 RoomCamera
GetSpawnPoint(transitionId)Fallback 到第一个出生点,防止配置疏漏导致崩溃RoomTransition通过SceneLoadRequestEventChannelSO触发场景切换,无 SceneManager 直调
7.3 关卡互动对象(★★★★☆)
完整互动对象库:
| 类型 | 实现亮点 |
|---|---|
Collectible |
WorldStateRegistry 持久化收集状态 |
CrumblePlatform |
Coroutine 驯服,可配置崩塌延迟 |
MovingPlatform |
Rigidbody2D Interpolation,玩家站上随动 |
FalseWall |
接受 Interact 事件后切换 Collider |
PhantomPlate |
压重感应,松开后弹回 |
DestructibleTile |
Tilemap 集成,支持 Hit 计数 |
PuzzleWire → PuzzleReceiver → PuzzleDoor |
三件套谜题系统 |
谜题系统设计:IPuzzleConnector 接口 + PuzzleWire 连接关系,可视化连线,策划友好。
7.4 CameraStateController(★★★★★)
public void SwitchRoom(RoomCamera targetCamera)
{
if (targetCamera == null || targetCamera == _activeCamera) return;
var profile = targetCamera.BlendProfile ?? _defaultBlendProfile;
_brain.DefaultBlend = profile.ToBlendDefinition();
_activeCamera?.Deactivate();
_activeCamera = targetCamera;
_activeCamera.Activate();
}
- Cinemachine Brain 封装:调用方无需了解 Cinemachine API
BlendProfile ?? _defaultBlendProfile回落链:房间可自定义混合曲线RegisterRoomCamera/UnregisterRoomCamera:相机生命周期安全TriggerImpulse统一接口:HitStop、Boss 出现等都可调用,参数直观
7.5 地图系统(★★★★☆)
MapManager+MapPlayerTracker+MapPin+MapPanel完整四件套MapRoomDataSO数据驱动地图房间定义(坐标/连通性/区域)WorldStateRegistry.OnStateChanged订阅:房间探索即时更新地图- 改善点:
MapPanel尚未实现迷雾遮罩(策划确认后补充)
7.6 商店系统(★★★★☆)
ShopInventorySO数据驱动库存:无需修改代码增删商品ShopController验证购买(Geo 检查 + WorldStateRegistry 已购标记)ShopPurchaseEventChannelSO广播:UI/任务系统各自监听- 改善点:
ShopNPC的对话序列 ID 硬编码为字段;建议提取为 SO 引用
8. 进度与成就系统
8.1 AchievementCondition 策略体系(★★★★★)
12 个具体条件类,全部继承 AchievementCondition 抽象基类:
| 条件类 | 数据源 | 典型实现 |
|---|---|---|
ParryCountCondition |
save.Stats.ParrySuccess |
>= requiredCount |
NoHealRunCondition |
save.Stats.HealUsed == 0 |
全程监控 |
TimedBossKillCondition |
save.Stats.BossKillTimes[bossId] |
字典查找 |
MapExplorationCondition |
save.World.ExploredRooms |
百分比达标 |
DefeatedAllBossesCondition |
save.World boss 子字典 |
全量检查 |
CollectedAllCharmsCondition |
save.Equipment.Collected |
Count 比较 |
NailClashCountCondition |
save.Stats.NailClashes |
≥ N |
EventTriggeredCondition |
事件频道触发标志 | 即时触发 |
EnteredRegionCondition |
save.World.VisitedRegions |
集合查找 |
CollectedItemCondition |
save.World.Collectibles |
精确 ID |
DefeatedBossCondition |
save.World.DefeatedBosses |
单 Boss |
UnlockedAllAbilitiesCondition |
save.Progression.Abilities |
全量 |
设计亮点:
IsMet(SaveData)+GetProgress(SaveData)双方法:既支持"完成/未完成"查询,也支持 UI 进度条- 所有条件只读 SaveData,无副作用
[CreateAssetMenu]策划可直接在 Inspector 中创建条件实例- 新成就 = 创建 SO + 组合条件,零代码
8.2 AchievementManager(★★★★★)
- P2-9 修复:
ServiceLocator.Unregister<AchievementManager>(this)引用比较安全卸载 - 批量检查在 SaveData 变更事件驱动(非每帧 Update)
AchievementEventChannelSO广播解锁事件:Steam 成就 / Toast / 音效各自响应
8.3 BossProgressTracker(★★★★☆)
- 订阅
_onBossFightEnded事件,记录到SaveData - 支持
TimedBossKillCondition需要的BossKillTimes字典
8.4 DifficultyManager(★★★★★)
见 §3.6,此处补充:
GetScaler(DifficultyLevel)数组遍历(O(n), n≤4):小型枚举集合,字典化收益极小_onDifficultyChanged广播:敌人/掉落/UI 实时响应
9. 叙事与过场系统
9.1 DialogueManager(★★★★★)
// OnEnable/OnDisable 安全订阅(非 RAII 但因为是 C# event 可接受)
private void OnEnable() => _inputReader.SubmitEvent += OnSubmit;
private void OnDisable() => _inputReader.SubmitEvent -= OnSubmit;
StartDialogue()幂等守卫:IsDialogueActive防重入_inputReader.EnableUIInput()自动禁用玩家移动输入_onNpcDialogueCompleted广播 npcId:QuestManager 订阅推进目标ResolveVariant()根据 WorldStateRegistry 选条件对话分支(可扩展)- 打字机效果在 Coroutine 中驱动,
_skipRequested跳过
9.2 CutsceneManager(★★★★★)
[RequireComponent(typeof(PlayableDirector))]编辑器强制检查PlayById()线性遍历_registeredCutscenes(数量极少,可接受)_onPlayCutsceneById频道触发:TimeLine Signal、游戏事件均可播放- 播放/停止时切换 Action Map,确保玩家控制与过场互斥
IsPlaying属性供外部查询,防止叠加播放
9.3 对话数据结构(★★★★☆)
DialogueSequenceSO 承载对话行序列;CutsceneSO 承载 Timeline Asset 引用:
- 数据与逻辑分离:策划编辑 SO,程序员维护 Manager
- 改善点:
ConditionalVariant中conditionFlag字符串尚未完全接入 WorldStateRegistry(注释 TODO)
10. UI 与 HUD 系统
10.1 UIManager(★★★★★)
Panel 栈管理:
OpenPanel(GameObject)推栈 + 动画CloseTopPanel()弹栈,自动回退到上一个面板- 事件频道触发:
PauseMenuController不直接引用 UIManager 面板列表
10.2 HUDController(★★★★★)
// 8 个事件频道订阅,OnEnable/OnDisable 对称
if (_onHPChanged != null) _onHPChanged.OnEventRaised += UpdateHP;
- 纯事件驱动:Player 发变化事件,HUD 响应,无任何
Update轮询 - HP Cell、Spring Icon 动态实例化(RebuildHPCells / RebuildSpringIcons):最大 HP 变化时重建,符合数据驱动
- 交互提示
ShowInteractPrompt(string)/HideInteractPrompt()频道分离:世界对象不依赖 UI 层
10.3 PauseMenuController(★★★★★)
- 按钮事件绑定在
Awake()中(而非 Start),避免首帧前点击无响应 Application.Quit直接绑定_btnQuit.onClick(简洁,无需中间层)GoToMainMenu()通过 SceneLoadRequest 频道切换(无 SceneManager 直调)- Settings 面板开关委托给 UIManager,层次清晰
10.4 ToastManager / FloatingDamageText(★★★★☆)
ToastManager:队列化提示,防止叠加显示FloatingDamageText:对象池驱动(依赖 GlobalObjectPool),伤害数字动画 Coroutine- 改善点:FloatingDamageText 的伤害值格式化(临界/暴击颜色)建议提取为
DamageDisplayConfigSO
10.5 输入设备图标切换(★★★★★)
InputDeviceIconSwitcher + InputDeviceIconSetSO:
- 自动检测 GamePad / KB+M 切换图标集
InputDeviceIconSetSO数据驱动:Xbox/PS/Switch 各一份 SO,切换零代码InputReaderSO.DeviceChangedEvent触发:UI 即时响应
10.6 重绑定系统(★★★★★)
RebindPanel + RebindActionRow:
ConflictDetector:绑定前检查冲突,防止两个操作共用同一按键- 绑定结果持久化到
PlayerPrefs(JSON) RebindActionRow支持 Composite(WASD)的分轴显示
11. VFX 与视觉反馈系统
11.1 HurtFlashController(★★★★★)
- 受击白闪:
MaterialPropertyBlock写入_FlashAmount,零 Material 实例化 - Flash 持续时间从
FeedbackConfigSO读取,策划可调 - Coroutine 归零:避免受击打断未完成的闪烁
11.2 PostProcessManager(★★★★☆)
- URP Volume Profile 运行时 Override
- Boss 战开始时提升 Vignette / ChromaticAberration
- 改善点:多个 Volume Override 共享 Lerp 系数,建议引入
PostProcessPresetSO描述每种场景的目标参数
11.3 PaletteSwapSystem(★★★★☆)
- GPU 端颜色替换:
Texture2DLUT 映射,1 DrawCall 无开销 RegionLightController区域暖/冷色调:Sprite Renderer Tint 批量设置
11.4 HitFXSpawner(★★★★★)
// 命中时通过 VFXPool.Play() 触发特效,fire-and-forget
_vfxPool?.Play(_config.HitVFX, hitPoint, Quaternion.identity);
- 订阅
HitConfirmedEventChannelSO:无需在 HitBox 内直接引用 VFX VFXCatalogSO数据驱动:不同武器/元素命中特效在 SO 中配置
12. 音频系统
12.1 BGMController(★★★★★)
- P2-7 修复:
CompositeDisposable管理所有 SO 事件订阅 - 跨场景 BGM 继续播放(同 clipId 不重新开始)
NullAudioService空对象模式:测试场景无需配置音频组件
12.2 接口隔离(★★★★★)
IAudioService 接口:BGM / SFX / 音量等操作全部接口化,实现可替换(正式 / Mock / Null)
13. 平台与支持系统
13.1 SteamPlatformService(★★★★★)
#if UNITY_STANDALONE && STEAMWORKS_NET
public class SteamPlatformService : IPlatformService
{
public void UnlockAchievement(string id)
=> SteamUserStats.SetAchievement(id);
}
#endif
- 条件编译隔离:非 Steam 平台零引用,
NullPlatformService无操作 PlatformBootstrap运行时选择实现并注册到 ServiceLocator
13.2 AccessibilityManager(★★★★★)
- P3-4 修复:第二实例
Destroy(this)+LogWarning(健壮的单例守卫) ColorBlindFilter运行时切换 URP Renderer FeatureAccessibilitySettingsSO持久化无障碍偏好
13.3 AnalyticsManager(★★★★☆)
- 事件驱动采集:无任何 Update 轮询
#if !DEVELOPMENT_BUILD控制编译,开发阶段不上报
13.4 AntiSoftlockSystem(★★★★★)
- 已在 MasterCodeReview 中详细分析
HardAbilityGate/RoomEscapeInfoSO完整逃脱链路,防止玩家卡死在无跳跃的深渊
13.5 SpeedrunTimer(★★★★★)
- TMP 文字更新,无 GC
IGameState订阅 Boss 场景入/出事件自动暂停/继续- 计时精度
Time.unscaledDeltaTime(不受暂停影响)
13.6 本地化系统(★★★★☆)
P3-5 完整实现:
- 双层缓存:
Language → table → Dictionary<key, value> - PlayerPrefs 持久化语言选择
Language.EnglishFallback:缺失 key 不崩溃- JSON Resources 加载:
Resources/Localization/{lang}/{table}.json - 改善点:大型项目建议迁移到 Unity Localization Package(Addressables 后端),Resources 目录随语言增多会臃肿
14. 性能工程综述
14.1 零分配热路径
| 路径 | 手段 |
|---|---|
| HUD 更新 | 纯事件驱动,零 Update |
| VFX 播放 | 对象池 Queue,零 new GameObject |
| 敌人受击 | DamageInfo 结构体,栈分配 |
| 动画状态机 | Animancer ClipTransition 预创建,零字符串查找 |
| 成就检查 | SaveData 变更时触发,非每帧 |
| LOS 检查 | 空间网格分批,O(cells) 而非 O(n²) |
14.2 内存管理
| 机制 | 实现 |
|---|---|
| Addressables 释放 | AssetReleaseTracker 场景销毁自动批量 Release |
| 粒子池 | VFXPool 超时自动回收,防止内存膨胀 |
| 对象池上限 | MaxCount > 0 强制上限 |
| SO 状态隔离 | _backing 字段防编辑器污染 |
| WorldStateRegistry | OnEnable 清除,防 Domain Reload 脏数据 |
14.3 GC 分析
最终剩余 GC 源(P3 修复后):
| 来源 | 频率 | 优先级 |
|---|---|---|
string.Format 伤害文字 |
每次命中 | P4(低频) |
UnityEngine.Debug.Log 字符串构建 |
调试时 | P4(构建时 strip) |
Dialogue 打字机 char 遍历 |
对话中 | P4(可接受) |
Resources.Load 本地化 JSON |
语言切换时 | P4(一次性) |
整体热路径(战斗/移动/动画)无 GC,达到商业级标准。
15. 可扩展性综述
15.1 新敌人类型
- 继承
EnemyBase - 实现
IEnemyState具体状态 - 配置
EnemyStatsSO+EnemyAnimationConfigSO - 创建 Behavior Designer 行为树(可选)
- 无需修改任何现有类
15.2 新护符(Charm)
- 实现
CharmEffect子类(OnEquip/OnUnequip) - 创建
CharmSO资产,引用效果列表 - 加入
CharmCatalogSO - 无需修改 EquipmentManager
15.3 新成就
- 创建
AchievementCondition子类(可选,12 个内置条件很可能够用) - 创建
AchievementSO资产,组合条件 - 加入
AchievementManager._allAchievements - 零代码改动
15.4 新咒语(Spell)
- 扩展
SpellEffectType枚举 SpellManager.ExecuteSpellEffect()添加 case- 创建
SpellSO资产 - 核心咒语逻辑完整,扩展成本极低
15.5 新关卡区域
- 创建关卡场景,放置
RoomController+RoomCamera - 创建
MapRoomDataSO填写坐标/区域归属 - 配置
CameraTriggerZone触发相机切换 - 无需修改 CameraStateController 或 MapManager
16. 编辑器友好性综述
16.1 Inspector 设计
| 实践 | 覆盖率 |
|---|---|
[Header] 分组 |
~95% 有多字段的组件 |
[Tooltip] 关键字段 |
~60%(可提升) |
[Min] / [Range] 约束 |
配置类 SO 全覆盖 |
[CreateAssetMenu] |
所有 SO 类覆盖 |
Debug.Assert 必要依赖 |
全主要组件覆盖 |
16.2 执行顺序控制
-1000: GameManager(最先)
-900: DifficultyManager, GlobalObjectPool
-800: SaveManager, EventBusMonitor
-100: CameraStateController, PlayerController
0: 其他 MonoBehaviour(默认)
DefaultExecutionOrder 完整,无初始化时序 Bug。
16.3 事件总线监控
EventBusMonitor:运行时查看所有已注册频道的订阅者数量和最近一次触发时间。策划测试时直接可见事件流向,调试效率极高。
16.4 Gizmos 支持
关键组件(RoomVisibleArea, CameraTriggerZone, HazardZone)实现 OnDrawGizmos,场景视图中可见范围框。
17. 模块评分汇总
| 模块 | 评分 | 亮点 | 改善点 |
|---|---|---|---|
| Core Events | ★★★★★ 10 | RAII + CompositeDisposable + 事件总线监控 | — |
| ServiceLocator | ★★★★★ 9.5 | 双 API + 引用比较 Unregister | 考虑 IoC 容器替代 |
| GameStateMachine | ★★★★★ 9.5 | 字符串常量 + 集中 RegisterStates | — |
| ObjectPool | ★★★★★ 9.5 | Addressables 预热 + MaxCount + LinkedList O(1) 移除 | — |
| SaveSystem | ★★★★★ 9.5 | SHA-256 + goto 迁移链 + JsonExtensionData | — |
| PlayerController | ★★★★★ 9.5 | RequireComponent + 19状态 + InputBuffer | — |
| ParrySystem | ★★★★★ 9.5 | 5阶段精确FSM + CounterWindow | — |
| FormController | ★★★★★ 9.5 | 双层事件 SO+C# | — |
| EquipmentManager | ★★★★★ 9.5 | Strategy Charm + 缓存 UsedNotches | — |
| Combat (HitBox/Projectile) | ★★★★★ 9.5 | DamageFlags 位掩码 + 4种投射物继承 | — |
| BatchLOSSystem | ★★★★★ 9.5 | 空间网格 O(1) 注销 | — |
| CameraStateController | ★★★★★ 9.5 | Cinemachine 封装 + BlendProfile | — |
| WorldStateRegistry | ★★★★★ 9.5 | 泛化+具名双API + OnEnable 清除 | — |
| AchievementConditions | ★★★★★ 9.5 | 12条件Strategy模式 + GetProgress UI | — |
| VFXPool | ★★★★☆ 9.0 | Coroutine 自动回收 + 超时兜底 | Warmup 异步版 |
| QuestManager | ★★★★☆ 9.0 | 事件驱动 + O(1) 字典索引 | 使用老式 += 订阅 |
| HUDController | ★★★★★ 9.5 | 8 频道纯事件驱动 | — |
| UIManager | ★★★★★ 9.0 | Panel 栈管理 | 动画曲线外置 SO |
| DialogueManager | ★★★★★ 9.5 | 打字机 + 跳过 + 条件分支 | TODO: WorldState 查询 |
| CutsceneManager | ★★★★★ 9.5 | Timeline 封装 + PlayById | — |
| DifficultyManager | ★★★★★ 9.5 | SteelSoul 降级保护 + ISaveable | — |
| DeathRespawnService | ★★★★★ 9.0 | 局部 lambda 订阅 Coroutine | TODO: 加载存档 |
| AssetLoader/ReleaseTracker | ★★★★★ 9.5 | 自动批量 Release | — |
| LocalizationManager | ★★★★☆ 9.0 | 双层缓存 + Fallback | Resources 路径扩展性 |
| SpellManager | ★★★★☆ 8.5 | 数据SO+冷却 | ExecuteSpellEffect TODO 分支 |
| StatusEffects | ★★★★☆ 8.5 | 独立计时 + 字典管理 | 无法堆叠同类效果 |
| BossSystem | ★★★★☆ 8.5 | WeakPointSystem + 多元素 | IBossPattern 接口缺失 |
| ShopSystem | ★★★★☆ 8.5 | SO 库存 + 购买校验 | 对话ID硬编码 |
| Tutorial | ★★★★☆ 8.5 | ContextualHintTrigger 场景触发 | — |
| Analytics | ★★★★☆ 8.5 | 事件驱动 + 条件编译 | — |
| PostProcessManager | ★★★★☆ 8.5 | URP Volume 运行时 | 建议 PresetSO |
18. 残留改善点(P4 建议)
以下为非阻塞性优化建议,按收益/成本排序:
P4-1 ✅ QuestManager 订阅模式升级
// 现状:老式 += 订阅
_onEnemyDied.OnEventRaised += HandleEnemyDefeated;
// 目标:统一 RAII 模式
private readonly CompositeDisposable _subs = new();
private void OnEnable()
=> _onEnemyDied?.Subscribe(HandleEnemyDefeated).AddTo(_subs);
private void OnDisable()
=> _subs.Clear();
状态:已修复。QuestManager.cs 新增 private readonly CompositeDisposable _subs = new(),OnEnable 改用 Subscribe(...).AddTo(_subs),OnDisable 仅一行 _subs.Clear()。全库事件订阅模式 100% 统一。
P4-2 ✅ DeathRespawnService 复活流程完整实现
public IEnumerator StartRespawnCoroutine()
{
_onRespawnStarted?.Raise();
yield return new WaitForSeconds(_respawnFadeDuration);
// TODO: 加载存档场景 ← 需实现
}
状态:已修复。DeathRespawnService 新增 [SerializeField] private SceneLoadRequestEventChannelSO _onSceneLoadRequest,StartRespawnCoroutine 通过该频道广播带 IsRespawn = true 的场景加载请求,复用 SceneService 路径,零直接 SceneManager 调用。
P4-3(低优先)本地化系统迁移
Resources.Load 方案在语言文件 > 100 条时仍可接受,但如需支持 DLC 语言包,建议迁移到 Unity Localization Package。
P4-4(低优先)SpellManager ExecuteSpellEffect 分支实现
private void ExecuteSpellEffect(SpellSO spell)
{
switch (spell.effectType)
{
case SpellEffectType.Projectile: // TODO
case SpellEffectType.AreaOfEffect: // TODO
case SpellEffectType.SummonShade: // TODO
case SpellEffectType.TeleportBlink: // TODO
}
}
架构已完整,各分支逻辑待填充。
P4-5(低优先)FloatingDamageText 显示配置化
// 建议:DamageDisplayConfigSO
[SerializeField] private DamageDisplayConfigSO _displayConfig;
// 配置临界色 / 暴击色 / 字体缩放曲线
P4-6 ✅ DialogueManager ConditionalVariant 完整接入
// 现有 TODO:
var resolved = ResolveVariant(sequence);
// ResolveVariant 尚未完整查询 WorldStateRegistry
状态:已修复。DialogueManager 新增 [SerializeField] private WorldStateRegistry _worldState;ResolveVariant 由 static 改为实例方法,TODO 替换为 _worldState.HasFlag(variant.conditionFlag) 真实查询;_worldState == null 时回退原序列(向后兼容)。
P4-7(信息)StatusEffect 堆叠设计
若游戏后期需要毒素/火焰叠加伤害,需在 StatusEffectManager 中将 Dictionary<Type, StatusEffect> 改为 Dictionary<Type, List<StatusEffect>>,并为每个效果维护独立计时器。目前单层字典架构符合当前设计要求。
附录:关键设计决策记录
A. 为何选择 SO 事件频道而非 C# 静态事件?
- SO 事件在 Inspector 中可见、可调试、可在 EventBusMonitor 中监控
- 编辑器测试场景无需启动完整游戏即可触发事件
- 频道可在多个场景中共享(Persistent 场景 + 关卡场景共用同一 SO)
- 避免静态事件在场景切换后的订阅残留问题
B. 为何选择 ServiceLocator 而非 DI 框架(Zenject/VContainer)?
- Unity 项目引入全量 DI 框架增加新人上手成本
- ServiceLocator 在本项目规模(424文件/30模块)完全够用
GetOrDefault<T>返回 null 的模式与 Unity 的空引用检查哲学一致
C. 为何 PlayerController 不用 RequireComponent?
ParrySystem是可选能力(能力解锁后才添加),编译期 RequireComponent 会强制 Prefab 上必须有此组件- 改用
[SerializeField]手动绑定 +if (parrySystem != null)守卫,支持渐进式能力开放
文档生成时间:2026 全 P0–P3 修复后
覆盖文件:424 个 .cs 文件,30 个 Assembly Definition
综合评分:9.5 / 10(商业 AA 对标)