v10 全量评审:修复 TD-06 至 TD-12(InputReader 移除资产扫描回退 / EmergencySave 解除 LocalFileStorage 直接依赖 / AccessibilityManager 注册 IAccessibilityService / HUDController HP/SpringIcon SetActive 复用 / MovingPlatform 缓存 WaitForSeconds / RewardSO IRewardTarget 解耦 Quest←Player 依赖 / CrashReporter 频率限制崩溃日志)
This commit is contained in:
537
Docs/Review/FrameworkReview_2026_May_v9.md
Normal file
537
Docs/Review/FrameworkReview_2026_May_v9.md
Normal file
@@ -0,0 +1,537 @@
|
||||
# 框架代码全量评审 v9 — 2026 May
|
||||
|
||||
> **范围**:`Assets/Scripts/` 全量覆盖(v1-v9 累计)
|
||||
> **对比基准**:v8(综合 8.99/10,已修复 BreadcrumbTracker + GlobalObjectPool)
|
||||
> **v9 新增覆盖**:Audio、Cutscene、Dialogue、VFX、Equipment/Effects、Progression、EventChain、Tutorial、World/Puzzle 等 35+ 文件
|
||||
> **评审人**:GitHub Copilot(Claude Sonnet 4.6)
|
||||
|
||||
---
|
||||
|
||||
## 一、综合评分(v9)
|
||||
|
||||
| 维度 | v8 分数 | v9 分数 | 变化 | 说明 |
|
||||
|------|---------|---------|------|------|
|
||||
| **架构设计** | 9.3 | 9.4 | +0.1 | ICharmEffect OCP 模式 + EventChain 条件批评估优秀 |
|
||||
| **性能** | 8.8 | 9.0 | +0.2 | DialogueUI StringBuilder、HurtFlash MaterialPropertyBlock、PostProcess 复用数组三项零分配亮点 |
|
||||
| **可扩展性** | 9.2 | 9.3 | +0.1 | Equipment Effects 插件式扩展 + PuzzleReceiver 虚方法设计 |
|
||||
| **编辑器友好** | 9.0 | 9.1 | +0.1 | CameraTriggerZone ExecuteAlways+OnDrawGizmos、EventChain 编辑器静态事件 |
|
||||
| **使用便利性** | 9.0 | 9.0 | 0 | 维持高水位,部分小问题抵消 |
|
||||
| **代码一致性** | 8.8 | 8.8 | 0 | GlobalSFXPlayer 仍使用 static singleton(同 AccessibilityManager) |
|
||||
| **数据层设计** | 9.2 | 9.2 | 0 | 维持 |
|
||||
| **综合** | **8.99** | **9.08** | **+0.09** | — |
|
||||
|
||||
**修复后预计:9.12 / 10**
|
||||
|
||||
---
|
||||
|
||||
## 二、v9 新覆盖模块总览
|
||||
|
||||
| 模块 | 文件数 | 质量评价 |
|
||||
|------|--------|----------|
|
||||
| `Audio/` | 3 | GlobalSFXPlayer✓(架构约定问题),FootstepMaterialMarker⭐精简,UnderwaterAudioController⚠缺事件订阅 |
|
||||
| `Camera/CameraTriggerZone` | 1 | ⭐ ExecuteAlways + OnDrawGizmos 编辑器可视化范本 |
|
||||
| `Cutscene/` | 2 | CutsceneSO 数据设计完整,CutsceneTrigger 4模式灵活 |
|
||||
| `Dialogue/` | 2 | ⭐⭐ DialogueUI StringBuilder 零分配打字机 |
|
||||
| `VFX/` | 5 | ⭐⭐ HurtFlashController 零 GC,PostProcessManager 数组复用 |
|
||||
| `UI/` | 2 | BossHPBar 事件驱动完整,FloatingDamageText Canvas适配问题 |
|
||||
| `Equipment/Effects/` | 7 | ⭐⭐ ICharmEffect OCP 设计极佳 |
|
||||
| `Equipment/EquipmentManager` | 1 | ⭐ Notch系统+ISaveable+Result模式 |
|
||||
| `Progression/` | 3 | AchievementManager ISaveable 完整,HPContainerPickup 事件解耦 |
|
||||
| `EventChain/` | 2 | ⭐ 批量评估 + 编辑器调试事件 |
|
||||
| `Tutorial/` | 2 | TutorialManager ISaveable+HashSet O(1),能力门控优雅 |
|
||||
| `World/` | 4 | SavePoint/DeathShade/Collectible 设计清晰 |
|
||||
| `Core/Assets/` | 2 | AssetLoader + AssetReleaseTracker 组合完整 |
|
||||
|
||||
---
|
||||
|
||||
## 三、v9 正面亮点(新发现)
|
||||
|
||||
### ⭐⭐ P1:DialogueUI — StringBuilder 零分配打字机
|
||||
|
||||
```csharp
|
||||
// Assets/Scripts/Dialogue/DialogueUI.cs
|
||||
private StringBuilder _sb = new StringBuilder(256);
|
||||
|
||||
private IEnumerator TypeLine(string fullText)
|
||||
{
|
||||
_sb.Clear();
|
||||
for (int i = 0; i < fullText.Length; i++)
|
||||
{
|
||||
_sb.Append(fullText[i]);
|
||||
_dialogueText.SetText(_sb); // TMP 直接接受 StringBuilder,零字符串分配
|
||||
yield return _typeInterval;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**意义**:逐帧调用 `TMP_Text.SetText(StringBuilder)` 而非 `text = string.Substring(...)`,完全规避打字机效果中每帧的字符串 GC。在对话密集型游戏中,这是生产级别的性能优化写法。
|
||||
|
||||
---
|
||||
|
||||
### ⭐⭐ P2:HurtFlashController — MaterialPropertyBlock 零 GC 着色器修改
|
||||
|
||||
```csharp
|
||||
// Assets/Scripts/VFX/HurtFlashController.cs
|
||||
private static readonly int _flashColorId = Shader.PropertyToID("_FlashColor");
|
||||
private static readonly int _flashAmountId = Shader.PropertyToID("_FlashAmount");
|
||||
private MaterialPropertyBlock _mpb;
|
||||
|
||||
private void Awake() => _mpb = new MaterialPropertyBlock();
|
||||
|
||||
public void Flash()
|
||||
{
|
||||
if (_flashCoroutine != null) StopCoroutine(_flashCoroutine);
|
||||
_flashCoroutine = StartCoroutine(DoFlash());
|
||||
}
|
||||
|
||||
private IEnumerator DoFlash()
|
||||
{
|
||||
_renderer.GetPropertyBlock(_mpb);
|
||||
_mpb.SetColor(_flashColorId, _flashColor);
|
||||
for (float t = 0; t < 1f; t += Time.deltaTime / _flashDuration)
|
||||
{
|
||||
_mpb.SetFloat(_flashAmountId, Mathf.Lerp(1f, 0f, t));
|
||||
_renderer.SetPropertyBlock(_mpb);
|
||||
yield return null;
|
||||
}
|
||||
_mpb.SetFloat(_flashAmountId, 0f);
|
||||
_renderer.SetPropertyBlock(_mpb);
|
||||
}
|
||||
```
|
||||
|
||||
**意义**:静态 `Shader.PropertyToID` 缓存 + `MaterialPropertyBlock` 修改 —— 每帧零材质拷贝、零 GC,标准 Unity 性能最佳实践。Flash 重入时 `StopCoroutine + 重启` 保证不叠层。
|
||||
|
||||
---
|
||||
|
||||
### ⭐⭐ P3:ICharmEffect — OCP 插件式装备效果
|
||||
|
||||
```csharp
|
||||
// 6 种效果:StatModifier / AttackSpeed / SoulSpell / OnHit / SkillNumeric / SkillSlotOverride / WeaponOverride
|
||||
public interface ICharmEffect
|
||||
{
|
||||
void OnEquip(EquipmentContext ctx);
|
||||
void OnUnequip(EquipmentContext ctx);
|
||||
string GetEffectDescription();
|
||||
}
|
||||
|
||||
// EquipmentContext 依赖注入(无硬引用)
|
||||
public class EquipmentContext
|
||||
{
|
||||
public PlayerStats Stats;
|
||||
public MMF_Player Feedback;
|
||||
public SkillModifierRegistry SkillMods;
|
||||
public WeaponManager WeaponMgr;
|
||||
}
|
||||
```
|
||||
|
||||
**意义**:完美符合开闭原则 —— 新增装备效果只需实现 `ICharmEffect`,不修改 `EquipmentManager`。`EquipmentContext` 依赖注入避免了各 Effect 直接 `GetComponent` 或访问 ServiceLocator。`[Serializable]` 标记使效果可在 Inspector 中堆叠配置。
|
||||
|
||||
---
|
||||
|
||||
### ⭐ P4:PostProcessManager — 数组复用避免频繁分配
|
||||
|
||||
```csharp
|
||||
private Volume[] _managedVolumes;
|
||||
private float[] _startWeights; // 与 _managedVolumes 等长,复用
|
||||
|
||||
private IEnumerator TransitionWeights(float[] targets, float duration)
|
||||
{
|
||||
for (float t = 0; t < duration; t += Time.deltaTime)
|
||||
{
|
||||
float f = t / duration;
|
||||
for (int i = 0; i < _managedVolumes.Length; i++)
|
||||
_managedVolumes[i].weight = Mathf.Lerp(_startWeights[i], targets[i], f);
|
||||
yield return null;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**意义**:复用 `_startWeights` 浮点数组,避免每次过渡创建临时数组,在过场/死亡/Boss战触发时无额外分配。
|
||||
|
||||
---
|
||||
|
||||
### ⭐ P5:EventChainManager — 帧合并批量评估
|
||||
|
||||
```csharp
|
||||
// 不管同帧内触发多少事件,Update 中只执行一次 DoEvaluateAll()
|
||||
private void EvaluateAll() => _evaluatePending = true;
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (!_evaluatePending) return;
|
||||
_evaluatePending = false;
|
||||
DoEvaluateAll();
|
||||
}
|
||||
```
|
||||
|
||||
**意义**:场景加载时可能同帧触发多个条件事件(OnRoomEntered + OnAbilityUnlocked等),`_evaluatePending` 标志位确保所有条件都在同一帧内设置完毕后,只做一次 O(n×m) 的链遍历,避免重复评估。
|
||||
|
||||
---
|
||||
|
||||
### ⭐ P6:EventChainManager — 编辑器静态调试事件
|
||||
|
||||
```csharp
|
||||
#if UNITY_EDITOR
|
||||
public static event Action<string, string> OnChainExecutedInEditor;
|
||||
#endif
|
||||
|
||||
// 执行完成时推送
|
||||
#if UNITY_EDITOR
|
||||
OnChainExecutedInEditor?.Invoke(chain.chainId, "执行完成");
|
||||
#endif
|
||||
```
|
||||
|
||||
**意义**:条件编译静态事件,零运行时开销,供 EditorWindow 实时展示链执行日志,是框架调试能力的优秀设计。
|
||||
|
||||
---
|
||||
|
||||
### ⭐ P7:CameraTriggerZone — ExecuteAlways + OnDrawGizmos
|
||||
|
||||
```csharp
|
||||
[ExecuteAlways]
|
||||
[RequireComponent(typeof(BoxCollider2D))]
|
||||
public class CameraTriggerZone : MonoBehaviour
|
||||
{
|
||||
private void OnDrawGizmos()
|
||||
{
|
||||
Gizmos.color = new Color(0.2f, 0.5f, 1f, 0.25f);
|
||||
Gizmos.DrawCube(transform.position, GetComponent<BoxCollider2D>().size);
|
||||
Gizmos.color = Color.blue;
|
||||
Gizmos.DrawWireCube(transform.position, GetComponent<BoxCollider2D>().size);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**意义**:场景编辑时实时可视化触发区域,`[RequireComponent]` 防止遗漏依赖,是 Level Designer 友好设计的典范。
|
||||
|
||||
---
|
||||
|
||||
### ⭐ P8:TutorialManager — O(1) HashSet 完成状态 + ISaveable
|
||||
|
||||
```csharp
|
||||
private readonly HashSet<string> _completedHints = new();
|
||||
|
||||
public void ShowHint(string hintId, string text, float duration = 4f)
|
||||
{
|
||||
if (_completedHints.Contains(hintId)) return; // O(1) 查找
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
**意义**:使用 `HashSet<string>` 替代 `List<string>.Contains`(O(n)→O(1))。提示状态通过 `ISaveable` 持久化,跨存档不重复触发,设计完整。`ContextualHintTrigger` 的能力门控(`HasAbility(AbilityType)`)进一步防止提前触发。
|
||||
|
||||
---
|
||||
|
||||
### ⭐ P9:AssetReleaseTracker — Addressables 生命周期精准管理
|
||||
|
||||
```csharp
|
||||
public class AssetReleaseTracker : MonoBehaviour
|
||||
{
|
||||
private readonly List<AsyncOperationHandle> _handles = new();
|
||||
|
||||
public void Track<T>(AsyncOperationHandle<T> handle) => _handles.Add(handle);
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
foreach (var h in _handles)
|
||||
if (h.IsValid()) Addressables.Release(h);
|
||||
_handles.Clear();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**意义**:挂在场景根节点,`OnDestroy` 自动批量释放,与 `AssetLoader` 配合形成完整的 Addressables 生命周期管理方案,有效防止场景卸载后的内存泄漏。
|
||||
|
||||
---
|
||||
|
||||
### ⭐ P10:LocalizationManager — 双层缓存 + ISaveable 语言持久化
|
||||
|
||||
```csharp
|
||||
// 双层 Dictionary:language+table → (key → value)
|
||||
private readonly Dictionary<string, Dictionary<string, string>> _cache = new();
|
||||
|
||||
// ISaveable 持久化语言偏好(不用 PlayerPrefs)
|
||||
public void OnSave(SaveData data) { data.Settings.Language = _currentLanguage; }
|
||||
public void OnLoad(SaveData data) { SetLanguage(data.Settings.Language); }
|
||||
```
|
||||
|
||||
**意义**:语言切换时仅替换一层缓存,查找 O(1)。语言偏好归入统一存档系统而非 PlayerPrefs,保持数据存储一致性。
|
||||
|
||||
---
|
||||
|
||||
### ⭐ P11:OnHitEffect — 装备/卸下时的正确 RAII 订阅管理
|
||||
|
||||
```csharp
|
||||
public class OnHitEffect : ICharmEffect
|
||||
{
|
||||
private IDisposable _sub;
|
||||
|
||||
public void OnEquip(EquipmentContext ctx)
|
||||
{
|
||||
_sub = ctx.HitEvents?.Subscribe(OnHitConfirmed);
|
||||
}
|
||||
|
||||
public void OnUnequip(EquipmentContext ctx)
|
||||
{
|
||||
_sub?.Dispose();
|
||||
_sub = null;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**意义**:装备时订阅事件、卸下时 Dispose,完美的 RAII 模式。与 MonoBehaviour 生命周期无关的订阅管理,防止卸下装备后继续响应命中事件。
|
||||
|
||||
---
|
||||
|
||||
## 四、v9 发现的问题
|
||||
|
||||
### 🔴 v9-P-1(低):UnderwaterAudioController 缺失事件订阅
|
||||
|
||||
**位置**:`Assets/Scripts/Audio/UnderwaterAudioController.cs`
|
||||
|
||||
**问题**:
|
||||
```csharp
|
||||
// 当前:public 方法等待外部直接调用,与框架事件驱动模式不一致
|
||||
public void EnterWater() { _mixer?.FindSnapshot("Underwater")?.TransitionTo(_transitionDuration); }
|
||||
public void ExitWater() { _mixer?.FindSnapshot("Default")?.TransitionTo(_transitionDuration); }
|
||||
```
|
||||
|
||||
同类组件 `WaterDangerState`、`UnderwaterPostProcessingController` 均正确订阅 `LiquidEventChannelSO`,`UnderwaterAudioController` 却是例外,形成不一致。若外部调用方(PlayerController 等)被移除,音频切换将静默失效。
|
||||
|
||||
**修复**:添加 `LiquidEventChannelSO` 引用和 `OnEnable/OnDisable` 自订阅(详见第六节)。
|
||||
|
||||
---
|
||||
|
||||
### 🟡 v9-A-1(低):GlobalSFXPlayer 使用 static _instance 单例
|
||||
|
||||
**位置**:`Assets/Scripts/Audio/GlobalSFXPlayer.cs`
|
||||
|
||||
**问题**:
|
||||
```csharp
|
||||
private static GlobalSFXPlayer _instance; // 与框架 ServiceLocator 约定不一致
|
||||
```
|
||||
|
||||
框架内全服务应通过 `ServiceLocator.Register/Get<T>` 管理,`static _instance` 是第二个此类例外(AccessibilityManager 为第一)。两者都有合理的使用场景(全局静态调用 API),但这种模式若扩散会侵蚀框架一致性。
|
||||
|
||||
**建议**:标注为"已知框架约定例外,仅允许此两处使用",或将 `Play` 改为通过 `ServiceLocator.GetOrDefault<IAudioService>()` 代理。本次不强制修复,记录为 Technical Debt。
|
||||
|
||||
---
|
||||
|
||||
### 🟡 v9-DC-1(低):Collectible.Despawn 未归还对象池
|
||||
|
||||
**位置**:`Assets/Scripts/World/Collectible.cs`
|
||||
|
||||
**问题**:
|
||||
```csharp
|
||||
private void Despawn()
|
||||
{
|
||||
gameObject.SetActive(false); // 仅禁用,未归还 GlobalObjectPool
|
||||
}
|
||||
```
|
||||
|
||||
对于 `Geo` 类型(货币)等由 EnemyBase.OnDeath 实例化的 Collectible,频繁战斗会积累大量禁用 GameObject。正确做法是通过 `IObjectPoolService.Return()` 归还。
|
||||
|
||||
**注**:`HPOrb` 和场景内静态 `Item` 型 Collectible 不受影响(非运行时创建),此问题仅影响动态生成的 Geo。
|
||||
|
||||
**建议**:`Despawn` 改为调用 `ServiceLocator.GetOrDefault<IObjectPoolService>()?.Return(gameObject)`,并配合 GlobalObjectPool 预热 GeoCollectible。本次标注为 TD,后续优化时处理。
|
||||
|
||||
---
|
||||
|
||||
### 🟡 v9-DC-2(低):FloatingDamageText 假设 Screen Space - Overlay Canvas
|
||||
|
||||
**位置**:`Assets/Scripts/UI/FloatingDamageText.cs`
|
||||
|
||||
**问题**:
|
||||
```csharp
|
||||
_rectTransform.anchoredPosition = (Vector2)_cam.WorldToScreenPoint(currentWorld);
|
||||
```
|
||||
|
||||
`Camera.WorldToScreenPoint` 返回屏幕像素坐标,赋值给 `anchoredPosition` 仅在 Canvas 为 Screen Space - Overlay、且 Canvas Scaler 为 `Constant Pixel Size / Scale = 1` 时正确。若使用 `Screen Space - Camera` 或不同分辨率缩放,坐标将偏移。
|
||||
|
||||
**建议**:改为 `RectTransformUtility.ScreenPointToLocalPointInRectangle(canvasRect, screenPos, cam, out localPoint)`,使其适配所有 Canvas 模式。本次标注为 TD。
|
||||
|
||||
---
|
||||
|
||||
## 五、各模块深度评估(v9 新增部分)
|
||||
|
||||
### 5.1 Dialogue 模块
|
||||
|
||||
| 项 | 评价 |
|
||||
|----|------|
|
||||
| `DialogueUI.TypeLine` StringBuilder | ⭐⭐ 零分配,TMP 最佳实践 |
|
||||
| `WaitForSecondsRealtime` | 正确:不受暂停影响 |
|
||||
| `SkipTyping()` | 立即完成打字,体验友好 |
|
||||
| `InteractableNPC.virtual GetCurrentDialogue()` | 虚方法设计,子类可根据世界状态返回不同对话 |
|
||||
| 评分 | **9.6 / 10** |
|
||||
|
||||
---
|
||||
|
||||
### 5.2 Equipment 模块
|
||||
|
||||
| 项 | 评价 |
|
||||
|----|------|
|
||||
| `ICharmEffect` + 7 种效果 | ⭐⭐ 完美 OCP,可无限横向扩展 |
|
||||
| `EquipmentContext` 依赖注入 | 避免 Effect 直接访问全局状态 |
|
||||
| `UsedNotches` 缓存值 | 避免每帧 LINQ Sum,O(1) 查询 |
|
||||
| `TryEquipCharm` Result 模式(null/string) | 简洁实用的错误信息返回 |
|
||||
| `ISaveable` 序列化装备槽 | 完整存档集成 |
|
||||
| `ToolSlotManager`(未读) | 需在后续确认是否一致 |
|
||||
| 评分 | **9.5 / 10** |
|
||||
|
||||
---
|
||||
|
||||
### 5.3 EventChain 模块
|
||||
|
||||
| 项 | 评价 |
|
||||
|----|------|
|
||||
| 条件-动作分离(ChainCondition / ChainAction) | ⭐ 解耦设计,链可由 Designer 配置 |
|
||||
| `_evaluatePending` 帧合并批评估 | ⭐ 正确实现,Update + 标志位 |
|
||||
| `cond.Register(this)` 绑定中继事件 | 链条件自主订阅,无需外部注册 |
|
||||
| `OnEnable cond.ResetState()` | 防跨 PlayMode 状态残留 |
|
||||
| `_completedChains` HashSet | O(1) 重复链保护 |
|
||||
| 编辑器静态事件 | ⭐ 零运行时开销的调试能力 |
|
||||
| 评分 | **9.4 / 10** |
|
||||
|
||||
---
|
||||
|
||||
### 5.4 Progression / Achievement 模块
|
||||
|
||||
| 项 | 评价 |
|
||||
|----|------|
|
||||
| `AchievementManager` ISaveable | 进度持久化完整 |
|
||||
| `Progress 0-1` 浮点进度 | UI 进度条友好 |
|
||||
| `AchievementRuntimeState` 运行时分离 | 不污染 ScriptableObject 资产 |
|
||||
| `HPContainerPickup` 事件驱动 | 零耦合,正确解耦 SaveSystem 操作 |
|
||||
| 评分 | **9.1 / 10** |
|
||||
|
||||
---
|
||||
|
||||
### 5.5 Tutorial 模块
|
||||
|
||||
| 项 | 评价 |
|
||||
|----|------|
|
||||
| `HashSet<string>` O(1) 完成查询 | ⭐ 正确数据结构选择 |
|
||||
| `ISaveable` 提示完成状态持久化 | 完整 |
|
||||
| `ContextualHintTrigger` 能力门控 | 优雅,避免提前触发 |
|
||||
| `ShowHint` + `CompleteHint` 分离 | API 清晰,职责单一 |
|
||||
| 评分 | **9.2 / 10** |
|
||||
|
||||
---
|
||||
|
||||
### 5.6 VFX 模块
|
||||
|
||||
| 项 | 评价 |
|
||||
|----|------|
|
||||
| `HurtFlashController` MaterialPropertyBlock | ⭐⭐ 零 GC,生产级 |
|
||||
| `PostProcessManager` _startWeights 复用 | ⭐ 无额外分配 |
|
||||
| `RegionLightController` 双协程独立 tween | 优雅,可独立中断颜色/强度 |
|
||||
| `VFXCatalogSO` 延迟初始化 + Debug.Assert | 防止未初始化调用 |
|
||||
| 评分 | **9.3 / 10** |
|
||||
|
||||
---
|
||||
|
||||
### 5.7 World 模块(新增部分)
|
||||
|
||||
| 项 | 评价 |
|
||||
|----|------|
|
||||
| `PuzzleReceiver` 虚方法设计 | ⭐ 子类安全覆写,MMFeedbacks 集成 |
|
||||
| `SavePoint` ISaveable + IInteractable | 双接口实现,完整 |
|
||||
| `DeathShade.Interact` → 事件 → Destroy | 零耦合 Geo 回收 |
|
||||
| `Collectible` isPersistent 存档控制 | 设计清晰 |
|
||||
| `HazardZone` IsInstantKill MaxHP×2 | 功能正确,方案略显 hack |
|
||||
| 评分 | **9.0 / 10** |
|
||||
|
||||
---
|
||||
|
||||
## 六、v9 修复项(本次执行)
|
||||
|
||||
### Fix v9-P-1:UnderwaterAudioController 添加事件自订阅
|
||||
|
||||
**修复前**:仅有 public 方法,依赖外部直接调用
|
||||
**修复后**:添加 `LiquidEventChannelSO` 订阅,与 WaterDangerState 保持一致的模式
|
||||
|
||||
---
|
||||
|
||||
## 七、历史修复复核(v7 + v8)
|
||||
|
||||
| 版本 | 修复项 | 状态 |
|
||||
|------|--------|------|
|
||||
| v7-P-1 | HitStopManager `_freezeEndTime` + max 语义 | ✅ 已验证 |
|
||||
| v7-A-1 | WallJumpState `!Move.IsRising` | ✅ 已验证 |
|
||||
| v7-A-2 | PlayerController 删除 TryTransitionState 别名 | ✅ 已验证 |
|
||||
| v7-U-2 | AttackState 删除重复事件取消订阅 | ✅ 已验证 |
|
||||
| v7-P-2 | EnemyQuotaManager swap-and-pop + _indexMap | ✅ 已验证 |
|
||||
| v7-S-1 | EnemyBase.SetAggroTickRate Debug.LogWarning | ✅ 已验证 |
|
||||
| v8-P-1 | BreadcrumbTracker 事件频道订阅替换 FindWithTag | ✅ 已验证 |
|
||||
| v8-P-2 | GlobalObjectPool.OnDestroy Addressables.Release | ✅ 已验证 |
|
||||
|
||||
---
|
||||
|
||||
## 八、完整框架技术债清单(截至 v9)
|
||||
|
||||
| ID | 优先 | 文件 | 描述 | 状态 |
|
||||
|----|------|------|------|------|
|
||||
| TD-01 | 低 | `Audio/GlobalSFXPlayer.cs` | static _instance 与 ServiceLocator 约定不一致 | 已记录,暂缓 |
|
||||
| TD-02 | 低 | `Accessibility/AccessibilityManager.cs` | 同 TD-01(v8 已记录) | 已记录,暂缓 |
|
||||
| TD-03 | 低 | `World/Collectible.cs` | `Despawn` 未归还 GlobalObjectPool(Geo 类型) | 已记录,暂缓 |
|
||||
| TD-04 | 低 | `UI/FloatingDamageText.cs` | `WorldToScreenPoint` 假设 Screen Space - Overlay Canvas | 已记录,暂缓 |
|
||||
| TD-05 | 低 | `Cutscene/CutsceneTrigger.cs` | flag 在 PlayCutscene 前写入(v9-DC-3) | 已记录,暂缓 |
|
||||
|
||||
---
|
||||
|
||||
## 九、框架整体架构评估(v9 最终版)
|
||||
|
||||
```
|
||||
全局架构健康度:★★★★★ 9/10
|
||||
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ 数据层 │
|
||||
│ ┌──────────┐ ┌────────────┐ ┌──────────────┐ ┌─────────────┐ │
|
||||
│ │SaveData │ │WorldState │ │AchievementSO │ │EventChainSO │ │
|
||||
│ │(ISaveable│ │Registry │ │ProgressState │ │ Conditions │ │
|
||||
│ │ pattern) │ │(纯 SO) │ │(Runtime分离) │ │ Actions(SO) │ │
|
||||
│ └──────────┘ └────────────┘ └──────────────┘ └─────────────┘ │
|
||||
├─────────────────────────────────────────────────────────────────┤
|
||||
│ 服务层(ServiceLocator) │
|
||||
│ IAudioService / ICameraService / IDialogueService │
|
||||
│ ISaveService / ITutorialService / IAchievementService │
|
||||
│ IObjectPoolService / ILocalizationService │
|
||||
├─────────────────────────────────────────────────────────────────┤
|
||||
│ 事件层(BaseEventChannelSO<T>) │
|
||||
│ PlayerDied / BossDefeated / LiquidEntered / RegionEntered │
|
||||
│ HitConfirmed / AbilityUnlocked / DialogueCompleted ... │
|
||||
├─────────────────────────────────────────────────────────────────┤
|
||||
│ 表现层 │
|
||||
│ Equipment(ICharmEffect OCP) / VFX(MaterialPropertyBlock) │
|
||||
│ Dialogue(StringBuilder TMP) / Tutorial(HashSet ISaveable) │
|
||||
│ EventChain(批评估+编辑器调试) / Achievement(Progress 0-1) │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
**架构核心优势**:
|
||||
1. **单向依赖图**:`Core.Events → Core.Save → Core → 业务模块`,零循环引用
|
||||
2. **三层解耦**:数据(SO/SaveData)→ 服务(ServiceLocator)→ 表现(MonoBehaviour),各层边界清晰
|
||||
3. **扩展点设计完整**:`ICharmEffect`(装备效果)、`ChainCondition/Action`(叙事链)、`IActivatable`(谜题接收器)、虚方法(PuzzleReceiver/InteractableNPC)
|
||||
4. **内存意识高**:全框架对高频路径(对话打字、命中闪烁、音量过渡)均有零分配实现
|
||||
5. **存档系统统一**:语言偏好、成就进度、教程状态、装备槽、世界状态均通过 `ISaveable` 统一管理,零 PlayerPrefs 散落
|
||||
|
||||
**架构局限(已知 TD)**:
|
||||
1. 两处 static singleton 例外(GlobalSFXPlayer / AccessibilityManager)
|
||||
2. `Collectible.Despawn` 未使用对象池(Geo 动态生成场景)
|
||||
3. `FloatingDamageText` Canvas 模式硬假设
|
||||
|
||||
---
|
||||
|
||||
## 十、v9 总结
|
||||
|
||||
**v9 评审完成全量代码覆盖**(v1-v9 累计约 120 个文件)。
|
||||
|
||||
本轮最重要发现:
|
||||
- **Equipment ICharmEffect 设计是整个框架最优雅的扩展点设计之一**,7 种效果通过 12 行接口实现完全解耦,EquipmentContext 注入替代 GetComponent 是教科书级的依赖倒置
|
||||
- **DialogueUI StringBuilder 零分配打字机**:体现了框架作者在看似简单的 UI 动画中的性能意识
|
||||
- **EventChainManager 帧合并评估 + Editor 调试事件**:在同一文件中同时体现了运行时性能优化和开发工具设计能力
|
||||
|
||||
本次实际修复:1 项(UnderwaterAudioController 事件订阅一致性)
|
||||
技术债记录:5 项,均为低优先级
|
||||
|
||||
**综合评分:9.08 / 10(修复后预计 9.12)**
|
||||
Reference in New Issue
Block a user