Files
gold_dolphin/unity/.workbuddy/memory/2026-07-07.md
T
2026-07-07 03:34:56 +08:00

12 KiB
Raw Blame History

2026-07-07

Unity 项目解耦架构重构(gold_dolphin/unity

  • 架构审计结论:定位到严重耦合信号

    • 7 个跨场景单例:GameManager/AudioManager/ScoreManager/SceneLoader(均 PersistentSingleton)、TimeController(Singleton)、EnemyManager/LightMaskSystem(static Instance)
    • GameObject.Find/FindWithTag/FindObjectsByType 散落运行时代码;GameHUD.Update 仅在 _playerHealth==null条件性 FindWithTag("Player") 补救(非每帧,但仍是反模式);EnemyAI 的 FindWithTag 仅 Start 一次
    • GameHUDtypeof(X).GetField(..., NonPublic|Instance) 反射读私有字段(health/rollCooldown/_lastEchoTime 等),字段改名会静默失效
    • God ClassGameManager(约295行,6职责)、HealthSystem(约216行,健康+死亡+溶解)PlayerController 仅轻度耦合(112行,RequireComponent 3系统),非 God Class
    • static 事件伪总线:onGameOver/onPlayerDamaged/OnEchoReleased/onScoreChanged
    • EnemyAI 直接 GetComponent<HealthSystem>().Damage() + HealthSystem 反向调 GameManager.GameOver()
    • Resources.Load 用于音频/视频
  • 已落地解耦基础框架 Assets/Architecture/(增量、零侵入、未改现有文件):

    • Core/GameEvent.cs(无参事件通道+监听器)、Core/TypedGameEvents.csGameEvent + Int/Float/Vector3/String/GameObject 通道及监听器)
    • Variables/Float/Int/Bool/Vector3 Variable,带 OnValueChanged + ContextMenu 重置)
    • RuntimeSets/RuntimeSet.cs(泛型集合 + TransformRuntimeSet + RuntimeSetRegistrar 自动注册)
    • Editor/VariableDrawer.csInspector 实时显示变量值)
  • 产出方案文档 Docs/解耦架构重构方案.md:分层架构(LO SO总线 / L1系统 / L2实体 / L3表现)、逐条 Before→After 改造映射、P0-P6 分阶段路线图。

  • 项目约定:无 .asmdef,所有代码在 Assembly-CSharpGameEvent/RuntimeSet/FloatVariable 等类名无冲突,新增代码可安全编入。

同行评审修正(2 份意见,全部核实采纳)

  • 文档 v1.1 已根据评审重写 Docs/解耦架构重构方案.md
    • 严重度下调:GameHUD 的 FindWithTag 实为 _playerHealth==null 条件回退(非每帧);PlayerController 从 God Class 降级为轻度耦合(112行/RequireComponent 3系统)。
    • 行号修正:EnemyAI.Damage():232(非 :230)。
    • 补遗漏点:EnemyHealthBar 反射读私有字段(:50-53,73-75)、GameHUD Start 的 FindObjectOfType<DamageFlashOverlay>onGameWin 静态事件被 GameResultScreen 监听、GameLostOverlayShader.FindFindObjectOfType 自检查、MainMenuUIController 对象名耦合、以及 ScorePickup/SoulDrop/ScoringUIController/MainMenuUIController/GameLostOverlayScoreManager/SceneLoader 的直接单例调用线。
  • 代码补强(评审点):TypedGameEvents.csStringEventListener(初稿声称有但未实现);RuntimeSet.cs 补泛型 RuntimeSetRegistrar<T>(强类型集合如 EnemyRuntimeSet 用,领域文件夹写一行具体闭包子类即可)。

