23 KiB
泽灵 v2 — 全面代码评估报告
评估基准:Unity 2022.3 LTS / C# / 2D 动作平台游戏
评估标准:商业高性能游戏代码实践(参考 Hollow Knight、Celeste、Hades 等同类产品)
评估范围:Assets/Scripts全量代码(约 330 个 .cs 文件)
评估日期:2025/2026
评分总览
| 评估维度 | 得分 | 备注 |
|---|---|---|
| 架构设计 | 8.5 | 模块化扎实,少量遗留单例 |
| 性能 | 8.0 | 热路径零分配,批量 LOS 优秀 |
| 可扩展性 | 8.5 | 数据驱动完善,少量硬编码管道 |
| 编辑器友好 | 9.0 | 工具链完整,为同类 Indie 最佳水平 |
| 使用便利性 | 8.0 | 契约清晰,命名偶有不一致 |
| 综合 | 8.4 | 接近商业发行水准 |
一、架构设计(8.5 / 10)
1.1 程序集隔离(✅ 优秀)
项目共 25+ 个程序集定义(.asmdef),粒度精确到子模块层级:
BaseGames.Core → 全局服务基础设施
BaseGames.Core.Save → 持久化(单向依赖 Core)
BaseGames.Core.Events → SO 事件频道(无外部依赖)
BaseGames.Combat → 战斗管道(无 Player/Parry 直接依赖)
BaseGames.Parry → 弹反(依赖 Combat 接口,不依赖 Player)
BaseGames.Player → 玩家逻辑(处于依赖图顶层)
BaseGames.Enemies.AI → BD 任务节点
BaseGames.Editor → 仅编辑器工具
...
编译时强制模块边界,跨模块引用必须使用接口或事件频道,与商业游戏标准一致。
依赖方向控制:
Core → Core.Save(单向)
↑
GameServiceRegistrar(桥接)使用 SaveServiceAdapter 适配器,
避免 Core.Save → Core 的反向引用,保持有向无环图。
1.2 服务定位器(✅ 优秀)
ServiceLocator(BaseGames.Core):
ServiceLocator.Register<IAudioService>(this); // 覆盖注册
ServiceLocator.RegisterIfAbsent<IAudioService>(null); // 防重复
ServiceLocator.GetOrDefault<ICameraService>() // 安全取用
- 接口隔离:调用方依赖
IAudioService,不依赖AudioManager具体类 - Null-Object 兜底:
GameServiceRegistrar.Awake()先注册NullAudioService,避免AudioManager未初始化时的空引用崩溃 - 测试支持:
OverrideForTest<T>/Reset()仅在#if UNITY_EDITOR暴露
已注册接口:
IAudioService / ISaveService / ISceneService / IDeathRespawnService / IEventChannelRegistry / IObjectPoolService / ICameraService / IPlatformService
1.3 游戏状态机(✅ 优秀)
GameStateMachine 为纯 C# 类,不继承 MonoBehaviour:
// 非法转换 → 返回 false + 错误字符串(不抛出异常)
bool ok = _fsm.TransitionTo(nextId, out string error);
- 每个状态声明
ValidNextStates(合法出口白名单),状态图文档化在代码中 GameManager持有GameStateMachine实例而非继承它(组合优于继承)- 与 Celeste 等游戏的
StateMachine模式完全吻合
1.4 玩家状态机(✅ 优秀)
PlayerController 维护 Dictionary<Type, PlayerStateBase> 状态字典;
PlayerStateBase POCO 类,不继承 MonoBehaviour,生命周期由 PlayerController 驱动:
OnStateEnter → OnStateUpdate → OnStateFixedUpdate → OnStateExit
12 个具体状态(Idle / Run / Jump / Fall / Dash / AerialDash / Attack / AirAttack / UpAttack / DownAttack / Parry / Hurt / Dead / Spring / WallSlide / WallJump / Swim)各自独立,新增状态只需实现 PlayerStateBase 并在 InitializeStates() 中注册。
#if UNITY_EDITOR ValidTransitions 仅在编辑期验证,不增加运行时开销。
1.5 敌人状态系统(✅ 优秀 + 尚有空间)
Phase 1 双轨实现:EnemyStateType 枚举保持对外 API,IEnemyState POCO 对象承载逻辑:
_stateObjs[EnemyStateType.Hurt] = new EnemyHurtState();
// 子类可在 base.Awake() 后替换:
_stateObjs[EnemyStateType.Hurt] = new BossSpecialHurtState();
ForceState() 完成三步 Exit → 赋值 → Enter,干净无副作用。
缺陷:EnemyBase.TakeDamage() 中 TODO:
// TODO: 根据霸体结果选 Stagger / Hurt
ForceState(EnemyStateType.Hurt); // 霸体判断尚未走 POCO 路径
实际 Stagger 触发仍绕过 _stateObjs 字典——Phase 1 双轨不完整。
1.6 SO 事件频道(✅ 优秀)
BaseEventChannelSO<T> / VoidBaseEventChannelSO 双泛型基类:
Subscribe()返回EventSubscription(IDisposable),配合CompositeDisposable.AddTo()零泄漏- Editor 模式下
EventBusMonitor.Record()记录所有事件(帧号 + 订阅数),供EventBusMonitorWindow运行时调试 description字段:设计师在 Inspector 中注释频道用途
1.7 遗留问题(⚠️)
| 问题 | 文件 | 影响 |
|---|---|---|
GameManager.Instance 静态单例与 ServiceLocator 并存 |
GameManager.cs |
双入口访问模式 |
AudioManager.Instance 标注 [Obsolete] 但仍存在 |
AudioManager.cs |
新代码可能误用旧入口 |
VFXPool.Instance 未注册到 ServiceLocator |
VFXPool.cs |
无法 Mock / 测试 |
GlobalObjectPool.Instance 保留(已注册 IObjectPoolService,但静态 Instance 共存) |
GlobalObjectPool.cs |
同上 |
SaveManager.Instance 保留(由 DeathRespawnService 直接调用) |
DeathRespawnService.cs |
依赖具体类 |
AntiSoftlockSystem 在全局命名空间(无 namespace) |
AntiSoftlockSystem.cs |
命名空间污染 |
EquipmentManager 使用 EventChannelRegistry.Instance(非 ServiceLocator) |
EquipmentManager.cs |
不一致 |
二、性能(8.0 / 10)
2.1 战斗热路径——零堆分配(✅ 优秀)
DamageInfo 为 struct(非 class),通过两种方式构造:
// 热路径:零堆分配,直接从 SO 初始化
DamageInfo info = DamageInfo.From(damageSourceSO);
info.KnockbackDirection = ...;
// 复杂情况:Builder 模式(分配一个 Builder 对象,可接受)
DamageInfo info = new DamageInfo.Builder()
.SetRaw(50).SetType(DamageType.Slash).SetFlags(DamageFlags.CanBeParried)
.Build();
HurtBox.ReceiveDamage() 8步流水线中无任何 LINQ / Alloc 调用,性能关键路径完全符合商业标准。
2.2 批量视线检测(✅ 优秀)
BatchLOSSystem 实现时间切片策略:
每 FixedUpdate 仅处理 min(_maxRequestersPerFrame=8, total) 个请求者
_currentOffset 轮询偏移,均匀分配负载
对比朴素实现(每敌人每帧一次 Raycast2D):20 个敌人 → 减少 60%+ 射线调用。
注:_results[idx] 写入后未被读取(结果已通过 requester.ReceiveLOSResult() 直接回调),_results List 是冗余字段,可删除。
2.3 对象池(✅ 优秀)
GlobalObjectPool 特性:
| 特性 | 实现 |
|---|---|
| Addressables 预热 | WarmupAsync() 异步批量实例化 |
| LRU 回收 | MaxCount 达限时回收 LinkedList 头节点(O(1)) |
| 后台补池 | 同步 Instantiate 后触发协程异步补充 |
| 接口隔离 | 实现 IObjectPoolService,可 Mock 测试 |
VFXPool(ParticleSystem 专用池)独立维护,合理的关注点分离(ParticleSystem 生命周期与普通 GameObject 不同)。
可改进:VFXPool.Play() 每次都通过协程启动,即使对象已在池中可同步取出,协程调度有 1-2 帧延迟。
2.4 音频池(✅ 良好)
AudioManager 使用 6 源轮转 SFX 池,双 AudioSource 交叉淡入淡出 BGM,避免频繁 AudioSource.Stop/Play 切换产生的爆音与内存分配。
2.5 状态效果双结构(✅ 优秀)
private readonly List<StatusEffect> _activeList = new(); // Update 遍历 O(n)
private readonly Dictionary<StatusEffectType, StatusEffect> _activeIndex = new(); // 类型查找 O(1)
MaterialPropertyBlock 修改 Shader 属性(不修改共享材质),符合 Unity 性能最佳实践。
2.6 序列化性能(✅ 良好)
SaveManager.SaveAsync() 使用 Formatting.None(减少 JSON 体积),SemaphoreSlim(1,1) 防止并发写入损坏。
2.7 性能风险点(⚠️)
| 风险 | 位置 | 说明 |
|---|---|---|
FindObjectsOfType<AudioListener> |
GameServiceRegistrar.EnsureSingleAudioListener() |
已有 _primaryListener 绑定后可绕过,但未绑定时仍全场景扫描 |
_equipped.Sum(c => c.notchCost) LINQ |
EquipmentManager.UsedNotches |
每次 UI 查询触发 LINQ,列表小(<8)时可接受;建议缓存 |
BatchLOSSystem._results 冗余写入 |
BatchLOSSystem.cs L68 |
每帧写入后不读取,微小 GC 风险 |
VFXPool.PlayCoroutine() |
VFXPool.cs |
即使池命中,仍需协程恢复一帧 |
PlayerController._states[typeof(T)] |
PlayerController.cs |
Type 键查找无 boxing(引用比较),但每次 TransitionTo 需哈希查找 |
三、可扩展性(8.5 / 10)
3.1 护符系统(✅ 优秀)
ICharmEffect 策略模式,完全数据驱动:
CharmSO → ICharmEffect[]
├── StatModifierEffect (+攻击力/防御)
├── AttackSpeedEffect (攻速修改)
├── OnHitEffect (命中触发)
├── SkillNumericModifierEffect (技能数值)
├── SkillSlotOverrideEffect (技能槽替换)
├── WeaponOverrideEffect (武器替换)
└── SoulSpellEffect (灵力法术)
新增效果只需实现 ICharmEffect,无需修改 EquipmentManager——完美的开闭原则。
3.2 成就系统(✅ 优秀)
AchievementCondition 抽象类,10 个具体实现:
CollectedItemCondition / DefeatedBossCondition / EnteredRegionCondition /
ParryCountCondition / TimedBossKillCondition / MapExplorationCondition ...
设计师可在 AchievementSO Inspector 中组合条件,不需要代码介入。
3.3 Boss 技能系统(✅ 良好)
BossBase → EnterPhase(int) [virtual]
→ BossSkillExecutor → BossSkillSO[]
→ SkillSequenceSO (有序技能序列)
→ AttackPatternSO (技能模式 SO)
TelegraphSystem 独立为组件,可复用于不同 Boss。
3.4 EventChain 系统(✅ 良好)
EventChainSO 顺序事件链,配合 EventChainManager 执行:设计师可在 SO 中定义剧情触发序列,无需写代码。
3.5 IValidatable + SOValidationRunner(✅ 优秀)
任何 SO 实现 IValidatable,自动纳入构建前扫描:
public IEnumerable<string> Validate()
{
if (BaseDamage <= 0) yield return "❌ BaseDamage 必须 > 0";
}
SOValidationRunner 作为 IPreprocessBuildWithReport,构建中止防止错误配置上线。
3.6 可扩展性缺陷(⚠️)
| 问题 | 说明 | 建议 |
|---|---|---|
HurtBox.ReceiveDamage() 8步流水线硬编码 |
新增拦截步骤须修改 HurtBox |
引入 IDamageInterceptor[] 责任链 |
StatusEffectManager.CreateEffect() |
DamageType→StatusEffect 极可能是 switch | 改为 SO 配置映射 Dictionary<DamageType, StatusEffect> |
| 无通用属性计算器 | 护符/Buff 效果各自修改 PlayerStats 字段 |
考虑 StatCalculator 优先级栈(参考 Hades 设计) |
AudioManager.PlayBGM/SFX(string) 为桩 |
Phase 2 未完成 | 优先实现 AudioEventSO 集成 |
Spells 模块仅有 _Placeholder.cs |
施法系统留空 | 按现有 Skill 模式扩展 |
四、编辑器友好(9.0 / 10)
编辑器工具链是本项目最突出的优势,接近 AA 商业游戏水准。
4.1 工具总览
| 工具 | 位置 | 功能 |
|---|---|---|
SOValidationRunner |
Editor/Validation/ |
全量 SO 数据验证,构建前自动执行 |
AddressKeyValidator |
Editor/ |
Addressable Key 有效性验证,防止引用失效 |
AddressReferenceGraphWindow |
Editor/ |
Addressable 引用图可视化 |
HurtBoxEditor |
Editor/Combat/ |
PlayMode 受击盒注入状态可视化(绿色/橙色) |
EventBusMonitorWindow |
Editor/ |
运行时事件总线监控(频道名 + 订阅数 + 帧号) |
EventChannelEditor |
Editor/ |
事件频道 Inspector 中一键 Raise 按钮 |
BossSkillSequenceWindow |
Editor/ |
Boss 技能序列可视化设计器 |
EventChainEditorWindow |
Editor/ |
EventChain 可视化编辑器 |
CharmEffectDrawer |
Editor/Equipment/ |
护符效果自定义 PropertyDrawer |
MapRoomDataEditor |
Editor/Map/ |
地图房间数据编辑器 |
SceneScaffoldTools |
Editor/ |
场景脚手架快捷工具 |
NavSurfaceBakeShortcut |
Editor/ |
导航网格一键烘焙快捷键 |
CreateEventChannelAssets |
Editor/ |
一键创建事件频道 SO 资产菜单 |
ScriptExecutionOrderTools |
Editor/ |
执行顺序可视化管理 |
DestructibleTileEditor |
Editor/World/ |
可破坏瓦片编辑器 |
AchievementSOEditor |
Editor/Achievements/ |
成就 SO 自定义编辑器 |
4.2 Inspector 设计(✅ 优秀)
- 所有配置使用
[Header]分组,字段有[Tooltip] - 所有事件频道 SO 有
description字段(设计师可见注释) [DefaultExecutionOrder]系统范围覆盖(-2000 到 +50),执行顺序文档化在代码中[RequireComponent]保证依赖完整性,避免配置错误
4.3 潜在改进(⚠️)
| 问题 | 说明 |
|---|---|
HurtBoxEditor 用反射读取私有字段 |
字段重命名后 Editor 静默失效,建议改用 SerializedProperty 或公开只读属性 |
SOValidationRunner 错误检测靠关键字 "必须" / "❌" |
语言切换后可能失效,建议改为 ValidationResult 枚举(Error / Warning / Info) |
BatchLOSSystem 无 Editor Gizmo |
调试时无法可视化射线,建议添加 OnDrawGizmos |
EventChainEditorWindow 无截图/文档 |
新成员上手曲线较高 |
五、使用便利性(8.0 / 10)
5.1 订阅模式(✅ 优秀)
// 组合式,OnDisable 一行清理,零泄漏
private readonly CompositeDisposable _subs = new();
private void OnEnable()
{
_onPlayerSpawned.Subscribe(OnPlayerSpawned).AddTo(_subs);
_onBossFightEnded.Subscribe(OnBossEnded).AddTo(_subs);
}
private void OnDisable() => _subs.Clear();
相比裸 +=/-= 订阅,极大降低事件泄漏风险,与商业级 Rx 风格一致。
5.2 伤害构造(✅ 优秀)
// 热路径首选:零分配
DamageInfo info = DamageInfo.From(source);
// 复杂流水线:Builder
DamageInfo info = new DamageInfo.Builder()
.SetRaw(100).SetFlags(DamageFlags.CanBeParried | DamageFlags.CanClash)
.SetKnockback(Vector2.right, 10f).Build();
双模式清晰区分使用场景,符合 API 设计最佳实践。
5.3 状态类便捷属性(✅ 良好)
PlayerStateBase 提供所有常用属性的简称:
// 在任意状态中直接使用
Anim.Play(AnimCfg.Run);
Move.Jump();
Stats.TakeDamage(info);
Buffer.Consume(InputType.Jump);
避免重复的 _owner. 链式访问,代码可读性接近 Celeste 的 Player.cs。
5.4 Null-Object 模式(✅ 良好)
NullAudioService // IAudioService 空实现(Log 警告,不崩溃)
NullPlatformService // IPlatformService 空实现(PC 非 Steam 环境)
NullPathAgent // IPathAgent 空实现(无导航组件时使用)
三个 NullObject 防御了三类常见 NullReferenceException,符合商业游戏的防御性编程要求。
5.5 命名一致性问题(⚠️)
| 问题 | 对比 |
|---|---|
玩家用 TransitionTo() 转换状态 |
敌人用 ForceState() |
玩家用 GetState<T>() 取状态对象 |
敌人用 _stateObjs[enumKey] |
ServiceLocator.Get<T>() 失败抛异常 |
GetOrDefault<T>() 失败返回 null |
Register() 覆盖已有注册 |
RegisterIfAbsent() 不覆盖 |
5.6 ISaveable 手动注册(⚠️)
// SavePoint.cs, EquipmentManager.cs, AchievementManager.cs ... 各自手动调用
SaveManager.Instance.Register(this); // OnEnable
SaveManager.Instance.Unregister(this); // OnDisable
约有 8+ 个 ISaveable 实现重复此样板代码。商业实践(如 Unity 官方 Open Project)通常用 SaveManager.FindAndRegister<ISaveable>() 或 ScriptableObject 驱动的注册表统一管理。
六、专项模块深度评估
6.1 存档系统(8.5/10)
优势:
- Newtonsoft.Json 序列化(完整类型支持,无反射限制)
SaveMigrator.Migrate()版本迁移管道(向前兼容)Checksum完整性验证(防止文件损坏导致存档不可用)SemaphoreSlim防并发写入CrashReporter+EmergencySaveService崩溃保护LocalFileStorage通过ISaveStorage接口可替换(云存档、主机平台扩展点)
不足:
SaveManager.Instance仍被DeathRespawnService直接引用(应通过ISaveService)SaveData结构若需新字段,SaveMigrator需手动更新(无自动 Schema 演化)- 无存档文件加密(对 PC 存档修改作弊无防御,可接受)
6.2 输入系统(8.0/10)
优势:
InputReaderSO封装 Unity InputSystem,作为 ScriptableObject 可在不同场景共享OnEnable重置防止 Play Mode 再进入时状态残留(工程实践亮点)EnableGameplayInput()/EnableUIInput()提供明确的上下文切换InputBuffer缓冲近端输入,解决游戏手柄操作的时序问题(Coyote Time 协同)ConflictDetector检测按键冲突(重映射安全保障)
不足:
InputReaderSOC# 事件(非 SO 事件频道)与其他模块的 SO Channel 模式不一致——需订阅 C# event 而非通过 SO 引用MoveInput轮询属性与事件订阅模式并存(两种取值方式)
6.3 摄像机系统(7.5/10)
优势:
ICameraService接口隔离,RoomCamera/CameraStateController通过服务层解耦CameraBlendProfileSO数据驱动过渡曲线
不足:
Camera/_Placeholder.cs说明摄像机系统尚未完整实现CameraStateController骨架代码较多,实际行为有限
6.4 AI 系统(8.5/10)
优势:
- BD 自定义任务节点(18 个)覆盖完整 AI 行为集
#if GRAPH_DESIGNER编译守卫,无 Behavior Designer 时代码仍可编译SharedString TargetStateName(vs 原SharedInt):枚举字符串绑定,BD 图重排枚举不破坏BatchLOSSystem+ILOSRequester接口:视线检测完全与敌人类型解耦BD_WaitForAnimation使用 Animancer State 轮询而非硬编码等待时间
不足:
- BD 图中的
BlackboardVariable与EnemyBase属性之间的映射文档化不足 SetAggroTickRate()为空方法(Opsive 运行时 API 变更留存了兼容桩)
6.5 战斗系统(9.0/10)
优势:
DamageInfostruct 流水线:RawDamage → Amount(护盾修改) → FinalDamage(防御减免)—— 清晰的三段式DamageFlags/DamageTags位域枚举:单值携带多语义(CanBeParried | IgnoreIFrame)HurtBox8步流水线顺序固定(无敌帧 → 弹反 → 霸体 → 护盾 → 防御减免 → TakeDamage → 广播 → DoT)ClashResolver拼刀碰撞检测独立为组件,不污染HitBoxParrySystem仅暴露窗口状态(ConsumeParry()),不引用玩家具体类型
不足:
HitBox无法限制同一帧对同一 HurtBox 的多次触发(需_hitCooldown+ HashSet 去重)——当前_hitCooldown仅是全局冷却,多目标情况下可能误伤PoiseWindowConfig存在但PlayerController.GetCurrentPoiseLevel()固定返回PoiseLevel.None(未实现)
七、优先修复建议(按影响面排序)
P1(影响正确性)
-
EnemyBase.TakeDamage() 霸体判断 TODO
- 现状:Stagger 触发 hardcode,POCO 路径不完整
- 修复:在
TakeDamage()中根据DamageInfo.Break和当前霸体等级选择Stagger或Hurt
-
HitBox 同目标重入保护
- 现状:
_hitCooldown仅限制全局频率,多目标情况下可能一帧命中同一 HurtBox 多次 - 修复:维护
HashSet<Collider2D> _hitThisActivation,Deactivate()时清空
- 现状:
-
ISaveable 自动注册
- 现状:8+ 个实现类手动 Register/Unregister
- 修复:在
SaveManager.Awake()中FindObjectsOfType<ISaveable>()批量注册(允许一次 FindObjects)
P2(影响质量)
-
移除 AudioManager.Instance 单例
- 仅通过
ServiceLocator.Get<IAudioService>()访问
- 仅通过
-
StatusEffectManager.CreateEffect() 替换 switch
- 改为
[SerializeField] private StatusEffectMappingSO _mapping,设计师可配置 DamageType→Effect
- 改为
-
AntiSoftlockSystem 加入命名空间
- 当前无 namespace,建议
namespace BaseGames.Support.AntiSoftlock
- 当前无 namespace,建议
-
BatchLOSSystem 删除冗余 _results List
- 结果已通过回调传递,
_results字段仅占内存,删除即可
- 结果已通过回调传递,
P3(体验优化)
-
HurtBoxEditor 改用 SerializedProperty
- 避免反射字段名依赖,防止重命名导致 Editor 静默失效
-
SOValidationRunner 使用枚举结果
ValidationResult{ Error, Warning } 代替关键字字符串匹配
-
VFXPool.Play() 同步取池
- 池命中时跳过协程,直接同步设置 Transform 并播放
-
完成 AudioEventSO Phase 2 集成
PlayBGM(string)/PlaySFX(string)目前输出警告,应接入 AudioEventSO 资产查找
八、对标商业游戏评估
| 维度 | Hollow Knight 类比 | 本项目水准 |
|---|---|---|
| 模块隔离 | 单 Assembly(早期) | ✅ 25+ asmdef,更现代 |
| 事件解耦 | C# event 直连 | ✅ SO EventChannel,更可配置 |
| 存档系统 | Binary 格式 | ✅ JSON + 迁移器,更易维护 |
| 对象池 | 自定义池 | ✅ Addressables + LRU,更完整 |
| 编辑器工具 | 无 | ✅ 16 个专用工具,远超同类 |
| AI 调试 | 无 | ✅ EventBusMonitor + HurtBoxEditor |
| 状态机 | MonoBehaviour 继承 | ✅ POCO 状态对象,更轻量 |
| 单元测试 | 无 | ⚠️ ServiceLocator.Reset() 提供基础,但测试文件尚未建立 |
九、总结
本代码库在 Indie 游戏中属于顶层水准,在架构规范性、模块化程度和编辑器工具链方面已达到部分 AA 商业标准。核心机制(战斗流水线、状态机、存档系统、对象池)设计扎实,接口边界清晰,后续扩展成本低。
主要短板集中在三个方面:
- 遗留单例模式(7 处)与 ServiceLocator 并存——形成双入口访问隐患
- 数个模块处于 Phase 1 / Stub 阶段(Audio Phase 2、霸体判断、IEnemyState Phase 2)
- 缺乏自动化测试覆盖——ServiceLocator 测试基础设施已就绪,但测试文件数量为零
若在当前基础上补齐上述三点,该代码库完全达到独立发行商业游戏的代码质量要求。
本报告由 GitHub Copilot 自动分析生成,基于源代码静态阅读,不包含运行时 Profile 数据。