Initializing a static class after each scene load?

Is there a way to initialize a static class every time a new scene loads without needing an extra script to access the class?

My concern is that I want to create an object pool in this class that is created new at the beginning of each scene. I am not entirely satisfied with the approaches I am familiar with, such as creating the pool in the constructor when calling it for the first time or call an Initialization function from an other script.

What I found is the attribute:RuntimeInitializeOnLoadMethodAttribute, but here too I can only call the class once at the beginning of the application. Is there some similar way with calling a method after every scene switch?

Simply put, I’m looking for a Start() Function for a class that isn’t assigned to a GameObject.

There are scene-loaded delegates you can hook in the SceneManager class.

Be careful monkeying around with this non-mainstream timing of things. You may be unpleasantly surprised with WHEN things happen. Just use lots of Debug.Log() statements to understand calling and execution order.

Here is some timing diagram help:

1 Like

[InitializeOnLoad] is the attribute you put above the class definition, then implement a static constructor for that class. This will run after every domain reload.

But for scene reloads there are callbacks too, like activeSceneChanged.

2 Likes

Thanks for the answer. but does this approach not also assume that you have a script that is in the scene and inherits from monobehavior?

You’re not going to get away from having SOMETHING in some scene somewhere.

That’s how Unity’s dependency injection works: the scene (specifically scene zero) tells Unity what classes to instantiate.

From one tiny class you can do whatever else you need, if you insist on minimal use of the super-powerful scene mechanism.

I take it back. You can have a zero-scene game if you like.

using UnityEngine;

// @kurtdekker - for the purists who don't want ANY scenes in project

public class RunmeJohnny : MonoBehaviour
{
    [RuntimeInitializeOnLoadMethod]
    static void StartWithNoScenes()
    {
        Camera cam = new GameObject("Camera").AddComponent<Camera>();
        cam.transform.position = new Vector3(0, 0, -10);
        cam.orthographic = false;
        cam.fieldOfView = 60.0f;

        GameObject cube = GameObject.CreatePrimitive(PrimitiveType.Cube);
        var runme = cube.AddComponent<RunmeJohnny>();
        runme.cube = cube;
        // NOTE: the material on this script won't be included
        // in the build because... you didn't have a scene!
        // You would need to get a material on this cube or
        // else it will be hot pink.
    }

    // injected by the above
    GameObject cube;

    private void Update()
    {
        float angle = Time.time * 100.0f;

        cube.transform.rotation = Quaternion.Euler(0, angle, 0);
    }
}
1 Like

It is certainly possible, just not exactly straightforward, particularly for users who aren’t already familiar with the traditional Unity way of doing things.

1 Like

Also, it’s not exactly zero scene because there will always be an active scene when the code runs, whether it be the active scene in the editor, or the lone, non-addressable scene in the build settings. So, there really is no escaping it. Even if you experiment with RuntimeInitializeOnLoadMethod order, there are times when the first scene is not loaded, but it doesn’t complain if you instantiate objects, they just won’t be there when the first scene in the build settings does load. So, there is always some kind of “scene” there.

2 Likes

Thank you. I don’t intend to have nothing in the scene anyway. I’ll explain briefly what I have in mind or already have.

Only in the project folder:

public static class AudioManager
{
    static AudioManager
    {
        CreateObjectPool();
    }

    public static void PlaySound(Audioclip clip)
    {
        AudioSource source = GetSourceFromPool()
        source.clip = clip;
        source.Play();
    }
}

In Hierarchy:

public class Player : MonoBehaviour
{
    public void Update()
    {
        if (getHit) AudioManager.PlaySound(hitSound);

        if (isAttacking) AudioManager.PlaySound(attackSound);
    }
}

At the moment the object pool is only created for me when the class is used for the first time.
My wish would be to have a complete audio manager object pool that is just a script without any dependencies.

That’s easy. You can do that all day long.

ULTRA-simple static solution to a GameManager:

https://discussions.unity.com/t/855136/6

OR for a more-complex “lives as a MonoBehaviour or ScriptableObject” solution…

Simple Singleton (UnitySingleton):

Some super-simple Singleton examples to take and modify:

Simple Unity3D Singleton (no predefined data):

Unity3D Singleton with a Prefab (or a ScriptableObject) used for predefined data:

These are pure-code solutions, DO NOT put anything into any scene, just access it via .Instance!

The above solutions can be modified to additively load a scene instead, BUT scenes do not load until end of frame, which means your static factory cannot return the instance that will be in the to-be-loaded scene. This is a minor limitation that is simple to work around.

If it is a GameManager, when the game is over, make a function in that singleton that Destroys itself so the next time you access it you get a fresh one, something like:

public void DestroyThyself()
{
   Destroy(gameObject);
   Instance = null;    // because destroy doesn't happen until end of frame
}

