547 lines
23 KiB
Markdown
547 lines
23 KiB
Markdown
# 泽灵 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`):
|
||
|
||
```csharp
|
||
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**:
|
||
|
||
```csharp
|
||
// 非法转换 → 返回 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 对象承载逻辑:
|
||
|
||
```csharp
|
||
_stateObjs[EnemyStateType.Hurt] = new EnemyHurtState();
|
||
// 子类可在 base.Awake() 后替换:
|
||
_stateObjs[EnemyStateType.Hurt] = new BossSpecialHurtState();
|
||
```
|
||
|
||
`ForceState()` 完成三步 Exit → 赋值 → Enter,干净无副作用。
|
||
|
||
**缺陷**:`EnemyBase.TakeDamage()` 中 TODO:
|
||
|
||
```csharp
|
||
// 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),通过两种方式构造:
|
||
|
||
```csharp
|
||
// 热路径:零堆分配,直接从 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 状态效果双结构(✅ 优秀)
|
||
|
||
```csharp
|
||
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`,自动纳入构建前扫描:
|
||
|
||
```csharp
|
||
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 订阅模式(✅ 优秀)
|
||
|
||
```csharp
|
||
// 组合式,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 伤害构造(✅ 优秀)
|
||
|
||
```csharp
|
||
// 热路径首选:零分配
|
||
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` 提供所有常用属性的简称:
|
||
|
||
```csharp
|
||
// 在任意状态中直接使用
|
||
Anim.Play(AnimCfg.Run);
|
||
Move.Jump();
|
||
Stats.TakeDamage(info);
|
||
Buffer.Consume(InputType.Jump);
|
||
```
|
||
|
||
避免重复的 `_owner.` 链式访问,代码可读性接近 Celeste 的 `Player.cs`。
|
||
|
||
### 5.4 Null-Object 模式(✅ 良好)
|
||
|
||
```csharp
|
||
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()` 不覆盖 | (语义差异明确,但命名可更直白:`RegisterOrReplace` / `RegisterOnce`) |
|
||
|
||
### 5.6 ISaveable 手动注册(⚠️)
|
||
|
||
```csharp
|
||
// 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` 检测按键冲突(重映射安全保障)
|
||
|
||
**不足**:
|
||
- `InputReaderSO` C# 事件(非 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)
|
||
|
||
**优势**:
|
||
- `DamageInfo` struct 流水线:RawDamage → Amount(护盾修改) → FinalDamage(防御减免)—— 清晰的三段式
|
||
- `DamageFlags` / `DamageTags` 位域枚举:单值携带多语义(CanBeParried | IgnoreIFrame)
|
||
- `HurtBox` 8步流水线顺序固定(无敌帧 → 弹反 → 霸体 → 护盾 → 防御减免 → TakeDamage → 广播 → DoT)
|
||
- `ClashResolver` 拼刀碰撞检测独立为组件,不污染 `HitBox`
|
||
- `ParrySystem` 仅暴露窗口状态(`ConsumeParry()`),不引用玩家具体类型
|
||
|
||
**不足**:
|
||
- `HitBox` 无法限制同一帧对同一 HurtBox 的多次触发(需 `_hitCooldown` + HashSet 去重)——当前 `_hitCooldown` 仅是全局冷却,多目标情况下可能误伤
|
||
- `PoiseWindowConfig` 存在但 `PlayerController.GetCurrentPoiseLevel()` 固定返回 `PoiseLevel.None`(未实现)
|
||
|
||
---
|
||
|
||
## 七、优先修复建议(按影响面排序)
|
||
|
||
### P1(影响正确性)
|
||
|
||
1. **EnemyBase.TakeDamage() 霸体判断 TODO**
|
||
- 现状:Stagger 触发 hardcode,POCO 路径不完整
|
||
- 修复:在 `TakeDamage()` 中根据 `DamageInfo.Break` 和当前霸体等级选择 `Stagger` 或 `Hurt`
|
||
|
||
2. **HitBox 同目标重入保护**
|
||
- 现状:`_hitCooldown` 仅限制全局频率,多目标情况下可能一帧命中同一 HurtBox 多次
|
||
- 修复:维护 `HashSet<Collider2D> _hitThisActivation`,`Deactivate()` 时清空
|
||
|
||
3. **ISaveable 自动注册**
|
||
- 现状:8+ 个实现类手动 Register/Unregister
|
||
- 修复:在 `SaveManager.Awake()` 中 `FindObjectsOfType<ISaveable>()` 批量注册(允许一次 FindObjects)
|
||
|
||
### P2(影响质量)
|
||
|
||
4. **移除 AudioManager.Instance 单例**
|
||
- 仅通过 `ServiceLocator.Get<IAudioService>()` 访问
|
||
|
||
5. **StatusEffectManager.CreateEffect() 替换 switch**
|
||
- 改为 `[SerializeField] private StatusEffectMappingSO _mapping`,设计师可配置 DamageType→Effect
|
||
|
||
6. **AntiSoftlockSystem 加入命名空间**
|
||
- 当前无 namespace,建议 `namespace BaseGames.Support.AntiSoftlock`
|
||
|
||
7. **BatchLOSSystem 删除冗余 _results List**
|
||
- 结果已通过回调传递,`_results` 字段仅占内存,删除即可
|
||
|
||
### P3(体验优化)
|
||
|
||
8. **HurtBoxEditor 改用 SerializedProperty**
|
||
- 避免反射字段名依赖,防止重命名导致 Editor 静默失效
|
||
|
||
9. **SOValidationRunner 使用枚举结果**
|
||
- `ValidationResult` { Error, Warning } 代替关键字字符串匹配
|
||
|
||
10. **VFXPool.Play() 同步取池**
|
||
- 池命中时跳过协程,直接同步设置 Transform 并播放
|
||
|
||
11. **完成 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 商业标准**。核心机制(战斗流水线、状态机、存档系统、对象池)设计扎实,接口边界清晰,后续扩展成本低。
|
||
|
||
主要短板集中在**三个方面**:
|
||
|
||
1. **遗留单例模式**(7 处)与 ServiceLocator 并存——形成双入口访问隐患
|
||
2. **数个模块处于 Phase 1 / Stub 阶段**(Audio Phase 2、霸体判断、IEnemyState Phase 2)
|
||
3. **缺乏自动化测试覆盖**——ServiceLocator 测试基础设施已就绪,但测试文件数量为零
|
||
|
||
若在当前基础上补齐上述三点,该代码库完全达到**独立发行商业游戏**的代码质量要求。
|
||
|
||
---
|
||
|
||
*本报告由 GitHub Copilot 自动分析生成,基于源代码静态阅读,不包含运行时 Profile 数据。*
|