编程小知识之 Object.Destroy

傷城~ 2023-05-31 06:48 95阅读 0赞

本文简单描述了 Unity 中 Object.Destroy 的一些知识~

Object.Destroy 应该是 Unity 开发中最常用的函数之一了,对于该函数的一个基本认知是:

  • Object.Destroy 是异步执行的,并不会立即生效

Object.Destroy 的异步特性让下面这种销毁代码成为了可能:

  1. for (int i = 0; i < transform.childCount; ++i)
  2. {
  3. var child = transform.GetChild(i);
  4. Object.Destroy(child.gameObject);
  5. }

如果 Object.Destroy 是同步执行的话,我们就不能简单的通过递增的索引(i)来获取 transform 所有的子节点,因为 Object.Destroy 之后,子节点的索引会产生变化(递减).

有一个简单技巧可以解决同步销毁过程中子节点索引递减的问题,那就是从后往前销毁子节点,这种方式可以保证各个子节点的索引在销毁过程中不会发生变化:

  1. for (int i = transform.childCount - 1; i >= 0; --i)
  2. {
  3. var child = transform.GetChild(i);
  4. Object.Destroy(child.gameObject);
  5. }

可惜技巧大多有扩展性不高的问题,当我们需要销毁某几个子节点(而非所有子节点)的时候,这个技巧便没有什么用处了~

Object.Destroy 的异步特性还带来一些恼人的陷阱,考虑以下代码:

  1. // codes destroy obj first
  2. Object.Destroy(obj);
  3. ...
  4. // before truely destroy,
  5. // check obj and pass it to some logic if valid
  6. if (obj)
  7. {
  8. SomeLogic(obj);
  9. }
  10. ...
  11. // after truely destroy,
  12. // some logic use obj, Oops ...
  13. obj.DoSomething();

由于 Object.Destroy 的异步特性,在调用 Object.Destroy 之后(但在真正执行销毁操作之前),销毁对象(obj)仍然是有效的,不注意这点就容易产生很多的(无效)对象访问错误.

自己来维护有效引用是规避这种陷阱的一种方法:

  1. // codes destroy obj first
  2. Object.Destroy(obj);
  3. // manually set obj to null
  4. obj = null;
  5. ...
  6. // before truely destroy,
  7. // check obj and pass it to some logic if valid,
  8. // since obj is null here, we will skip pass
  9. if (obj)
  10. {
  11. SomeLogic(obj);
  12. }

关于 Object.Destroy 普遍还有一些类似的错误认知:

  • Object.Destroy 下一帧才会真正生效
  • Object.Destroy 过几帧之后才会真正生效
  • Object.Destroy 本帧不会生效,下一帧开始后就真正生效了

实际上, Unity 文档中已经说的很清楚:

Actual object destruction is always delayed until after the current Update loop, but will always be done before rendering.

实际的销毁操作发生于本帧的 Update 之后,结束于本帧的 渲染 之前.不过根据我的测试,实际销毁操作的窗口期要更小一些,应该至少是 发生于本帧的 Update 之后,结束于本帧的 LateUpdate 之前(当然测试结果并不足以确定问题,实际开发中还是应该按照文档的说明为主).

基于此,我们就可以明确 Object.Destroy 真正生效的时间点了:

  • 实际的销毁操作发生于本帧的 Update 之后,结束于本帧的 渲染 之前.
  • 如果在实际的销毁操作发生之前(譬如 Update 中)调用 Object.Destroy,那么实际的销毁操作就会在本帧(Update 之后,渲染之前)生效.
  • 如果在实际的销毁操作发生之后(譬如 OnGUI 中)调用 Object.Destroy,那么实际的销毁操作就会在下一帧(Update 之后,渲染之前)生效.

这里贴下 Unity 中脚本事件的流程图,可以帮助我们明确各个事件发生顺序:

在这里插入图片描述

发表评论

表情:
评论列表 (有 0 条评论,95人围观)

还没有评论,来说两句吧...

相关阅读

    相关 编程知识生成排列

    > 本文简述了两种生成排列的方法 生成排列的方法不少,一种经典的方法就是采用递归,假设我们需要求解 1 ~ n 的所有排列,那么我们假设已经求解了 1 ~ n - 1 的所有

    相关 编程知识性能优化

    > 本文简述了一种性能优化中常见的思维误区 程序开发总是离不开性能优化,虽然规避不成熟优化的[箴言][Link 1]早已有之,但我们又往往会被自己翻涌的思维火花所牵绊,义无反