Best options to move away from the resources folder?

So I have a lot of game assets in my resources folder. I didn’t learn until late last year that I’m really only supposed to use that for prototyping. So now I need to start moving assets out of there and call them in through other means.

I’m wondering what options I have for this. I only really know of two. I suspect there are other options I had not considered.

I’ll mention that I was using the resources folder just as a means to call/load/instantiate various assets through scripts. Anything I ever want to spawn in my scene, well, I need to be able to reference it through a script, now don’t I?

The first method, the one taught in most tutorials I come across, is to have a public/serialized variable within a script, and within the editor I just drop a reference to the asset inside the inspector. Then when my script needs to load an asset into the scene it just calls the one stored.
This is useful for anything that only ever needs one kind of thing. Like if an enemy shoots fireballs, and they only ever shoot one kind of fireball, this is the method of choice.
But it begins to be an issue when I have several things that need to be called, or when these things change. I don’t need most of these loaded into memory to be called at any moment, and there really isn’t any way to “unload” anything that I don’t need, nor only load an asset “if” it is needed. Anything that is referenced within an asset in my scene will be loaded when the scene loads; anything that asset references will be loaded, and so on recursively.
This is really only a good method for things that either A) could be needed at any point within a scene, or B) could be needed at any point within the entire game.

The second method is to use the Addressable Asset System. This is new to me, and I’m still reading about how to use it. I don’t fully understand its advantages and disadvantages, when it should and should not be used, etc. It mostly looks like a good replacement for using the resources folder, but I can easily suppose that there are circumstances where I might want to try something else.
(If anyone has any thoughts on the best ways to use this, I welcome them.)

What other methods are there that I can use to call an asset via script?

I’ve been using Addressables for a bit over a year now, having switched over from using Resources, like yourself. I’ve found it’s been very straightforward and easy to use, and I think it should be fine for your use case. There is, however, one initial drawback to using Addressables: It’s asynchronous. This will almost certainly require you to rework some of your code if you’re replacing Resources.Load calls.

Specifically, when you load an asset via Addressables, it doesn’t immediately return the loaded object. Instead, you pass in a callback which will be run when the resource is loaded .The reason for this that Addressables is intended to aso be able to fetch things from remote sources, so it doesn’t want to lock the game up when loading an object. In practice, when loading local assets, the callback is executed almost instantly, but because there is no synchronous option for loading via Addressables, you’ll have to rework you code to be async friendly.

When I switched over, it took me a couple of days to reengineer my code to work with Addressables, since I had some methods where I would load a thing, do something to the thing, then continue doing other things. Converting synchronous code to run asynchronously, with callbacks, can be challenging some times, depending on the complexity of your code.

Anyway, that’s all to say, I don’t regret switching over to Addressables, even if it caused me a bit of work at the time.

2 Likes

So I guess it would be loading fast enough that I don’t have to wait a frame or two before the thing shows up, but if I need to tweak some variables I just can’t do that in the very same update. Right?

I think that only really impacts my projectiles; I have to initialize unique data for them each time they spawn. Most everything else can wait a tick or two, or can be easily moved to being someone’s public variable. Now that I know how to make sub-classes to have collapsible items within the inspector , I’m not as concerned about having huge lists of variables showing up the inspector.

I suppose I could combine tactics to avoid the asynchronous loading problem, too. I could have variables in my class to directly reference the needed assets, and then use Addressables to load those assets into those variables at a convenient time, (when I know that a given asset will be needed but doesn’t need to appear just yet.)

For projectiles, which I interpret to be things you’re spawning frequently and rapidly (like bullets), the common approach for that is to preload and pool/reuse those object, rather than instantiate them at runtime. Using an object pool is faster (fewer hiccups when loading), and is more memory efficient, as you’re not constantly instantiating and destroying objects.

To your first question, I’m not sure whether the callback can actually occur on the same frame that you instantiate. It’s possible it will do that, but it’s also possible it has to wait at least one from. I’m not actually sure.

1 Like

I just tested out whether the callback occurs on the same frame or a subsequent frame. When instantiating a relatively simple object, the callback was always called on the same frame as the call to Addressables.LoadAssetAsync. I don’t know if this will always be the case for local content. And although it might all happen in the same frame, you’ll still need to structure your code as though there could be a gap between the request and the actual instantiation.

