文章目录
前言
这段时间经历离职,十一,入职,搬家,一个月没有鼓捣博客了,现在继续我们的博客之旅。
这篇文章是关于Prefab加载管理器,实现GameObject创建销毁的自动化管理,保证内存的最小化和代码健壮性。
另外,这篇文章依赖于上一篇文章,读者可以先参阅。
Unity资源加载
Unity资源类型,按加载流程顺序,有三种
AssetBundle
资源以压缩包文件存在(Resources目录下资源打成包体后也是以ab格式存在)Asset
资源在内存中的存在格式GameObject
针对Prefab导出的Asset,可实例化
针对AssetBundle
的加载,读者可以参阅AssetBundle同步异步引用计数资源加载管理器,
针对Asset
的加载,读者可以参阅Asset同步异步引用计数资源加载管理器
针对GameObject
的加载,本文会作讲解,并提供整套方案和代码
Prefab实例化
我们都知道Prefab是一种存储格式,加载后是一个asset格式文件,需要实例化后才是一个可用的GameObject。
而如果每次我们都去加载asset,然后实例化,然后代码管理每一个GameObject的内存,这当然不是我们想要的。那我们想要的是什么?
我想要的是——自动化管理
- 提供一个接口,加载到指定GameObject
- 不用关心销毁,内存管理,实例克隆等操作
- 可以实现同步加载,异步加载和手动销毁
Unity自身肯定不包含上述功能,需要我们自己实现框架
自动销毁核心——ObjInfo
我们先不关心管理器内部逻辑,先关心如何实现自动销毁。
我们都知道Awake
和OnDestroy
函数,顾名思义,创建和销毁的时候调用,那么我们很简单地构造一个MonoBehaviour
,实现如下
using UnityEngine;
public class ObjInfo : MonoBehaviour
{
void OnDestroy()
{
//被动销毁,保证引用计数正确
PrefabLoadMgr.I.Destroy(this.gameObject);
}
}
我们可以对每个通过Prefab加载管理器创建的GameObject
添加一个ObjInfo
,这个GameObject
在销毁的时候,会调用ObjInfo
的OnDestroy
方法,会通知管理器,销毁自身,,这样就实现了自动销毁
如果GameObject被克隆了呢,就会造成引用计数出错,所以还要修正引用计数的正确性,如下
public int InstanceId = -1;
public string AssetName = string.Empty;
void Awake()
{
if (string.IsNullOrEmpty(AssetName)) return;
//非空,说明通过克隆实例化,添加引用计数
InstanceId = gameObject.GetInstanceID();
PrefabLoadMgr.I.AddAssetRef(AssetName, this.gameObject);
}
我们很开心地以为可以了,但Unity并没有按我们理想化的方式运行。
实例化——必须的active
Unity提供的Awake
和OnDestroy
函数,必须在GameObject
节点被active的情况下,才会触发运行,也就是说新加载的节点GameObject
, 必须挂在启用的GameObject
节点下。
那这样我们就先将节点创建在一个通用节点_assetParent.transform
下,然后再把它移到目标节点上,我们就得到了如下代码:
private GameObject InstanceAsset(PrefabObject _prefabObj, Transform _parent)
{
GameObject go = GameObject.Instantiate(_prefabObj._asset, _assetParent.transform;) as GameObject;
go.name = go.name.Replace("(Clone)", "");
ObjInfo obgInfo = go.AddComponent<ObjInfo>();
if(!go.activeSelf)
{
//保证GameObject active一次,ObjInfo才能触发Awake,未Awake的脚本不能触发OnDestroy
go.SetActive(true);
go.SetActive(false);
}
if (obgInfo != null)
{
obgInfo.InstanceId = go.GetInstanceID();
obgInfo.AssetName = _prefabObj._assetName;
}
if (_parent != null)
go.transform.SetParent(_parent);
return go;
}
这样终于实现了自动销毁,不过笔者测试发现,go.transform.SetParent
是一个非常耗时的操作,而我们大部分情况下,挂载的父节点都是active的,所以我们优化成可以挂载在目标节点下,直接挂载,不能再挂载在公用节点上,具体代码见下文。
Prefab加载管理器
加载管理器,在前面几篇描述了很多了,关键词是——加载单元,队列,外部接口。这一部分也不例外,只不过简单了很多。