itch.io is community of indie game creators and players

Devlogs

Level Loading with Unity Addressables

Skeeter's Grid
A downloadable game for Windows

I knew from the get-go that I wanted Skeeter’s Grid to be contained within a single scene. Why? I wanted zero-transitions between levels; ad-hoc level replays; shared resources between levels; other reasons.

There are a few options here. (Additive Scenes, etc), but in the end, I selected Addressables to manage loading and async of unloading of puzzles.

Earlier prototypes of the game bundled all puzzles into the main scene, but this became quite bloated, and build times began to take too long. On top of this, the game was becoming somewhat resource heavy, which is a bad sign in a simple puzzle game. An alternate approach here was to instantiate the puzzles as prefabs, which would have made sense as I had already packaged the puzzles into prefabs. But then I found that creating hard references, even if not loaded into the game, bound up some resources and kept the game from becoming the lightweight dynamo I wanted it to become. I mean, ideally Skeeter’s Grid would play on Switch or phones, so I needed it to be resource frugal.

Enter Addressables.

Any prefab marked as an addressable gets a string-based reference. Internally, Unity has a database linking the address to the prefab, so instead of having large lists or arrays of prefabs in your game, you have lists (or arrays) of addresses.

Any script employing Addresssables needs to include the proper libraries:

using UnityEngine.AddressableAssets;
using UnityEngine.ResourceManagement.AsyncOperations;

Then, when instantiating your gameObject from a prefab, you should use a Coroutine:

    IEnumerator LoadObjectFromAddress(AssetReference loadRef)
    {
        var levelRef = loadRef;
        nextLevelHandle = levelRef.LoadAssetAsync<GameObject>();
        yield return nextLevelHandle;
        if (nextLevelHandle.Status == AsyncOperationStatus.Succeeded)
        {
            GameObject nextLevelPrefab = (GameObject) nextLevelHandle.Result;
            if (nextLevelPrefab != null)
            {
                Vector3 nextLevelPosition = Vector3.zero;
                nextLevelObject = Instantiate(nextLevelPrefab, nextLevelPosition, Quaternion.identity);
                nextLevelAsset = loadRef;

This certainly isn’t as easy as Instantiating your object and calling it a day. Instead you are:

  1. Loading the prefab from a reference
  2. Waiting until it is loaded
  3. Instantiating

Missing here is the step to remove any previously loaded addressable. You need to manage these resources, or you could end up with memory leaks or errors.

Destroy(currentLevelObject);
currentLevelAsset.ReleaseAsset();

So in my case, I have globals for currentLevelAsset and nextLevelAsset, and I pass the reference between the two based on where I am in the level loading process, but this is because I only ever need to manage two levels at a time (the current level and the next level). More complex setups will need more complex scaffolding.

Lastly, it’s worth noting that loading from an address is not instantaneous. Some work is required to make sure that a level is loaded well before the player traipses into that space. In my case, my first level is not an addressable and is added to the scene itself. I also prevent the elevator in Skeeter’s Grid from moving to the next level until it is confirmed loaded.

One thing is certain though–it’s more work, but I can’t imagine not using Addressables as a foundational tech going forward. I just wish I’d started out with them in Skeeter’s Grid rather than having to retrofit the game later.

Download Skeeter's Grid
Leave a comment