Well, no one is firing off a chaingun in my game; just some fireballs and boomerangs and stuff. It’s not that rapid and frequent. And as I’ve been going through everything in my resources folder, I think most of my classes still have variables to hold the prefabs for them. Probably since I needed to directly access a script in the prefab to initialize them, it was easier to have it already as a variable. I just had the prefabs sitting in my resources folder for some reason.

Fair enough. Definitely no need to overcomplicate things if it doesn’t offer some advantages.

I think the main reason people use Addressables to instantiate things is to avoid having unnecessary things loaded into memory. I suspect by now that you’ve learned that anything in the Resources folder (along with any dependencies) is effectively in memory 100% of the time while your game is running. That means that if just one level in your game has a has a weird fireball you instantiate just in that level, and the fireball prefab is in Resources, that fireball prefab will be in memory in every level of your game. That’s why Resources isn’t recommended for objects, unless those objects are generally found throughout your game.

Using objects to hold references to prefabs, as it sounds like you’re doing, has the same risk potential. Worst-case would be some object that has references to every possible object, and clones them as necessary, since having that object in your scene would force all of the referenced prefabs into memory as well. This might not be a big deal in your game, but in some game, the prefabs can be expensive, with large textures and other memory demands. So, just be careful with that approach.

Addressables is nice because it’s easy to release the resources of an object after it’s no longer needed.

That said, I still keep some objects in Resources, objects which are effective present in every scene. These include UI prefabs (like my Pause screen dialog prefab), and certain effects that are used in every level (Like the ragdoll I display when the player dies.) So, I think some use of Resources is fine. Honestly, I think certain games would exclusively use Resources, if they don’t overdo it. Really depends on how “expensive” your prefabs are.

Perhaps I should make a new thread for it, but I’m getting a bit confused about how to use addressables.

One thing I really don’t follow is how to properly catch when an asset is loaded. How do I know when a thing has finished loading, and I am free to start calling functions dependent on it?

For example I have multiple player characters and I want to make them addressable (since only one is in a scene at a time, so there’s no need to load all of them all the time). They spawn at the very start of the level, but I need to set up various things like changing their health, and a number of other objects in my scene need to access the player’s position or other attributes.
If the player is loaded asynchronously, I need to know when the player is loaded so I can trigger all these other scripts to run.

It’s pretty simple. You don’t “catch” when the asset is loaded. Instead, you pass in a function that will be called when the object finishes loading. It would look something like this:

public void LoadAsset(string address, Action<GameObject> callback = null)
{
    Action<AsyncOperationHandle<GameObject>> addressableLoaded = (asyncOperation) =>
    {
        callback?.Invoke(asyncOperation.Result);
    };
    Addressables.LoadAssetAsync<GameObject>(address).Completed += addressableLoaded;
}

So, as soon as LoadAssetAsync has finished loading the GameObject, the callback will be executed, which provides you with the object that was loaded. You can then do whatever you need to do to the object after it’s been loaded.

So, you’d presumable put the calls to all the other scripts into the callback, which only executes once the object is loaded.

For example, I do something like this in my code to instantiate a robot enemy:

            // Platform has reached the bottom. Spawn the robot. Enter deactivated mode.
            GameDirector.Instance.LoadAsset(roboticSoldierAddress, (soldierGO) =>
            {
                var roboticSoldierController = soldierGO.GetComponent<RoboticSoldierControllerBase>();
                roboticSoldierController.IsBomber = isBomber;

                roboticSoldierController.RoboticSoldierSpawnState = RoboticSoldierSpawnState.Inactive;
                roboticSoldierController.SetIdleState(RoboticSoldierIdleState.Spawn);

                callback?.Invoke(roboticSoldierController);
            });

Okay, there are a number of things here I’ve never seen in C# code before, and I don’t know what they are nor what they mean.

Action<>
=>
A semicolon after a close curly bracket
Callback?.
And in your second example when you call the LoadAsset function above you have curly brackets and code within the arguments you pass the the function.