续作:P1+P2 落地改造(代码侧,本次会话)

  • P1 静态事件 → SO 事件资产(全部完成,含 onScoreSettled
    • 发布方改写:GameManager(onGameOver/onGameWin→GameEvent)、HealthSystem(onPlayerDamaged→GameEvent)、EchoSystem(OnEchoReleased→Vector3Event)、ScoreManager(onScoreChanged/onScoreSettled→IntEvent)。
    • 订阅方改写:GameResultScreen/GameOverScreen(→onGameOver/onGameWin GameEvent.Register)、DamageFlashOverlay(→onPlayerDamagedEvent.Register)、ScoreDisplay(→scoreChangedEvent IntEvent.Register)、EnemyAI(→echoReleasedEvent Vector3Event.Register)。
    • APIGameEvent 同时支持 Inspector 监听器(GameEventListener) 与代码监听器 Register(System.Action)GameEvent 用 Register(Action<T>)
  • P2a GameHUD:接 PlayerHealth IntVariableOnValueChanged 驱动 UpdateLifeIcons+ scoreChangedEvent;技能 CD 改读 PlayerController.RollCooldown / EchoSystem.BellCooldown 公共只读属性(替代反射)。
  • P2b EnemyHealthBar 重要正确性修正:原方案让所有 HealthSystem 写同一个全局 IntVariable,会导致多敌人共享同一份血量(严重 bug)。改为 EnemyHealthBar 引用同物体 HealthSystemRequireComponent 保证),订阅其 OnHealthChanged 事件刷新;HealthSystem 的全局 healthVar/maxHealthVar 写入仅限 isPlayer。HealthSystem 新增 CurrentHealth/MaxHealth 只读属性 + OnHealthChanged 事件。
  • P2d GameManagerDisableAllEnemyAI 改用 Enemies(TransformRuntimeSet) 遍历禁用,替代 FindObjectsOfType<EnemyAI>()EnemyManager 改 SerializeField 引用。保留 FindObjectsOfType 兜底分支(enemiesSet 未接入时),接入敌人 Prefab 的 RuntimeSetRegistrar 后可删。
  • P5 Shader.Find ×5 处理GameLostOverlay(水波纹→waterRippleShader 字段)、GroundBuilder/CliffWallBuilder(AbyssEdgeGlow→edgeGlowShader 字段)EchoSystem 已有 ringShader 字段;GroundClipTool.cs:19 为 Editor 脚本(运行时不进包,保留但建议改字段)。
  • 变量跨 Play Mode 状态泄漏(用户重点反馈)VariableRegistry + IVariable.ResetToDefault() + [RuntimeInitializeOnLoadMethod(BeforeSceneLoad)] 在每次进 Play Mode 统一重置;AssetBootstrap 生成变量时同时写 _value/_defaultValue
  • UIBuilder.csBuildResultScreen 时自动 LoadAssetAtPath 连线 OnGameOver/OnGameWin 资产,使编辑器搭建的 UI 直接可用(需先运行 Architecture > Bootstrap Core Assets)。
  • 仍需手动/后续步骤(Unity 编辑器内)
    1. 运行菜单 Architecture > Bootstrap Core Assets 生成全部 SO 资产。
    2. 各 UI/系统组件 Inspector 拖入对应事件/变量资产(GameResultScreen/GameOverScreen/DamageFlashOverlay/ScoreDisplay/EnemyAI/GameHUD 等)。
    3. 敌人 Prefab 挂 RuntimeSetRegistrar(Enemies)、Player Prefab 挂 RuntimeSetRegistrar(Players),使运行时集合被填充;之后删 GameManager 的 FindObjectsOfType 兜底分支。
    4. P2e 剩余:MainMenuUIController 对象名耦合 + GameLostOverlay.cs:85 FindObjectOfType 自检查;EnemyAI.cs:112 Start 一次性 FindWithTag(待 Players 集合接入后移除)。
  • 验证:全仓 grep 确认 HealthSystem.onPlayerDamaged/ScoreManager.onScore*/EchoSystem.OnEchoReleased/GameManager.onGameOver/onGameWin 已无代码引用(仅注释),新 API 表面(OnHealthChanged/CurrentHealth/RollCooldown/BellCooldown/IVariable)均已落地。

计划/方案文档同步(用户 4 条反馈)

  • Addressables 过度:P5 改为仅 SerializeField 引用(AudioClip/VideoClip),不引入 Addressables。
  • 变量重置策略:计划 P0/P1 明确 VariableRegistry + _defaultValue 方案。
  • Shader.Find 5 处:P5 清单补全。
  • onScoreSettledP1 一并迁移。
  • 两份文档(plans/quantum-pulse-turing.mdDocs/解耦架构重构方案.md)均已更新至反映上述反馈。

编译错误修复(用户反馈 4 个 CS 错误,已全部修正)

  • GameManager.cs:55/64CS0120 —— GameOver()/Win() 是 static 方法,却直接访问实例字段 onGameOverEvent/onGameWinEvent。改为 Instance.onGameOverEvent?.Raise() / Instance.onGameWinEvent?.Raise()(方法开头已有 if (Instance == null) return; 保护,安全)。
  • GameHUD.cs:82/102CS0123 —— IntVariable.OnValueChangedAction<int>,但 UpdateLifeIcons() 是无参方法,委托签名不匹配。改为 UpdateLifeIcons(int value)(内部 currentHealth = value),并把初始直接调用 UpdateLifeIcons()UpdateLifeIcons(playerHealthVar.Value)
  • 经验教训(可复用):① 把 static 事件总线改成 SO 事件字段后,原 GameManager.GameOver()/Win() 这类 static 入口方法必须改走 Instance.字段(或把字段也变 static,但 SO 资产不应是 static);② 订阅 Variable.OnValueChangedAction<T>)时,回调方法必须带对应 T 参数,否则 CS0123。已提醒用户编译后按接线手册拖资产。
  • VariableDrawer.csCS0246 —— Architecture.Editor 命名空间下的 Editor 脚本引用 FloatVariable/IntVariable/BoolVariable/Vector3Variable(位于 Architecture.Variables)时漏 using Architecture.Variables;规则Architecture 下的 Editor 脚本都需显式 using Architecture.Variables;using Architecture.Core;AssetBootstrap.cs 已带齐,无需改。
  • UIBuilder.cs(位于 Assets/Editor/,非 Architecture/Editor):CS0246 —— 引用 GameEventArchitecture.Core)漏 using Architecture.Core;,已在文件头补上。已全仓 grep 排雷:其余引用架构类型的外部文件(DamageFlashOverlay/GameHUD/GameOverScreen/GameResultScreen/EnemyAI/EchoSystem/HealthSystem/GameManager/ScoreDisplay/ScoreManager)均已在改写时带齐 using,无其它漏网。TransformRuntimeSetGameEvent 同处 Architecture.Core 命名空间。

第二轮 Review 意见处理(2 份,全部落地)

  • 关键时序修复VariableRegistry.Register() 内立即 variable.ResetToDefault(),解决 RuntimeInitializeOnLoadMethod(BeforeSceneLoad) 在 SO 尚未 OnEnable 注册前执行 ResetAll_variables 为空)导致重置无效、跨局状态残留的问题。ResetAll 保留作兜底。
  • HealthSystem 去单例直调:玩家死亡改为 onPlayerDiedEvent?.Raise(),不再 GameManager.GameOver()GameManager 订阅 OnPlayerDiedGameOver()。新增双方 onPlayerDiedEvent 字段(同指 OnPlayerDied 资产)。
  • GameLostOverlay 解耦:删除静态 Show() / _instance / FindObjectOfType,改为订阅 OnGameOver 事件(OnEnable 注册 → Activate)。GameManager.DeathTransition 不再直接调用。
  • 空响应清理GameResultScreen 删除空 OnGameOver 方法+字段+订阅(只负责胜利);GameOverScreen 全仓无引用,已删除 .cs+.metaUIBuilder 同步移除 onGameOverEvent 接线(否则 FindProperty 返回 null 抛 NRE)。
  • 顺手修GameEvent.Raise() 改为 Inspector 监听(_listeners)先于代码监听(_codeListeners)SpiritLanternSystempublic float CooldownGameHUD 灵灯 CD 删最后一处反射;GameHUD 移除 Update 逐帧延迟订阅 ScoreManager(改为 Start 无条件注册);ScoreDisplay 初始值走事件不再访问 ScoreManager.Instance
  • 静态校验通过grep 确认无 GameOverScreen/GameLostOverlay.Show/_instance/typeof(SpiritLanternSystem) 残留;onPlayerDiedEvent(HealthSystem 发布/GameManager 订阅)、onGameOverEvent(GameManager 发布/GameLostOverlay 订阅) 一致。
  • 新增接线要求HealthSystem+GameManager 的 On Player Died EventOnPlayerDiedGameLostOverlay(场景物体) On Game Over EventOnGameOverDamageFlashOverlay 需场景预置已接线实例;GameResultScreen 仅 On Game Win Event。旧场景若残留 GameOverScreen 组件需手动移除。

Play Mode 回归(用户实测,03:15

  • 通过:魂灵数(ScoreChanged)、生命图标(PlayerHealth)、玩家死亡过场(OnPlayerDied→OnGameOver+Enemies 集合)、VariableRegistry 重置(退出再进血量=5,无跨局泄漏)。
  • 失败两项(已定位,均为 SO 事件资产接线缺口,非代码 bug):
    1. OnPlayerDamaged 受击泛红HealthSystem.Damage 第87行 onPlayerDamagedEvent?.Raise() 逻辑正确(同机制 OnPlayerDied 可通),断链原因为 HealthSystem 或/且 DamageFlashOverlay 的 On Player Damaged Event 字段为 null。
    2. EchoReleased 摇铃引敌EchoSystem.StartEcho 第220行 echoReleasedEvent?.Raise(p) + EnemyAI.OnEnable Register(OnBell) 均正确,断链原因为 EchoSystem 或/且 敌人预制体 EnemyAI 的 Echo Released Event 字段为 null(极可能玩家预制体 EchoSystem 未接)。
  • 诊断加固(已落地):给 HealthSystem / EchoSystem / EnemyAI / DamageFlashOverlay 的事件字段加 OnValidate() 编辑器实时告警(Inspector 黄色三角+控制台),并在 Raise 处加一次性空引用 Debug.LogWarning;另把 GameHUD 中「自动 new 一个 event=null 的 DamageFlashOverlay」改为仅告警不创建,避免掩盖未接线问题。
  • 结论:代码链路正确,失败=事件字段未接。用户需在编辑器看 OnValidate 警告锁定漏接组件,重拖 OnPlayerDamaged/EchoReleased 资产(注意 EnemyAI 要接预制体资产而非场景临时实例)。