There are also lots of Youtube tutorials on the concepts involved in making a suitable GameManager, which obviously depends a lot on what your game might need.

OR just make a custom ScriptableObject that has the shared fields you want for the duration of many scenes, and drag references to that one ScriptableObject instance into everything that needs it. It scales up to a certain point.

And finally there’s always just a simple “static locator” pattern you can use on MonoBehaviour-derived classes, just to give global access to them during their lifecycle.

WARNING: this does NOT control their uniqueness.

WARNING: this does NOT control their lifecycle.

public static MyClass Instance { get; private set; }

void OnEnable()
{
  Instance = this;
}
void OnDisable()
{
  Instance = null;     // keep everybody honest when we're not around
}

Anyone can get at it via MyClass.Instance., but only while it exists.

If you really insist on a barebones C# singleton, here’s a highlander (there can only be one):

1 Like

I really appreciate all the effort, but that’s not quite what I want to know or what I’m looking for.

I am familiar with most of the concepts related to singletons, events, etc. anyway. What I would like to have is less a manager and more an object pool, which should be initialized before it is actually used for the first time, and preferably not from another script. Here is my approach of an AudioManager object pool:

using System.Collections.Generic;
using UnityEngine;

public static class AudioManager
{
    private static List<AudioSource> audioSourcesPool;
    private static GameObject audioManagerObject;
    private static int poolSize = 10;

    public static void InitializePool()
    {
        audioManagerObject = GameObject.Find("AudioManager");
        if (!audioManagerObject) audioManagerObject = new GameObject("AudioManager");

        audioSourcesPool = new List<AudioSource>();

        for (int i = 0; i < poolSize; i++)
        {
            audioSourcesPool.Add(CreateNewAudioSource());
        }
    }

    public static void PlaySound(AudioClip clip)
    {
        if (audioManagerObject == null) InitializePool();

        AudioSource audioSource = GetAudioSourceFromPool();

        audioSource.clip = clip;

        audioSource.Play();
    }

    static AudioSource GetAudioSourceFromPool()
    {
        for (int i = 0; i < audioSourcesPool.Count; i++)
        {
            if (!audioSourcesPool[i].isPlaying) return audioSourcesPool[i];
        }

        AudioSource newAudioSource = CreateNewAudioSource();
        audioSourcesPool.Add(newAudioSource);

        return newAudioSource;
    }

    static AudioSource CreateNewAudioSource()
    {
        GameObject audioSourceObject = new GameObject("SFX");
        audioSourceObject.transform.SetParent(audioManagerObject.transform);

        AudioSource audioSource = audioSourceObject.AddComponent<AudioSource>();

        return audioSource;
    }
}

My question is, can I start the InitializePool() function from within this class?

A static constructor?

        static AudioManager()
        {
            SceneManager.activeSceneChanged += (scene0, scene1) => InitializePool();
        }

edit: or run at a specific part of the player loop, after scene load, e.g. using async UniTask.

Callbacks registered through a static class like SceneManager or EditorApplication work for every class that registers them, including classes NOT derived from UnityEngine.Object.

Are you sure??? Don’t say I didn’t warn you. :slight_smile:

The costs and issues associated with object pooling / pools:

https://discussions.unity.com/t/892797/10

https://discussions.unity.com/t/833104/2

1 Like

Yes I am sure. I’m targeting mobile platforms and with smooth camera movements I’m seeing noticeable stuttering when instantiating more complex objects. Object pooling completely solves the problem for me. Of course I could also optimize elsewhere, but that was the quickest fix to continue without problems and the profiler confirmed this again. And just so that the pooling doesn’t affect my workflow, I asked this question here. But thanks for the warning :wink:

Just use this: https://docs.unity3d.com/ScriptReference/RuntimeInitializeOnLoadMethodAttribute.html

Or, have it lazily initialise itself.

I believe my Audio pool system lazily initialises itself.

Edit: Just realised the docs for this attribute have been expanded upon. That’s good.

Yes, as mentioned at the top of this thread, I’m already using this and it works exactly as I want after launching the application. my question is if there is the same thing only that it is called after every loading of a new scene.

Did you miss this immediately after your first post?

1 Like

Yes…

First you get the delegate.

Then you get the powah!!

One thing to keep in mind is that the InitializeOnLoad attributes and the scene load events will fire on main thread, but if your code would ever call the static PlaySound() function from something other than the main thread, while your canary GameObject has been deleted through a scene unload and the scene load events you’re listening to haven’t fired yet, you’ll get an exception for trying to create a GameObject on a thread that is not the main thread.

If that might be an issue for you, you can sidestep that issue by creating the object via a coroutine started on the thread and creating the object after the first yield Return null, and check for initialization via either the Objects presence, or the running coroutine (e.g. via a bool set true in the coroutine on starting it and false after creating the object, or by keeping and clearing a reference to the coroutine.)

1 Like