Object references in static classes

I'm trying to make a tool that lets you set variables for static classes from the inspector, but I'm stuck in figuring out how to do so for things like prefabs and audio clips and such. I've tried storing the instance ID, but for some reason, you can only convert an instance ID to an object in the editor even though instance IDs exist in builds. Is there any way I can get the object from an instance ID in the build?

If not, is there any way that I can hard-code a reference to something like a prefab or an audio clip during the build process? How does the compilation work for a script attached to a GameObject in a scene? If there's a reference to a prefab, I imagine it must be compiled into a reference that can be used at runtime, so is there a way to do that for a static class too?

Sounds like you're trying to serialise static values, which is just a really bad idea. Namely because they can be initialised before Unity is completely initialised and then your program just falls over before it even begins.


I mean you can do this, but it won't help: https://docs.unity3d.com/ScriptReference/Resources.InstanceIDToObject.html

Note the 'instance' part of InstanceID. It's just an incrementing number for instances of objects that are created from loading assets from disk. They are not persistent between editor or runtime sesssions, and can't be used for this.

Basically I don't think there's a reliable way to do this. If you want static values, you can more easily make scriptable object singletons are are initialised when Unity has gotten itself properly out of bed using Preloaded assets: https://docs.unity3d.com/ScriptReference/PlayerSettings.GetPreloadedAssets.html

1 Like


Oh, that's unfortunate. Do you know any better ways to make universally accessible variables that can be edited in the inspector? My initial idea was to just make a static class and use a JSON file to edit the values from the inspector, but I guess it seems like that wouldn't support referencing assets.

I know I could use a scriptable object singleton system, but there is still the risk of creating a second instance which could override the first one. I'm still up for that solution if it's the only way though. I wish there was a way to make a singleton scriptable object where you wouldn't even make an instance, there would just be an inspector on the .cs file that lets you input values lol.

I mean a scriptable object singleton system has been my way of doing it, and it's been quite reliable. It wasn't too difficult to write some editor tools to validate things and ensure there is only ever one scriptable object asset of a given type in my project files. I even got it to work with addressables, too.


I mean you can get the asset GUID at edit time and hold onto that, but we don't have any API with which we can convert that GUID into an actual object reference.

So the reference would have to be done with an asset of some kind (so basically a scriptable object) and then injected into the static field via reflection. But that's kind of just a scriptable object singleton with more steps involved.

Instance IDs are not stable. They change every time you open the project in the editor, and they are different in the build too. You can only use Instance IDs to uniquely identify objects within a given playmode or runtime session.

Whenever I read "static" (which includes singleton) my advice is simply this: find a way to avoid using statics.

The most sobering experience is this:
9687908--1381898--upload_2024-3-8_10-26-48.png
Go to: Project Settings => Editor and tick that Enter Play Mode Options checkbox, leave the others unchecked. Then enter playmode. Do you want to keep it THIS fast? I mean who wouldn't want INSTANTANEOUS enter playmode? ;)

In order to keep working with this, you need to be very careful with using static anything, and ideally just avoid it because the project architecture improves because of this.

Create a singleton GameObject like this:

public abstract class MonoSingleton<T> : MonoBehaviour where T : MonoBehaviour
{
    private static T s_Instance;
    private static Boolean s_Instantiated;

    public static T Singleton
    {
       get
       {
          if (s_Instantiated == false)
          {
             s_Instantiated = true;
             s_Instance = new GameObject($"{typeof(T)}", typeof(T)).GetComponent<T>();
             DontDestroyOnLoad(s_Instance);
          }

          return s_Instance;
       }
    }

    [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]
    private static void OnRuntimeInit()
    {
       s_Instantiated = false;
       s_Instance = null;
    }
}

You need not add this singleton to any scene since it creates itself on first access and then forever lives throughout the lifetime of the session.

But in your case, you want to do so. You put it in the first scene that your game loads. Then you can select it and assign the ScriptableObject references in the Inspector. From then on, you can access those SO via something like:

var playerOuchy = GameData.Singleton.AudioClips["PlayerHurt"];

How you design the AudioClips etc collections is up to you. I used a string indexer only for illustration, of course using strings to index anything is rarely recommendable.

This system guarantees that any asset reference is accessible via singleton and there will not be any duplications. The only static is the singleton, and that's taken care of.

The only thing you need to keep in mind is if you want to be able to enter playmode in various scenes, then ideally you'd place this singleton object into a scene of its own that gets additively loaded every time you enter playmode or the game launches, so that it is always available.

I advice against using a prefab for the singleton because you can have scene-specific overrides in each prefab instance which will cause different behaviour in the game depending on what scene you launch with.

Since I need this myself anytime now, I experimented a little with this approach and made sure this works.

First, create a scene which I named "+PersistentObjects". I always prefix additively loaded scenes with "+" to make it clear they are supposed to be used additively.

Then remove all objects in the scene, create a new game object, and add this script to it:

using UnityEngine;
using UnityEngine.SceneManagement;

public class AutoLoadPersistentObjectsScene : MonoSingleton<AutoLoadPersistentObjectsScene>
{
    [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
    private static void LoadPersistentObjectsScene()
    {
       Debug.Log("OnLoad additive scene load persistent objects");
       SceneManager.LoadScene("+PersistentObjects", LoadSceneMode.Additive);

       // ensure that Singleton object is already instantiated and put into DontDestroyOnLoad
       var dummyRef = Singleton;
    }
}

This assumes you also have the MonoSingleton class (see above) in your project.

You can then either add all the scripts you want to be globally available to the single game object in this scene. Or you create additional MonoSingleton instances and put them in the +PersistentObjects scene. The important bit to realize is that objects in +PersistentObjects only persist if they are flagged with DontDestroyOnLoad().

I have a separate component that simply does this, so I can just drop it onto the game object and make it clear to editors that this is a DDoL object:

    public class DontDestroyOnLoad : MonoBehaviour
    {
        private void Awake()
        {
            if (enabled)
                DontDestroyOnLoad(gameObject);
        }
    }


Thank you, I think I figured out a system that will work.


The good thing about scriptable object singletons is they work outside of play mode, and you usually want them to. So domain reloads are a non-factor in this scenario. Whether there is one or not, you'll still have a reliable singleton instance.

And on the editor side of things we have ScriptableSingleton<T0> for configuration files.


Oh nice, that's good to know. I don't think I could use that here since I need to access the data in the build, but I have another tool that was using scriptable objects as editor config files, so I'll definitely switch to ScriptableSingletons for that.

1 Like