4
\$\begingroup\$

I have been advised to move away from the Resources system to the new Addressables system, which seems to have many advantages.

I need to convert the following code to use the Addressables:

public static void LoadDefs()
{
    foreach (var baseTileType in Resources.LoadAll(Settings.BASE_TILES_PATH, typeof(TextAsset)))
    {
        var baseTile = Deserializer.Deserialize<BaseTile.Initializer>(((TextAsset) baseTileType).text);
        BaseTileInstances.Add(baseTile.Name, baseTile.CreateInstance());
    }

}

It is important for me to detect the type of TextAsset because those are .yaml files that I need to load the information from. Then, derived from information in that YAML file, I load sprites that I need with Resources.Load<Sprite>(path) later, but that should be simple.

The documentation is pretty unclear on how to do this. It mentions labels, but not much else.

How can I achieve a similar result with this new system?

\$\endgroup\$

2 Answers 2

2
\$\begingroup\$

I've managed to come up with a basic solution for the following problem. Here's an example of needing to load and store sprites:

  1. Add your addressables into a group and label them accordingly. In my case, "pictures".

addressables view

  1. Add the code.

    public static readonly Dictionary<string, Sprite> AllSprites = new Dictionary<string, Sprite>();
    
    public static async Task InitSprites(string assetLabel)
    {
        var locations = await Addressables.LoadResourceLocationsAsync(assetLabel, typeof(Sprite)).Task;
        List<Task<Sprite>> tasks = new List<Task<Sprite>>();
    
        foreach (var location in locations)
        {
            tasks.Add(Addressables.LoadAssetAsync<Sprite>(location).Task);
        }
    
        var loadedSprites = await Task.WhenAll(tasks);
    
        foreach (var sprite in loadedSprites)
        {
            AllSprites.Add(sprite.name, sprite);
        }
    }
    

You may call InitSprites from any async method. For example, you can define a public static async void Load() method, which you call from the Start() method in your GameManager singleton.

Then, inside of that, call await InitSprites("pictures"). This will asynchronously load and store your sprites wherever you need (in my case, a dictionary).

  1. Modify

You can get this code to work with any asset type. All you have to do is to replace the typeof() parameter with your desired type. You can even write typeof(object) and then store it into a List<object>. That way, one list can hold ALL of your assets of various types, which you can then use however you like.

\$\endgroup\$
10
  • \$\begingroup\$ Nice answer, thanks for sharing. I can see a case when this would need to be revisited again, you are loading by labels, so my understanding is that it will load any asset with that label even if it's outside target group. Also, if you need different labels inside of your group, you might want to load whole group. If you want to load multiple groups without labels then a Type would be the identifier to define what asset to load or not. But main thing, it's similar to Resources but it's not the same, if you were loading all assets from a group of some Type it would be more similar to it. \$\endgroup\$ Commented Jul 13, 2021 at 9:49
  • \$\begingroup\$ True, this approach is a bit limited by labels. LoadResourcesLocationsAsync() requires some "key" identifier - it seems supplying only the type isn't enough for it to find assets. We would need to identify a whole group and then look for Types in there, but I'm not sure how to do that yet. Will update answer if I find out. \$\endgroup\$
    – caleidon
    Commented Jul 13, 2021 at 10:27
  • \$\begingroup\$ Yep, that is okay if we can access the whole group but Unity seems to think that we don't need that - forum.unity.com/threads/… , unless I am confused with the things in the forum thread. \$\endgroup\$ Commented Jul 13, 2021 at 10:33
  • \$\begingroup\$ I have found a somewhat decent workaround if you are interested. You can manage your labels from code and create a new label for each new group. Then you can assign this label to each addressable asset when it gets moved around. In the end your loading with labels is a valid solution for this when we use this approach to define if asset is in a certain group. Check out my previous comment with a link, there are a lot more details in that thread from Pixel Lifetime about this. \$\endgroup\$ Commented Jul 13, 2021 at 11:04
  • \$\begingroup\$ The way I see it, groups are useless. Whether you define multiple labels in one group, or one label per multiple groups, in the end the result will be the same as you always load by label with this approach. Correct me if I missed something. \$\endgroup\$
    – caleidon
    Commented Jul 13, 2021 at 12:53
1
\$\begingroup\$

No idea if this is more or less efficient than the OPs solution, but here is my take on the same problem. In my case, I have lots of different data types that derive from scriptable objects that I loaded by type into lists at runtime using Resources.LoadAll(). Some have prefabs which means they have dependencies thus making the main data payload quite large (project is WebGL), so moving these out of resources to load from addressables was critical. Like OP, I organize the Addressable assets with labels and groups. Instead of using System.Threads.Task I'm using the familiar Monobehaviour Coroutine, because, well, I'm familiar with it :)

IEnumerator  LoadAssetsAsync<T>(string label, List<T> list, System.Type type)
    {
        yield return null;
        AsyncOperationHandle<IList<IResourceLocation>> handle = Addressables.LoadResourceLocationsAsync(label, type);
        yield return handle;

        for(int i = 0; i < handle.Result.Count; i++)
        {
            StartCoroutine (LoadAssetAsync<T>(handle.Result[i], list));
        }
        Addressables.Release(handle);
    }

    IEnumerator LoadAssetAsync<T>(IResourceLocation location, List<T> list)
    {
        var handle = Addressables.LoadAssetAsync<T>(location);
        yield return handle;
        list.Add(handle.Result);
    }

And it gets called like so:

List<Foo> foos = new List<Foo>(); //Foo can be anything

void OnGameAwake()
    {
        StartCoroutine(LoadAssetsAsync<Foo>("Foo", foos, typeof(Foo)));
    }

Probably just a dumber way of restating the OPs solution, but it works...

\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .