I was trying to integrate Addressables in an existing project and in one project that we build from scratch. This is my feedback for you.
This post took me quite some time to write, so please don’t feel offended or think I’m trying to put your work in a bad spot. I’m providing this feedback to help you to improve Addressables, which helps me with my own work too.
Missing AssetReference / GUID / AssetPath / Address lookup table
I use the AssetReference class to store references to scenes. However, I didn’t find a way to check if the scene from that AssetReference is loaded already.
The AssetReference.RuntimeKey represents the GUID, but I didn’t find a practical way to look-up the assetName or assetPath for that GUID. Why would I want this I hear you ask. Because to check if a scene is loaded (UnityEngine.SceneManager) I need the scene name or path.
A lookup table in Addressables that translates a GUID to its assetPath and vica versa would be tremendously helpful. A GetSceneByGUID method in the UnityEngine.SceneManager API could be useful too.
Missing lookup table workaround
My workaround to the missing lookup table is to implement one myself.
However, this shouldn’t be necessary, because this information must exist in Addressables already. How would you map an AssetReference to the Address otherwise?!
My approach is to have a Component that holds AssetReference’s to all addressable assets in the project. At initialization, I trigger a gazillion Addressables.LoadResourceLocationsAsync
operations and then store the result in my own data-structure that the game then uses to quickly look up meta information of an AssetReference.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AddressableAssets;
public class AssetReferenceLookup : MonoBehaviour
{
[SerializeField] List<AssetReference> m_AssetReferences = default;
public struct Entry
{
public string guid;
public string address;
public bool valid;
}
static List<Entry> s_Lookup = new List<Entry>();
public static bool isReady
{
get;
private set;
}
public static Entry Find(AssetReference assetReference)
{
return Find(assetReference.RuntimeKey as string);
}
public static Entry Find(string key)
{
var index = FindIndex(key);
if (index != -1)
return s_Lookup[index];
Dbg.Error(null, $"Could not find addressable entry for '{key}'.");
var r = new Entry();
r.address = key;
r.guid = key;
return r;
}
static int FindIndex(string key)
{
for (var n = 0; n < s_Lookup.Count; ++n)
{
var e = s_Lookup[n];
if (string.Equals(e.guid, key, System.StringComparison.OrdinalIgnoreCase))
return n;
if (string.Equals(e.address, key, System.StringComparison.OrdinalIgnoreCase))
return n;
}
return -1;
}
IEnumerator Start()
{
isReady = false;
yield return null;
yield return Addressables.InitializeAsync();
var asyncOperations = new Dictionary<AssetReference, UnityEngine.ResourceManagement.AsyncOperations.AsyncOperationHandle<IList<UnityEngine.ResourceManagement.ResourceLocations.IResourceLocation>>>();
foreach(var r in m_AssetReferences)
{
var op = Addressables.LoadResourceLocationsAsync(r);
asyncOperations.Add(r, op);
}
var isDone = false;
while (!isDone)
{
isDone = true;
foreach (var pair in asyncOperations)
{
if (!pair.Value.IsDone)
{
isDone = false;
break;
}
}
yield return null;
}
foreach(var pair in asyncOperations)
{
foreach(var r in pair.Value.Result)
{
var e = new Entry();
e.guid = pair.Key.RuntimeKey as string;
e.address = r.PrimaryKey;
e.valid = true;
s_Lookup.Add(e);
}
}
isReady = true;
//foreach(var e in s_Lookup)
//{
// Dbg.Log(null, $"address: {e.address}, guid: {e.guid}");
//}
}
void OnDestroy()
{
isReady = false;
s_Lookup = new List<Entry>();
}
}
Using the Address as SceneName hack
In order to find out whether a scene is loaded already I need the scene name or path.
But since I don’t seem to be able to lookup the assetName/Path from an AssetReference, I use the Address as name instead.
In the code I then just assume (and hope) that the specified address really is the scene name and use it in SceneManager.GetSceneByName(address).
This is a hack to be honest and can easily fall apart if the Addressables address and scene name do not match. For example, if the scene was renamed and someone forgot to rename its address too.
Why the need for the custom AssetReferenceLookup?
Perhaps you’re still wondering why I store the guid / address mapping in my own data structure and not use Addressables.LoadResourceLocationsAsync where I need it?
Because LoadResourceLocationsAsync doesn’t allow to quickly lookup the Address.
For example the game features an achievement/trophy “Enter secret level something”. The game code checks if the player entered a certain scene like this:
public class AwardEnterScene : Award
{
[SerializeField] AssetReference m_SceneReference;
void OnSceneEntered()
{
var address = AssetReferenceLookup.Find(m_SceneReference).address;
if (SceneManager.GetActiveScene().name == address)
Debug.Log("Trigger award");
}
}
AssetReferenceLookup is the code I implemented to workaround using Addressables.LoadResourceLocationsAsync.
Otherwise I would have to implement async handling in many places throughout the game code, where it actually has no benefit and would just over-complicate things.
Without AssetReferenceLookup I guess the code would look something like this instead:
public class AwardEnterScene : Award
{
[SerializeField] AssetReference m_SceneReference;
void OnSceneEntered()
{
this.StartCoroutine(OnSceneEnteredCoroutine());
}
System.Collections.IEnumerator OnSceneEnteredCoroutine()
{
var op = Addressables.LoadResourceLocationsAsync(m_SceneReference);
yield return op;
if (!op.IsValid())
{
Debug.LogError("error");
yield break;
}
foreach (var r in op.Result)
{
if (SceneManager.GetActiveScene().name == r.PrimaryKey)
{
Debug.Log("Trigger award");
}
}
}
}
While this technically works, there is no benefit of writing that code in an async way. And there are also many occurences where turning something async is just not feasable.
Missing non-async API
Which leads me to missing non-async methods. Using Addressables over-complicated things for me in various cases, because it features async loading only.
Sometimes I want to load non-async on purpose, especially when I know that the content is stored in StreamingAssets.
Not having the ability to do so limits the usefulness of Addressables and causes me to find workarounds which are often more complex than necessary.
For example, I had something like this to lazy load an audio mixer asset:
public static class SoundMixer
{
static AudioMixer s_AudioMixer = null;
public static AudioMixer audioMixer
{
get
{
if (s_AudioMixer != null)
return s_AudioMixer;
s_AudioMixer = Resources.Load<AudioMixer>("AudioMixer");
return s_AudioMixer;
}
}
}
Since Addressables does not allow blocking calls, I would have to rewrite all the code where SoundMixer.mixer is used, to handle its async’ness. This wasn’t feasable!
My workaround was to turn SoundMixer to a Component instead:
public class SoundMixer : MonoBehaviour
{
[SerializeField] AudioMixer m_AudioMixer = null;
public static AudioMixer audioMixer
{
get;
private set;
}
void Awake()
{
audioMixer = m_AudioMixer;
GameObject.DontDestroyOnLoad(gameObject);
}
}
This isn’t even a bad change I would say! Now the “SoundMixer” Component must be available at all times and across scene changes. I achieve this by storing a GameObject with the SoundMixer Component in a globals scene and mark it as DontDestroyOnLoad.
Unsupported GameObject.DontDestroyOnLoad
This led me to learn that Addressables does not seem to support GameObject.DontDestroyOnLoad.
I have a “loader” scene which is the first scene that the engine loads and its only responsibility is to load a so called “globals” scene via Addressables.
The globals scene contains all the objects that must stay alive for the entire application uptime. Those objects are set to DontDestroyOnLoad. However, some of those objects have references to assets, such as the SoundMixer Component shown above, which has a reference to an AudioMixer asset.
When the game now loads a scene, Addressables throws out all the assets that are actually used by those global DontDestroyOnLoad objects.
This is blocking me from further integrating Addressables, I filed a bug-report for this issue:
(Case 1297620) Addressables unloads assets that are still in use
The same problem occurs in the loading screen. The loading screen is an additive scene that’s loaded and all its objects are set to DontDestroyOnLoad. When the loading scene then loads the requested game scene, Addressables throws out the loading screen assets.
Again, I have no idea how I can fix this.
Addressables bundles editor-only assets
One problem with Addressables is that you have to mark assets as addressable. Thus I don’t mark every single asset in question as addressable, but an entire directory instead.
This makes it not only easier to work with, it also allows non-tech people with no Addressables knowledge to add content to the game.
However, Addressables pulls in editor assets when a parent directory is marked as addressable.
For example our levels are made of several scenes. We have one directory per level. We mark the “level directory” as addressable, so we can add further scenes to it without having to mark each individual scene as addressable.
Our level building process also generates editor-only level specific assets and stores them in an “editor” folder under the “level directory”. The reason why those assets are stored in the “level directory” is that these are specific/unique to this level only. If we remove the “level directory” all its related assets are deleted too, so we don’t end up with “dead assets” in the project, which is especially useful when creating/testing a lot of level ideas.
However, Addressables pulls editor assets in too and they end up in a build. That’s not useful for me and I didn’t expect that Addressables would do this.
I submitted the following bug-report for this issue:
(Case 1296505) Addressables pulls in assets from an Editor folder
Unforunatelety, you didn’t acknowledge this as an issue and closed the bug-report with the following comment:
It’s beyond me why you think this isn’t an issue or at least a missing feature.
Give us functionality to exclude assets from getting bundled. If you provide such functionality, I could simply add a rule to exclude all “editor” folders myself and Addressables would immediately become a lot more useful.
By the way, this does not only affect user assets, Unity itself stores editor-only assets in a scene directory: (Case 1296514) Addressables outputs "Type LightingDataAsset is in editor assembly" warning
Anyway, since Addressables does not exclude editor-only assets, I thought I could trick the system and mark all those assets with HideFlags.DontSaveInBuild instead.
However, doing this causes Addressables build process to fail.
(Case 1296496) Using HideFlags.DontSaveInBuild causes Addressables to fail
https://forum.unity.com/threads/case-1296496-using-hideflags-dontsaveinbuild-causes-addressables-to-fail.1014991/
Individual loads at the same time
I just learned Addressables has issues with “individual loads at the same time” where the system is sometimes “messing something up”.
This also sounds like a major limitation of the system. Please see my response to this limitation here:
I think I stop here.
- The unfortunate outcome for me is that Addressables doesn’t seem production ready for me. It seems it’s missing fundamental functionality or perhaps I didn’t find the documentation so I don’t know how to use the system.
- The few Addressables package updates I have done caused tremendous issues. I would welcome it if you improve your tests and QA.
- The documentation is lacking and does not meet “Unity standard” yet.
- The communication with Unity staff in the Addressables sub-forum is non-existent most of the time. Which perhaps wouldn’t even be necessary if the documentation would be better.
Thank you for reading.
PS: In 2018 I was already wrote some Addressables feedback and I guess it’s still spot on: