Hi everyone:
I was always wondering about how to unload Asset bundles properly. The key is how to keep an track of all the references in the scene, and how to de-reference them properly.
Before I share my solution, I wanted to give some test results:
AssetBundle bundle = AssetBundle.LoadFromFile("Assets/SomeAssetBundle.unity3d");
for (int i = 0; i < 10000; i++)
{
// could be other type of assets
Sprite sp = bundle.LoadAsset<Sprite>("a.png");
Debug.Log(sp.GetInstanceID());
}
Loading from assetbundle wich the same asset path will always return the same instance, unless you unload the assetbundle and reload.
Why did I do that test? Because I’ve been confused for a long time: which should I keep a reference on, assets? or asset bundles?. I mean, if I keep a track of loaded assets, instead of asset bundles, the next time when I try to load an asset, I could just return the loaded asset. So every time when an asset load request is on, we could just load the asset from assetbundle, and store the asset, and unload the assetbundle right away.
But that is not the correct answer that I’m looking for. As I tested out, load and unload asset bundles could be expensive. So asset bundles are the one that should be referenced.
To reference an asset bundle is easy, but how to de-reference them so that we know when to unload them?(when reference count <= 0)
Here is the code that I just wrote, and it works fine for me. Though I haven’t put the async-load methods in it.
public static T GetOrCreateComponent<T>(this GameObject go) where T : UnityEngine.Component
{
T com = go.GetComponent<T>();
if (com == null)
{
com = go.AddComponent<T>();
}
return com;
}
public static void BindAsset(this GameObject go, Object asset)
{
go.GetOrCreateComponent<AssetRecorder>().BindAsset(asset);
}
///////////////////////////////////////////////////////////
// All type of assets
public static void LoadImage(this UnityEngine.UI.Image img, string assetPath)
{
// Unload first
int oldInstanceID = -1;
if (img.sprite != null) oldInstanceID = img.sprite.GetInstanceID();
img.sprite = AssetLoader.LoadAsset<Sprite>(assetPath);
if (oldInstanceID >= 0 && img.GetInstanceID() != oldInstanceID)
{
AssetLoader.RemoveRef(oldInstanceID);
}
img.gameObject.BindAsset(img.sprite);
}
So the idea is to use a Mono script that records all the assets the gameObject has “asked” from an asset bundle. By doing that, we now can do the dereference in the OnDestroy callback:
protected void OnDestroy()
{
for (int i = 0; i < assetBundleRefs.Count; i++)
{
// do the unbind operation
AssetLoader.RemoveRef(assetBundleRefs[i]);
}
}
That’s the key idea. But there is one problem though: you must destroy the gameObject, instead of components.
So this assetbundle framework is not completely finished. I’d like to share when it’s done. And I really appreciate if anyone would share their thoughts on ab management, cause I struggled a lot all along the way.