Files
zeling_v2/Docs/Review/FrameworkReview_2026_May_v9.md

22 KiB
Raw Blame History

框架代码全量评审 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 CopilotClaude 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 零 GCPostProcessManager 数组复用
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 正面亮点(新发现)

P1DialogueUI — StringBuilder 零分配打字机

// 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。在对话密集型游戏中这是生产级别的性能优化写法。


P2HurtFlashController — MaterialPropertyBlock 零 GC 着色器修改

// 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 + 重启 保证不叠层。


P3ICharmEffect — OCP 插件式装备效果

// 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,不修改 EquipmentManagerEquipmentContext 依赖注入避免了各 Effect 直接 GetComponent 或访问 ServiceLocator。[Serializable] 标记使效果可在 Inspector 中堆叠配置。


P4PostProcessManager — 数组复用避免频繁分配

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战触发时无额外分配。


P5EventChainManager — 帧合并批量评估

// 不管同帧内触发多少事件Update 中只执行一次 DoEvaluateAll()
private void EvaluateAll() => _evaluatePending = true;

private void Update()
{
    if (!_evaluatePending) return;
    _evaluatePending = false;
    DoEvaluateAll();
}

意义场景加载时可能同帧触发多个条件事件OnRoomEntered + OnAbilityUnlocked等_evaluatePending 标志位确保所有条件都在同一帧内设置完毕后,只做一次 O(n×m) 的链遍历,避免重复评估。


P6EventChainManager — 编辑器静态调试事件

#if UNITY_EDITOR
    public static event Action<string, string> OnChainExecutedInEditor;
#endif

// 执行完成时推送
#if UNITY_EDITOR
    OnChainExecutedInEditor?.Invoke(chain.chainId, "执行完成");
#endif

意义:条件编译静态事件,零运行时开销,供 EditorWindow 实时展示链执行日志,是框架调试能力的优秀设计。


P7CameraTriggerZone — ExecuteAlways + OnDrawGizmos

[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 友好设计的典范。


P8TutorialManager — O(1) HashSet 完成状态 + ISaveable

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>.ContainsO(n)→O(1))。提示状态通过 ISaveable 持久化,跨存档不重复触发,设计完整。ContextualHintTrigger 的能力门控(HasAbility(AbilityType))进一步防止提前触发。


P9AssetReleaseTracker — Addressables 生命周期精准管理

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 生命周期管理方案,有效防止场景卸载后的内存泄漏。


P10LocalizationManager — 双层缓存 + ISaveable 语言持久化

// 双层 Dictionarylanguage+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保持数据存储一致性。


P11OnHitEffect — 装备/卸下时的正确 RAII 订阅管理

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-1UnderwaterAudioController 缺失事件订阅

位置Assets/Scripts/Audio/UnderwaterAudioController.cs

问题

// 当前public 方法等待外部直接调用,与框架事件驱动模式不一致
public void EnterWater() { _mixer?.FindSnapshot("Underwater")?.TransitionTo(_transitionDuration); }
public void ExitWater()  { _mixer?.FindSnapshot("Default")?.TransitionTo(_transitionDuration); }

同类组件 WaterDangerStateUnderwaterPostProcessingController 均正确订阅 LiquidEventChannelSOUnderwaterAudioController 却是例外形成不一致。若外部调用方PlayerController 等)被移除,音频切换将静默失效。

修复:添加 LiquidEventChannelSO 引用和 OnEnable/OnDisable 自订阅(详见第六节)。


🟡 v9-A-1GlobalSFXPlayer 使用 static _instance 单例

位置Assets/Scripts/Audio/GlobalSFXPlayer.cs

问题

private static GlobalSFXPlayer _instance;  // 与框架 ServiceLocator 约定不一致

框架内全服务应通过 ServiceLocator.Register/Get<T> 管理,static _instance 是第二个此类例外AccessibilityManager 为第一)。两者都有合理的使用场景(全局静态调用 API但这种模式若扩散会侵蚀框架一致性。

建议:标注为"已知框架约定例外,仅允许此两处使用",或将 Play 改为通过 ServiceLocator.GetOrDefault<IAudioService>() 代理。本次不强制修复,记录为 Technical Debt。


🟡 v9-DC-1Collectible.Despawn 未归还对象池

位置Assets/Scripts/World/Collectible.cs

问题

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-2FloatingDamageText 假设 Screen Space - Overlay Canvas

位置Assets/Scripts/UI/FloatingDamageText.cs

问题

_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 SumO(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-1UnderwaterAudioController 添加事件自订阅

修复前:仅有 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-01v8 已记录) 已记录,暂缓
TD-03 World/Collectible.cs Despawn 未归还 GlobalObjectPoolGeo 类型) 已记录,暂缓
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