Home > database >  Unity Audio Singleton Across Scenes
Unity Audio Singleton Across Scenes

Time:11-26

So I have a basic singleton that handles audio across scenes.

    private void Awake() {
        if (Instance == null) {
            Instance = this;
        } else {
            Destroy(gameObject);
            return;
        }

        DontDestroyOnLoad(gameObject);
    }

Let's say this audio singleton is placed in Scene1, if I switch to Scene2 it seems to work. The only issue is if I start from Scene2, the audio no longer works. I'm guessing this is because the singleton is only created in Scene1 so there is no singleton reference in Scene2. I have tried making my singleton into a prefab so I can have them in each of my scenes which solves my issue of not having an existing singleton in Scene2 but if I switch scenes then it stops working.

Is there a way I can have an audio singleton that works even if I don't start at Scene1? I guess it doesn't have to be a singleton but that's what I have so far. I'm new to unity so I've only been looking up basic Unity tutorials.

Edit: The reason I want to start from Scene2 is because I want to test specific scenes.

CodePudding user response:

You can use my Singleton template below. When declaring your class use "public class MyClass : Singleton<MyClass>". What you need to do, is have a scene like Bootstrap or Startup and this is where you place your singleton gameobjects. They should use some standard singleton code very similar to what derHugo posted except I would not instantiate the component if it's not found - it should show an error in the log instead. Usually my manager singletons have useful properties and arrays on them that are set in the inspector, so just creating a component like that would lose all that functionality. Once you have your Bootstrap or Startup scene, you move it to the top of the load order in your Build scene list. You should have another singleton gameobject also in the Startup scene that then loads Scene1 or Scene2 or whatever you need. I make a singleton called GameManager that has a state machine and determines what scenes to load and know where we are. Quite often it will load in a GameUI scene or multiple UI scenes, and you can load them additively. That way you break up your game and UI into multiple scenes for organization. It's also important for collaboration when working on a team to have multiple scenes since they tend not to merge easily. Quite often people want an Intro scene, so the GameManager will have different states and move between them loading the different scenes. Don't call the singleton SceneManager though, Unity already has a class named that way.

using UnityEngine;

public class Singleton<T> : MonoBehaviour where T: MonoBehaviour
{
    protected virtual void Awake()
    {
        if (instance != null)
        {
            Debug.LogError($"Duplicate Singleton: {name} of type {typeof(T)}, destroying self");
            GameObject.DestroyImmediate(gameObject);
        }
        else
            instance = gameObject.GetComponent<T>();
    }

    static bool doneOnce;

    /// <summary>
    /// Returns the instance of this singleton.
    /// </summary>
    public static T Instance
    {
        [System.Diagnostics.DebuggerStepThrough]
        get
        {
            if (instance == null)
            {
                instance = (T)GameObject.FindObjectOfType(typeof(T)); // not really any point to doing this (it will fail), Awake would have happened but it's possible your code got here before Awake

                if (instance == null && !doneOnce)
                {
                    doneOnce = true;
                    Debug.LogError($"!!! An instance of type {typeof(T)} is needed in the scene, but there is none !!!");
                }
            }

            return instance;
        }
    }

    private static T instance;
}

CodePudding user response:

You could probably use a lazy instantiation like e.g.

[RequireComponent(typeof(AudioSource))]
public class CentralAudio : MonoBehaviour
{
    [SerializeField] private AudioSource _source;

    public AudioSource Source => _source;

    private static CentralAudio _instance;
    
    public static CentralAudio Instance
    {
        get
        {
            // instance already exists -> return it right away
            if(_instance) return _instance;

            // look in the scene for one
            _instance = FindObjectOfType<CentralAudio>();

            // found one -> return it
            if(_instance) return _instance;

            // otherwise create a new one from scratch
            _instance = new GameObject(nameof(CentralAudio), typeof(AudioSource)).AddComponent<CentralAudio>();
        }
    }
    
    private void Awake()
    {
        // ensure singleton
        if(_instance && _instance != this)
        {
            Destroy(gameObject);
            return;
        }

        _instance = this;

        // lazy initialization of AudioSource component
        if(!_source) 
        {
            if(!TryGetComponent<AudioSource>(out _source))
            {
                _source = gameObject.AddComponent<AudioSource>();
            }
        }

        DontDestroyOnLoad(gameObject);
    }
}

so now you can use e.g.

CentralAudio.Instance.Source.PlayOneShot(someAudioClip);

and the first time the instance will be created on demand.

  • Related