Unity开发——Destory延迟销毁导致异常的处理
一、简单介绍Destory
Unity中,Destory是用于销毁物体对象,物体组件,游戏资源的方法;
是Unity中用于管理对象生命周期、释放资源的重要方法,合理使用它可以有效地优化游戏性能和内存管理。
1、函数方法介绍:
public static void Destroy(Object obj, float time = 0.0f);
obj:要销毁的对象,可以是物体,组件,资源;
time:可指定延迟多久后再执行销毁操作,以秒为单位;
如果没有指定时间参数,默认会在当前帧结束后,渲染前销毁对象。
(指不指定,都不会立马销毁物体)
2、销毁不同对象的异同:
(1)GameObject:会销毁该对象及其组件和所有子物体;
(2)Component:会将该组件从所属的GameObject身上移除销毁;
(3)Asset:一些加载进来的资源,如纹理、材质等,都可以使用Destory进行销毁;
二、使用Destory注意事项
1、物品销毁存在时间延迟:并不是执行Destory语句后,就立马对物品进行销毁!!!
实际的对象销毁操作,总是延迟到当前帧结束后,渲染前销毁对象。
执行Destory语句后,在当前帧的剩余时间里,要被销毁的对象仍然存在,并且可以被访问和操作的!!!
同一帧里,前面的代码执行Destory销毁物体,但物品还是存在状态,后面的代码仍然能检测获取到物体。
如:玩家身上有装备,先从玩家身上卸下/销毁,然后才能将新物品替换上去,因为destory是延迟销毁,不是立马销毁,同一帧里执行了destory后,物品也没有销毁,执行物品替换就容易出现异常,在真正执行销毁时,出现销毁异常,引起物品的引用异常;
2、不要在遍历集合(数组、列表等)时,直接使用Destory销毁集合中的元素;
删除会导致集合长度变化,导致集合元素和索引的映射关系发生变化,一边遍历一边删除,容易导致操作和数据异常;
3、注意资源的管理和引用关系,避免出现资源泄漏或其他问题;
如,前面删除了资源,但引用还存在,导致引用异常;引用不存在了,但资源物体还存在,导致内存泄漏等问题;
三、Destory延迟销毁导致异常的处理方法
1、DestroyImmediate();强制立即销毁;(编辑器下推荐使用)
(1)方法说明
此方法会立即销毁对象。
一旦调用,对象会马上从场景中移除,相关资源也会被立即释放。
之后,对该对象的任何引用都会变为null。
若在脚本中调用DestroyImmediate销毁一个游戏对象,那么在调用之后,该对象就不复存在了,后续代码无法再对其进行访问或操作。
(2)函数方法介绍:
public static void DestroyImmediate(Object obj, bool allowDestroyingAssets = false);
obj:同Destory,要销毁的对象,可以是物体,组件,资源;
allowDestroyingAssets:是否销毁只销毁实例对象,是否销毁资源。true允许销毁资源;
默认值为false,只销毁场景中的实例对象。
(3)和Destory区别
Destroy:
Destroy是异步的,当前帧结束后销毁;运行时和编辑器模式都支持;更安全,适合大多数场景;
允许Unity引擎在执行销毁之前完成必要的清理工作,对性能友好;
不是立即生效,可能在某些需要即时销毁的场景下不够灵活;
DestroyImmediate :
DestroyImmediate只能在主线程中使用,会立即销毁对象;运行时慎用,限编辑器模式;
不够安全,适用于需要快速释放资源的场景,但要谨慎操作,仅在必要时使用;
会立即释放对象资源,可能因此会造成性能开销,在运行时频繁使用可能会影响游戏性能;
也可能导致未定义行为,容易出现引用已销毁的对象情况:
被销毁对象的引用会变成null,要避免在销毁对象后继续访问该对象,以防出现空引用异常。
若要销毁资源(如纹理、材质等),需要将allowDestroyingAssets
参数设为true
,要谨慎操作,避免意外销毁重要资源;
2、GameObject = null;强制置空,跳过等待删除;(推荐使用)
(1)先将要删除的对象赋值给临时/指定变量,进行引用存储;
方便后面通过临时变量来对实例物品删除,也避免后面直接对物品置空操作先于对物品删除导致引用丢失,引用异常;
(2)对赋值的临时/指定变量执行Destory操作,真正对物体执行销毁;
临时变量不属于功能逻辑执行里的检测操作对象,可延迟销毁,不影响正常代码的运行执行;
同时也实现对要销毁的实例对象执行销毁;
(3)然后直接将要立即销毁的对象置为null,实现功能上的立即删除,避免等待延迟删除;
//if(itemGo != null)DestroyImmediate(itemGo);//功能上需要立即销毁,否则会出错
if(itemGo != null)
{
var t = itemGo; // 使用临时变量避免内存泄漏
Destroy(t); // 安全销毁对象
itemGo = null; // 清空引用,目标对象逻辑上实现立即销毁,避免影响正常功能需求
}
3、其他方式
(1)指定Destory的销毁时间,减少延迟
通过将延迟时间设置为非常小的值(如 0
或接近 0
的值)来模拟“立即销毁”的效果。
Destroy(gameObject, 0); // 尽量减少延迟,接近立即销毁
仍然需要等待当前帧结束, 对于需要立即销毁对象的情况下,并不起作用;
(2)先禁用对象,再延迟销毁;
将游戏对象的激活状态设置为false。
当游戏对象处于非激活状态时,它不会参与游戏的更新循环(如Update方法不会被调用);
也不会在场景中渲染,相当于从游戏运行逻辑和显示中暂时移除。
如果不需要完全销毁对象,而是希望快速使其失效,可以通过禁用对象或其组件来实现。
gameObject.SetActive(false); // 禁用整个对象(立即生效)
GetComponent<Renderer>().enabled = false;// 或者禁用特定组件(立即生效)
Destory(gameObject);//销毁对象(延迟销毁)
立即生效,性能开销小。但对象仍然存在于内存中,只是不可见或不可交互。
仍然需要等待当前帧结束,对于需要立即销毁对象的情况下,并不起作用;
(3)使用对象池,回收对象,实现立即“销毁”
显著提高性能,减少垃圾回收压力。但需要额外实现对象池逻辑。
本质是将对象放回池中,而不是真正销毁。
(4)强制手动卸载、释放资源(仅限对资源操作)
如果确实需要在运行时立即释放某些资源(如纹理、音频剪辑等),可以尝试手动卸载资源。
Resources.UnloadAsset(texture); // 卸载纹理资源
Resources.UnloadUnusedAssets(); // 卸载未使用的资源
AssetBundle assetBundle;
assetBundle.Unload(true); // 卸载指定的AssetBundle及其包含的资源。
这仅适用于资源管理场景,不能用于销毁游戏对象。