Basically, that’s the syntax for Lambda expression (Lambda expressions - Lambda expressions and anonymous functions - C# reference | Microsoft Learn)

It looks weird at first, but the basic idea is it’s like you’re passing around a function that you don’t actually declare as a function. Often this is convenient for passing to “callbacks”, since it’s a one-off thing, and not a function that will be used by other code.

A “callback” is typically just some code that you pass into a function which that function will call once it’s done doing it’s normal work.

"Action"s allow you to create inline functions which you can pass around or call repeatedly. Here’s a simple, if contrived, example:

// Set up the action
Action<GameObject> deactivateObject = (go) => { go.SetActive(false); };

// Run the action on a list of GameObjects.
foreach (var gameObject in SomeListOfGameObjects)
{
    deactivateObject(gameObject);
}

“Action” means that you’re basically creating an inline function that take a GameObject as its parameter.The “go”, the “=>”, and the curly braces are just the syntax for setting up the function. This action I have here is essentially identical to a plain old void function:

private void DeactivateObject(GameObject go)
{
    go.SetActive(false);
}

The reason you might want to use an Action instead if because if this is the only code that calls “deactivateObject”, it’s often better to simplify the class as a whole and not create separate functions in the class for things that are only relevant to one function.

Also, if a method accepts an “Action” as a parameter, you can create that action inline instead of creating it. So, assume you have a function like this:

public void LoadObject(string objectID, Action<GameObject> callback)
{
    GameObject loadedObject = null;
    // Do some stuff

    // Then...
    callback?.Invoke(loadedObject);
}

You could call that in two identical ways:

LoadObject("ABC", deactivateObject);
// Or...
LoadObject("ABC", (go) => { go.SetActive(false); });

In the first call, we’re passing in the Action we created earlier. In the second call, we’re passing in an inline function which we’re defining right there.

Above, where I defined an action of type " Action<AsyncOperationHandle>", that means we’re setting up an inline function whose argument is of type “AsyncOperationHandle”. the <> syntax is what’s known as “generics” in C#, in case you haven’t seen that yet. It’s a way of creating methods that work on arbitrary arguments.

Anyway, I’d recommend looking into Actions, as they’re super convenient in C#. It simplifies a lot of code. “Func” is similar to Action, except that it returns a value instead of acting as a void, but otherwise they basically follow the same pattern.

I’ve been reading up on delegates. This is new stuff for me and it is hard for me to get it without some solid examples, but I guess I need to actually make some code before I will really get it.

So in the example you used, is “go” an arbitrary name you chose to represent the game object (eg __G__ame __O__bject) or is it an actual command? That is, would this be valid:

// Set up the action
Action<GameObject> deactivateObject = (rubyTuesday) => { rubyTuesday.SetActive(false); };
// Run the action on a list of GameObjects.
foreach (var gameObject in SomeListOfGameObjects)
{
    deactivateObject(gameObject);
}

…or is one of those “go” telling the code to “go” somewhere?
I assume that’s just an otherwise arbitrary name but it’s worth it to make sure.

Yup, “go” was arbitrary, so your “rubyTuesday” example is perfectly valid.

This Action approach is similar to delegates. I think in certain cases, it’s better to use delegates for high-performance purposes, but normally I don’t think it really makes a lot of difference.

Well when I was reading about “action” I found out that “action” and “func” ARE delegates, so I had to go back and read about delegates.

The more I understand about this though, the more annoyed I am getting at these tutorials. Seriously the hardest part about learning code is finding someone who actually explains things in a decent manner. I recall it took me ages to understand class inheritance just because of all the bad and ineffective examples everyone was using. I feel like I’m going through that all over again.

All of these examples I am finding are just people making complicated routes to execute a function you could have just called, instead of showing how this is actually useful, like what you are showing with how I can send the function new arbitrary code. I’ve found one video lesson where a a guy actually demonstrates how silly this is to use a delegate to call an existing function, (could have saved me so much headache if these other examples I’ve been staring at bothered to mention this; I thought I was losing my mind because I couldn’t “get it,”) but he follows it up with an example to teach that you can use callbacks, and that’s what’s special about them. But even then he doesn’t do much to demonstrate what I can do with callbacks.

But I guess I’m getting closer to understanding this.

Yeah. Average tutorials tend to give clear examples, but they don’t explain why you’d want to use that approach. Good tutorials show how something works, but also show you why that’s a good thing, or they show useful things you’d actually want to do.

Anyway, this has indeed gotten a bit derailed by now. :slight_smile: Using Addressables requires a very rudimentary understanding of Actions, so you don’t need to make a deep dive on this topic now unless you find it interesting.

Am I supposed to add another “using” line to my header for this to work? I can’t seem to find this mentioned in the documentation but I’m getting errors for “Action” and “Addressables” not being found.

You’ll need using System; for Action to work. For Addressables, you’ll need to have installed the Addressables package first, then you can project use using UnityEngine.AddressableAssets;

In Visual Studio, hovering over the error should show you the Quick Help option to automatically include the correct using statements:

Apparently I needed to add four lines:
using System;
using Random = UnityEngine.Random;
using UnityEngine.AddressableAssets;
using UnityEngine.ResourceManagement.AsyncOperations;

Okay, so I’m trying to follow the example you posted for the “load asset” code, but either I’m not actually loading it to the correct spot, or I’m triggering the callback wrong and trying to execute code before it finished loading.

I’m testing this with creating my map avatars on my map screen, since they have fewer things that need to run once they are loaded.
The way I figured I should set this up was to have a function that gets called when the asset loads, rather than execute custom code (because really all the code I need to run after loading will be the same, but I could be loading a different prefab.) So I was thinking I would create a GameObject variable to load the prefab into, then my function would instantiate the object from that variable. player1Avatar = (MapAvatar)Instantiate(CurrentAvatarPrefab).GetComponent<MapAvatar>();

But I don’t really see where the asset is going once it is loaded. I thought maybe that’s what the “soldierGO” was in your example, so I tried this: LoadAsset("Avatar-Mage", (CurrentAvatarPrefab) => { Debug.Log("Load complete"); CreateCurrentMapAvatar(); }); but when the instantiate command was called within CreateCurrentMapAvatar I got a null reference error.

I next tried CurrentAvatarPrefab = LoadAsset("Avatar-Mage", (CurrentAvatarPrefab) => { Debug.Log("Load complete"); CreateCurrentMapAvatar(); }); to make sure the loaded asset was going into that variable, but then I get a compile error because LoadAsset returns void, not a gameObject. I suppose I could modify the LoadAsset function to return the loaded asset, but I don’t know how to get it to do that. Looking at the LoadAsset example code you provided, honestly I barely even see how the asset gets loaded to begin with, much less obtain a reference to it.

Or maybe I’m doing something else wrong here; too much of this is still new to me.

So the basic flow of loading things via addressables is fairly simple. Load, then Instantiate:

  • First use Addressables.LoadAssetAsync() to “load” the asset into memory. This is very similar to Resources.Load. It doesn’t put any objects into your scene. It basically just loads the definition and resources associated with that object.
  • Once the asset has been “loaded”, you will be given a prefab GameObject which you can then call Instantiate() on in order to create an instance of the object.

This is pretty simple, but the tricky part is making sure that you’re waiting until LoadAssetAsync finishes before you try to call Instantiate. I’ll try to show how I’d load and instantiate your asset:

public void LoadAvatarMage()
{
    // This is the callback that will be executed when the Avatar Mage is fully loaded.
    Action<AsyncOperationHandle<GameObject>> addressableLoaded = (asyncOperation) =>
    {
        // The Addressables system has finished "loading" the object. Now we can Instantiate is.
        var avatarMageInstance = Instantiate(asyncOperation.Result);

        Debug.Log("We finished instantiating the new Avatar Mage");

        // Now that we have the instance, we can get one of its components:
        var mapAvatarComponent = avatarMageInstance.GetComponent<MapAvatar>();

        // Do whatever else you want now that you have the object.
    };
    Addressables.LoadAssetAsync<GameObject>("Avatar-Mage").Completed += addressableLoaded;
}

If you got a null reference exception, I would assume that’s because you called Addressables.LoadAssetAsync, but didn’t wait for the object to be loaded.

Okay, I have something that works now!
I’ve got a lot of changes to make around my code to get it to work properly again, but now that I have the loading code working I can figure out how to make the changes I need.

Thank you very much for your help this week!