Log Detail

vampire-like Git 变更总结

修复战斗状态切换到商店或升级时敌人异常残留问题,通过事件订阅和标志位在敌人出现时立即隐藏

2026/06/26 basil/vampire-like commit: 4250e48e..4250e48e

敌人残留状态切换事件订阅

仓库:vampire-like

本次变更说明

本次仅包含 1 个 commit(4250e48e),主要修复了从 GameStateBattle 切换到 GameStateShopLevelUp 时敌人异常残留的问题。涉及 3 个文件,共 +43 / -8 行,全部为代码改动。


具体改动分析

1. EnemyManagerComponent.cs (+40 / -0)

  • 新增了一个 _isStopped 布尔字段,用于标记当前是否应该阻止新敌人出现。
  • 订阅了 ShowEntitySuccessEventArgs 事件(在 AwakeStart 中),并在事件处理中判断 _isStopped 状态:如果已停止,则立即隐藏刚生成的敌人实体。
  • 在对应的生命周期中注册和注销事件订阅(即 Subscribe / Unsubscribe)。

为什么这么做?
在状态切换时,如果直接调用清除逻辑,可能会遇到异步操作的问题。敌人实体的生成请求在发送给游戏框架(Game Framework)后是无法取消的——即使使用 CancellationToken 取消后续流程,但 ShowEntity 请求已经发出,框架内部仍会执行并触发 ShowEntitySuccess 事件。因此,用 _isStopped 标志位配合事件订阅,可以在敌人出现时立刻将其隐藏,而不是试图取消已发出的请求。这是一种“事后拦截”而非“事前阻止”的策略,虽然有些取巧,但在现有框架约束下是相对安全的做法。

2. EnemyRegistry.cs (+2 / -7)

  • 移除了 using UnityEngine; 引用。
  • 删除了对 _enemyById.ContainsKey(entityId) 的提前检查逻辑,简化了代码。

这部分改动属于清理工作,可能与本次修复无关,但顺手整理了冗余代码。

3. Launcher.unity (+1 / -1)

  • 将某个整数值从 0 改为 1

背景说明:这个改动与本次敌人残留修复无关。之前为了打包测试,将游戏框架的资源模式从编辑器模式临时改成了 AssetBundle 模式,现在只是改回来。属于配置回滚,不影响游戏逻辑。


决策反思

  • 为什么选择事件订阅 + 标志位,而不是直接清除所有敌人?
    如上所述,ShowEntity 请求一旦发出就无法取消。直接清除已生成的敌人虽然能解决视觉残留,但可能会破坏框架内部的状态一致性(例如实体对象池引用未清理导致后续报错)。而利用事件在敌人出现瞬间隐藏,既保证了不再产生新敌人,又避免了破坏框架已有的生命周期管理。

  • _isStopped 的复位时机
    在 commit message 中已经明确:_isStoppedInitReset 方法中进行切换——这两个方法对应 GameStateBattle 的初始化与重置。所以当战斗再次开始时,标志位会被正确清空,不会影响正常战斗流程。

  • 潜在的竞态条件
    如果玩家在多个状态之间快速切换(例如反复进商店再回战斗),_isStopped 的切换与事件订阅/注销是同步的,且事件处理本身在主线程执行,因此不会出现竞态条件。唯一需要关注的是事件订阅是否在适当时机被注销——当前代码在 OnDestroyDispose 中注销,生命周期管理是明确的。


后续关注点

  1. 验证其他状态切换(如 GameStateShop → GameStateBattle)是否也存在类似残留问题。当前方案只覆盖了离开战斗的场景。
  2. 在战斗重新开始或场景重载时,确认 _isStopped 能被正确复位(目前看来由 Init / Reset 保证,但仍需测试边界情况)。
  3. 检查事件订阅/注销的配对,避免内存泄漏——尤其当玩家多次进出战斗时,确保不会重复订阅。
  4. 移除的 using UnityEngine 不会影响其他未记录的 MonoBehaviour 调用(当前 EnemyRegistry 不再直接依赖 Unity 类型,移除是安全的)。

注:本文由模型 deepseek-v4-flash 生成(草稿与终稿同模型)。