Unity 实体异步安全与空引用防护

摘要

针对 Unity 游戏代码中实体(Entity)在异步流程后被回收/隐藏、回调中仍直接访问引用而引发 NRE 的问题,形成一套防御性编码规范:立即使用处判空、延迟回调中只捕获值类型 id、目标解析失败用 0 表示”无目标”。

问题根因

  • NRE 出现在实体的异步流程之后(实体可能已被回收/隐藏)。
  • 常见场景:代码在 await/延迟后或回调中仍直接访问 EntityEntity.Id,此时父对象已被回收。

解决策略

立即使用处判空

  • 在立即使用 Entity.Id 的地方先判断 Entity != null

延迟回调捕获 id

  • 在延迟回调中先捕获 Entity.Id(int)到局部变量,回调内只使用该 id,避免直接访问 Entity 引用。

目标解析防御

  • 解析目标实体 id(例如 homing 子弹)时做防御性 null 检查,无法解析时使用 0(表示”无目标”)。

补丁模板

延迟闭包场景

// before
DOVirtual.DelayedCall(dt, () => {
    if (GameApp.Entity.GetEntity(Entity.Id) != null)
        GameApp.Entity.HideEntity(Entity);
});
 
// after
var cap = Entity != null ? Entity.Id : 0;
DOVirtual.DelayedCall(dt, () => {
    if (cap != 0 && GameApp.Entity.GetEntity(cap) != null)
        GameApp.Entity.HideEntity(cap);
});

await 后访问 id

// before
await SomeAsync();
if (GameApp.Entity.GetEntity(Entity.Id) != null)
    GameApp.Entity.HideEntity(Entity);
 
// after
if (Entity == null) return;
var id = Entity.Id;
await SomeAsync();
if (id != 0 && GameApp.Entity.GetEntity(id) != null)
    GameApp.Entity.HideEntity(id);

工程化建议(可选后续)

  • 全仓库自动扫描所有可能直接访问 Entity.Id 或将 Entity 传给 HideEntity(...)GetEntity(Entity.Id) 的位置。
  • 将 HideEntity 调用统一为 id 版本(int),更易防 NRE。
  • 为实体基类或工具增加便捷方法:protected bool TryHideSelf()EntityUtil.SafeHide(EntityLogicBase logic),集中处理判空与日志。
  • 为延迟/异步回调建立规范:所有交给延迟/定时器/Task 的回调只捕获原始 id(值类型),不捕获引用类型 Entity。

关联概念

来源回溯