Programmers, please learn how to make stew or any slow-cooking food. You can work while making it!

An article to journal my learning process on yet another bleeding edge Unity tech : Addressable Asset System.

Unity Addressable Asset System | Package Manager UI website
Once an asset is marked "addressable", the addressable asset can be called from anywhere. Whether that addressable…docs.unity3d.com

This article is similar to “[Unity ECS] All of the Unity’s ECS + Job system gotchas (so far)”. It will not talk about how to use, but talk about various obstacles in using it. The version is at 0.3.5 at the time of typing.

[Unity ECS] All of the Unity’s ECS + Job system gotchas (so far)
These are only problems I came across personally and not all of the details of ECS noted for self referencing. I will…gametorrahod.com

“Addressables are not available in edit mode”

This caught me off-guard. That means you cannot use your string address in edit mode, and in effect you cannot unit test anything involving the Addressables system.

This includes loading via string key by the static method Addressables.LoadAsset<T> , and by AssetReference ‘s instance method .LoadAsset<T>

I have made an extension library to deal with this. Look at the alternative path that is available in edit mode. Luckily for AssetReference , we got the “cheating” path .editorAsset . For the static one I just use editor-only resource loading function. This is so that my unit test code works the same way.

Assigning .editorAsset will assign the GUID field automatically

In AssetReference there are 2 fields : the GUID and the editorAsset .

If you assign .editorAsset the GUID will be automatically figured out. So you could write an editor extension that mass-assign .editorAsset you want to keep the GUID for load in the real game.

However if you use the constructor new AssetReference(guid) the editorAsset field will not be automatically set. You should just new AssetReference() then assign the editorAsset you load from API like AssetDatabase.LoadAssetAtPath<T>

How to release correctly??

If you are getting :

ResourceManager.Release() - unable to find location info for asset ...

Let’s try to learn what is the correct way to release what you have loaded.

assetReference.ReleaseAsset(object)

I thought this is very unintuitive API design, if I load with the asset reference then the release API should have 0 argument right? But in reality the code is just :

…so let’s look at the static version. It is pointless to call this instance API!

EDIT : From v0.4.0 onwards this is now parameterless.

Addressables.ReleaseAsset(object)

Every time you load, it will be “recorded”. The recording algorithm is to pair the loaded object to its location. If already exist, add +1 to the count. The key is your loaded object. That means your object will be GetHashCode for the key.

Then let’s look at the source of that warning, the release method.

Here, you see your object will be used as a dictionary key. You see the check, I have searched and found that the count of this dict is purely for this warning and early return to trigger.

If it manages to pass that return , it will be released by means depending on the provider of that resource, which is flexible and depends on your “location” key.

From this observation we can simulate some situations :

  • If you load from Addressables 3 times from the same address, would it make any different if you use only the first instance to release 3 times or use each instance 1 time? Ultimately it depends on your loaded object’s GetHashCode , since that is what dictionary use. If the hash code ended up the same then you can release 3 times with 1 instance and it will decrease the count by -3, each count calling ResourceManager.ReleaseResource
  • What about its dependency? The count for loading goes up for only the object returned, so you cannot release its dependencies manually. Imagine if you loaded a game object via Addressables, if you attempt to call release on its related assets the warning would triggered right away since the dictionary only contains the hash from your loaded game object. You will have to call release exactly on the returned loaded object and hope that it understands to release all of its dependencies.
  • If you get the resource via editorAsset do not release it, because the dictionary count was not increased when you get it. You will get a warning.

What’s in the “location”?

When you use the Addressables system with a simple string you have defined in your catalog file, it is actually treated as an object . So your string is not really the IResourceLocation

The string key will have to consult the “locator”. Probably some locator recognize the object and turn it into IResourceLocation

That data contains much more, including all dependencies.

This together with your input object will be used for releasing by a way that the provider dictates. There is a recursive function to release all of its dependency.

What to worry next is, will the location includes all of the dependencies correctly? Currently I don’t know but I hope it is doing a good work.

What is a ProviderId ?

Inside IResourceLocation is a provider ID (string). This location will be CanProvide against all providers you have until it found a suitable ones.

You see that it is a string comparison. What does a Provider ID looks like?

It is a cached FullName of the type! For example, like “UnityEngine.ResourceManagement.JsonAssetProvider”

For a location, it says what provider it wants by storing this string in it.

Do not touch Addressables system in [RuntimeInitializationOnLoad]

From version 0.4.0 onwards, this is in the Addressables source code :

What if in your own [RuntimeIntializationOnLoadMethod] which happens to run before this one, you…

Run Addressables.Initialization()

The method’s comment says “It is safe to call this method mutliple times as it will only initialize once.” But it is not safe to call it before the “official” initialization.

The reason for that, you see there is a line above Initialize() that your own Initialize() will miss. It cause your string that point to your json config file to not resolve to the true address, finallly causing UnityWebRequest that is getting a json config file to run forever.

Wait on Addressables.InitializationOperation

It would be fine to wait on this anywhere else but your own [RuntimeIntializationOnLoadMethod] , because inside this property contains Initialize() anyways. The wait will never be finished because of the aforementioned unending UnityWebRequest

So how can I load something on start regardless of script entry point/scene ?

Maybe you can use the “Initialization Objects” instead? Basically you can do something on Addressables initialization based on a config file you can design by yourself.

[Unity Addressables] Understanding “Initialization Objects”
New in 0.4.0, “Initialization Objects” of Addressables is not that you can get an object at start up, it is not that…gametorrahod.com

Addressables loads in editor but not in a real device??

The difference is that in editor it is using fast mode, and in a real device it is using packed mode. If you change editor to packed mode too then you might see the same problem.

Now onto how to solve the “packed mode” problems.

Loading a GameObject

If we have MyGo addressed, on it is a script SomeScript , an image of red square and an animation clip.

To correctly add this game object along with all related contents, just the game object is enough. The red square and the clip do not have to follow, this will happen automatically if you drag your game object to any AssetReference exposed field slot. Only the game object is addressed.

This is the correct way of loading that game object :

  • The 2nd way used Instantiate too but mistakenly use SomeScript in the generic. The only valid generic is GameObject , I don’t know why Unity team made it a generic. In fast and virtual mode it works, tricking you into thinking that it is the correct way. In packed mode you get :
Unable to load asset to instantiate from location Assets/SelfContainedGO/MyGo.prefab
  • The 3rd way used LoadAsset , again in fast and virtual mode it works. In packed mode you get :
ArgumentException: The Object you want to instantiate is null.

Loading a GameObject with materials

In packed mode you might find your material attached to your GO turns to pink.

But this is the case that in the device (packed mode) it works just fine but pink only in editor’s packed mode.

Because I am on Android build, the packed material’s shader was converted to Android-compatible (OpenGLES), it is now not render properly in the macOS editor (Metal?) anymore. If this is the case then this “packed mode in editor” button is not perfect… at least for just materials.

Understanding how the key is transformed into resource

“How to load by ___ instead of a string key ?” might be your question.

Resource Locator locates a location with a key

You see that LoadAsset has an another overload that takes IResourceLocation . We will need to make this location without a key.

Key will be transformed in to a location by the “locator”. Addressables.ResourceLocator , this static property will probably contains some :

  • ResourceLocationMap , responsible for string/Hash128 to location.
  • AssetReferenceLocator , responsible for AssetReference to location, which just grab its Hash128 of GUID and throw that to the remaining ResourceLocationMap to figure out the rest.

By default the LoadAsset(key) overload will search through ALL locators first then the first one that can Locate the key, will have that chance to provide location.

Look at this debug. I currently have 2 ResourceLocationMap and 1 AssetReferenceLocator in my ResourceLocator . The 2nd RLM is probably for other Addressables system files. The 1st one is the main locator made from things we have in the Addressables panel, look at the yellow box. This entry turns into 2 entries in the locator, one which use a string and one which use a Hash128.

You can now visualize that when you use a string key it will go to the 1st dictionary entry in that yellow box. AND you can also try a75e8... and see that it should ended up in the other one.

By knowing this, you can already use Hash128 as a key Hash128.Parse("a75e8...") to LoadAsset and of course it works! Because the locator has the 2nd dict entry waiting for you. If you are not satisfied with just key or hash, you can then write a new locator.

PS. Apparently LoadAsset has a special use, if you “load the location” LoadAsset<IResourceLocation>(key) it will not give you the resource just yet but only the location! You can then.. use the location overload of LoadAsset to get the real thing. (When will this be useful? Pre-locating?)

What

What’s the { } in the path ?

You are able to use { } syntax in your addressable group’s build and load path but what is that?

Turns out anything inside { } is a fully reflection-powered static getter! You could define some static variables and use it in your build this way.

The [ ] square brakets is for the profile entries variable you have on your settings file.

AssetBundleProvider vs BundledAssetProvider

Remember that in your resource locator returns a location, and on that location you can see which provider is appropriate?

Most of things in your Addressables panel will be assigned BundledAssetProvider in its location. There is a dependency in this location though, it is the real ___.bundle file where your file actually lives with AssetBundleProvider assigned. Now it make sense? You want to load a bundled asset, which in turn need to consult an another location which wants to load the actual, whole bundle.

You can always check the real identity of its logic. BundledAssetProvider just wait for its dependency AssetBundleProvider , hope that the result is an AssetBundle , then issue LoadAssetAsync on it.

Meanwhile, the AssetBundleProvider has a useful switch whether to use the good old AssetBundle.LoadFromFileAsync (which it will be from StreamingAssets ) or create a web request. This is at the core of the “smart” of Addressables system.

CachedProvider

There is a thing called CachedProvider too. How does it works?

Looking to the built-in packed mode script we can see that it tries to “wrap” the BundledAssetProvider . We could guess all bundled asset will be cached.

If you have read the initialization object article, you know that this code is an attempt to create a serializable initialization object so that at runtime a class instance can appear out of nowhere by reflection.

[Unity Addressables] Understanding “Initialization Objects”
New in 0.4.0, “Initialization Objects” of Addressables is not that you can get an object at start up, it is not that…gametorrahod.com

The corresponding Initialize code looks like this

Finally, the Provide of this cached provider

I don’t fully understand this yet, but it should be the core of “reference counting” mechanism where you load the same addressables multiple time but it only takes time for the 1st time, from that point just increase the count. Releasing goes in backwards, only when the count reaches 0 it is actually released.

Building player content puts out UNITY_EDITOR temporarily

If you build your bundles manually and found errors from your game, it might be because those are related to UNITY_EDITOR . Therefore this is a good way to check for non-editor compile error without building the game!