编程小知识之 Object.Destroy

2019-10-24 21:08:42 浏览数 (1)

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。undefined本文链接:https://cloud.tencent.com/developer/article/1526995

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

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

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

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

代码语言:javascript复制
for (int i = 0; i < transform.childCount;   i) 
{
    var child = transform.GetChild(i);
    Object.Destroy(child.gameObject);
}

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

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

代码语言:javascript复制
for (int i = transform.childCount - 1; i >= 0; --i) 
{
    var child = transform.GetChild(i);
    Object.Destroy(child.gameObject);
}

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

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

代码语言:javascript复制
// codes destroy obj first
Object.Destroy(obj);

...

// before truely destroy,
// check obj and pass it to some logic if valid
if (obj) 
{
    SomeLogic(obj);
}

...

// after truely destroy,
// some logic use obj, Ops ...
obj.DoSomething();

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

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

代码语言:javascript复制
// codes destroy obj first
Object.Destroy(obj);
// manually set obj to null
obj = null;

...

// before truely destroy,
// check obj and pass it to some logic if valid,
// since obj is null here, we will skip pass
if (obj) 
{
    SomeLogic(obj);
}

关于 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 人点赞