Solutions

Addressables

First, if you use Addressables that reference ScriptableObjects (SO), those SOs need to be Addressables as well. Then, you just need to ensure that everyone is using the same AssetReference and that the Addressable has been loaded before it is used.

There are a few solutions to achieve this:

  1. Expose the AssetReference instead of the SO in your scripts, and then use a static class to load it. This class will also cache the loaded instances in a dictionary.

using UnityEngine;
using UnityEngine.AddressableAssets;
using System.Collections.Generic;
using System.Threading.Tasks;

public static class AddressableSOHelper
{
    private static Dictionary<AssetReference, ScriptableObject> _loadedInstances = new Dictionary<AssetReference, ScriptableObject>();

    public static async Task<T> LoadSOAsync<T>(AssetReference assetReference) where T : ScriptableObject
    {
        if (_loadedInstances.TryGetValue(assetReference, out var loadedInstance))
        {
            return loadedInstance as T;
        }

        var loadOperation = assetReference.LoadAssetAsync<T>();
        T instance = await loadOperation.Task;
        _loadedInstances[assetReference] = instance;
        return instance;
    }

    public static void ReleaseAll()
    {
        foreach (var assetReference in _loadedInstances.Keys)
        {
            assetReference.ReleaseAsset();
        }
        _loadedInstances.Clear();
    }
}

To see how to use this, here’s an example: There is an EnemySpawner that loads the enemy prefab as an Addressable. Additionally, each enemy prefab has a script, Enemy.cs, which exposes a reference to an AssetReference of the SO event. The only difference when using SO events as Addressables is that we need to expose an AssetReference (instead of the SO), make the SO event an Addressable, and ensure we load it (using the static class) before using it.

using Obvious.Soap;
using UnityEngine;
using UnityEngine.AddressableAssets;

public class Enemy : MonoBehaviour
{
    public AssetReference EventSOReference;
    private ScriptableEventNoParam _eventSO;
    
    async void Start()
    {
        _eventSO = await AddressableSOHelper.LoadSOAsync<ScriptableEventNoParam>(EventSOReference);
        _eventSO.OnRaised += Kill;
    }

    private void OnDestroy()
    {
        _eventSO.OnRaised -= Kill;
    }

    private void Kill()
    {
        Destroy(gameObject);
    }
}
using System.Collections;
using Obvious.Soap;
using UnityEngine;
using UnityEngine.AddressableAssets;
using UnityEngine.ResourceManagement.AsyncOperations;
using Random = UnityEngine.Random;

public class EnemySpawner : MonoBehaviour
{
    public AssetReference enemyPrefabReference;
    private AsyncOperationHandle<GameObject> enemyPrefabHandle;
    
    public AssetReference destroyEventReference;
    private ScriptableEventNoParam _eventSO;

    async void Start()
    {
        enemyPrefabHandle = Addressables.LoadAssetAsync<GameObject>(enemyPrefabReference);
        await enemyPrefabHandle.Task;
        _eventSO = await AddressableSOHelper.LoadSOAsync<ScriptableEventNoParam>(destroyEventReference);
        StartCoroutine(SpawnEnemies());
    }

    IEnumerator SpawnEnemies()
    {
        while (true)
        {
            yield return new WaitForSeconds(1f);
            Vector2 randomPosition = Random.insideUnitCircle * 10;
            var pos = new Vector3(randomPosition.x, 0, randomPosition.y);
            Addressables.InstantiateAsync(enemyPrefabReference, pos, Quaternion.identity)
                .WaitForCompletion();
        }
    }

    private void Update()
    {
        if (Input.GetMouseButtonDown(0))
        {
           _eventSO.Raise();
        }
    }

    private void OnDestroy()
    {
        if (enemyPrefabHandle.IsValid())
            Addressables.Release(enemyPrefabHandle);
        AddressableSOHelper.ReleaseAll();
    }
}
  1. Load the AssetReference in one script, then pass it as a dependency to all other scripts that need a reference to it. This breaks the independence of SOs, but it works.

  2. Create a "manager" or an SO database that will load all your AssetReferences. Any class that needs a specific SO will have to interact with this manager to get the loaded Addressable.

  3. Create a loading scene that will load all Addressables, then load other scenes as Addressables. I found this solution here.

Of course, it would be great to be able to use the EventListeners and the Bindings with Addressables, and I am planning to try to support that in the future. In the meantime, load and use the addressables SO by code and do not use the EventListeners or Bindings in Addressable loaded prefabs or scenes.

Last updated