Unity game manager. Script works only one time

Viewed 72827

I'm making simple game manager. I have a script, which will be accessible from all scenes in the game. And I need to check values of its variables after loading new scene. But my code runs only once after starting the simulation while an object with this script exists in all scenes. What is wrong? Why doesn't it work after loading a new scene?

5 Answers

@Fattie: Thanks for elaborating all this, it's great! There is a point though that people are trying to get through to you, and I'll just give it a go as well:

We do not want every instantiation of everything in our mobile games to do a "FindObjectOfType" for each and every every "global class"!

Instead you can just have it use an Instantiation of a static / a Singleton right away, without looking for it!

And it's as simple as this: Write this in what class you want to access from anywhere, where XXXXX is the name of the class, for example "Sound"

public static XXXXX Instance { get; private set; }
void Awake()
{
if (Instance == null) { Instance = this; } else { Debug.Log("Warning: multiple " + this + " in scene!"); }
}

Now instead of your example

Sound sound = Object.FindObjectOfType<Sound>();

Just simply use it, without looking, and no extra variables, simply like this, right off from anywhere:

Sound.Instance.someWickedFunction();

Alternately (technically identical), just use one global class, usually called Grid, to "hold" each of those. Howto. So,

Grid.sound.someWickedFunction();
Grid.networking.blah();
Grid.ai.blah();

Here is how you can start whatever scene you like and be sure to reintegrate your _preload scene every time you hit play button in unity editor. There is new attribute available since Unity 2017 RuntimeInitializeOnLoadMethod, more about it here.

Basically you have a simple plane c# class and a static method with RuntimeInitializeOnLoadMethod on it. Now every time you start the game, this method will load the preload scene for you.

using UnityEngine;
using UnityEngine.SceneManagement;

public class LoadingSceneIntegration {

#if UNITY_EDITOR 
    public static int otherScene = -2;

    [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
    static void InitLoadingScene()
    {
        Debug.Log("InitLoadingScene()");
        int sceneIndex = SceneManager.GetActiveScene().buildIndex;
        if (sceneIndex == 0) return;

        Debug.Log("Loading _preload scene");
        otherScene = sceneIndex;
        //make sure your _preload scene is the first in scene build list
        SceneManager.LoadScene(0); 
    }
#endif
}

Then in your _preload scene you have another script who will load back desired scene (from where you have started):

...
#if UNITY_EDITOR 
    private void Awake()
    {

        if (LoadingSceneIntegration.otherScene > 0)
        {
            Debug.Log("Returning again to the scene: " + LoadingSceneIntegration.otherScene);
            SceneManager.LoadScene(LoadingSceneIntegration.otherScene);
        }
    }
#endif
...

An alternate solution from May 2019 without _preload:

https://low-scope.com/unity-tips-1-dont-use-your-first-scene-for-global-script-initialization/

I've paraphrased from the above blog to a how-to for it below:

Loading a Static Resource Prefab for all Scenes

In Project > Assets create a folder called Resources.

Create a Main Prefab from an empty GameObject and place in the Resources folder.

Create a Main.cs C# script in your Assets > Scripts or wherever.

using UnityEngine;

public class Main : MonoBehaviour
{
    // Runs before a scene gets loaded
    [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
    public static void LoadMain()
    {
        GameObject main = GameObject.Instantiate(Resources.Load("Main")) as GameObject;
        GameObject.DontDestroyOnLoad(main);
    }
    // You can choose to add any "Service" component to the Main prefab.
    // Examples are: Input, Saving, Sound, Config, Asset Bundles, Advertisements
}

Add Main.cs to the Main Prefab in your Resources folder.

Note how it uses RuntimeInitializeOnLoadMethod along with Resources.Load("Main") and DontDestroyOnLoad.

Attach any other scripts that need to be global across scenes to this prefab.

Note that if you link to other scene game objects to those scripts you probably want to use something like this in the Start function for those scripts:

if(score == null)
    score = FindObjectOfType<Score>();
if(playerDamage == null)
    playerDamage = GameObject.Find("Player").GetComponent<HitDamage>();

Or better yet, use an Asset management system like Addressable Assets or the Asset Bundles.

actually as a programmer who comes to unity world I see none of these approaches standard

the most simplest and standard way: create a prefab, according to unity docs:

Unity’s Prefab system allows you to create, configure, and store a GameObject complete with all its components, property values, and child GameObjects as a reusable Asset. The Prefab Asset acts as a template from which you can create new Prefab instances in the Scene.

Details:

  1. Create a prefab within your Resources folder:

  2. create a script with contents like below:

using UnityEngine;

public class Globals : MonoBehaviour // change Globals (it should be the same name of your script)
{
    // loads before any other scene:
    [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
    public static void LoadMain()
    {
        Debug.Log("i am before everything else");
    }
}
  1. assign it to your prefab

and you can make it even better:

use prefab and namespaces together:

in your prefab script:

using UnityEngine;

namespace Globals {
    public class UserSettings
    {
        static string language = "per";
        public static string GetLanguage()
        {
            return language;
        }
        public static void SetLanguage (string inputLang)
        {
            language = inputLang;
        }
    }
}

in your other scripts:

using Globals;

public class ManageInGameScene : MonoBehaviour
{
    void Start()
    {
        string language = UserSettings.GetLanguage();
    }

    void Update()
    {
        
    }
}

Related