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.
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.
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;
}
Related
I'm trying to store the current music that is playing in the current scene that I'm on and then in the next scene change it for another one.
Here are my scripts.
AudioManager
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class AudioManager : MonoBehaviour {
public static AudioManager Instance;
private MusicList musicList;
[SerializeField] private AudioSource _effectsSource, currentMusic;
private void Awake() {
if (Instance == null) {
Instance = this;
DontDestroyOnLoad(this);
}
else Destroy(this);
Instance = this;
musicList = GetComponentInChildren<MusicList>();
}
public void PlayMusic(MusicId id) {
AudioSource musicToPlay = musicList.GetMusicSource(id);
musicToPlay.Play();
currentMusic = musicToPlay;
}
public void ChangeMusic(MusicId newMusicId) {
currentMusic.Stop();
AudioSource musicToPlay = musicList.GetMusicSource(newMusicId);
musicToPlay.Play();
currentMusic = musicToPlay;
}
public void PlaySound(AudioClip clip) {
_effectsSource.PlayOneShot(clip, 0.1f);
}
}
MusicList is a children of the AudioManager
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class MusicList : MonoBehaviour
{
public Music[] musicList;
private void Awake()
{
foreach (Music music in musicList) {
music.source = gameObject.AddComponent<AudioSource>();
music.source.clip = music.clip;
music.source.volume = music.volume;
music.source.loop = true;
}
}
public AudioSource GetMusicSource(MusicId id) {
foreach (Music music in musicList) {
if (music.id == id) return music.source;
}
return null;
}
}
Music
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
[System.Serializable]
public class Music
{
public MusicId id;
public AudioClip clip;
[HideInInspector]
public AudioSource source;
public float volume;
}
public enum MusicId {
Menu,
Gameplay
}
When I debug in the first scene the current track playing is stored, but when I change the scene and try to access the currentMusic value it's null.
Thank you for the help!
You have at least two approaches here. One of them is using proprietary scene data (not a technical term), meaning you use whatever info the current scene has, while the other method is using data persistence (this is a technical term) to "save" data in (normally) transient objects after scene changes. There is a third way to persist data, but it's not recommended for your use-case.
There are use-cases for all of them, each having different aims. Let's attack them:
scene info
This is straight forward: you have a menu, a tutorial, 2 levels, a boss fight level then the "end game + credits". This would mean 6 different songs (assuming each scene, including the end-credits has a different theme). Once you switch from menu to level and from level to level and then to "the end", a new song pops-up on the audio manager.
The simplest approach here is to have a GameObject in each scene (preferably with the same name and same components, only the song being different). Let's call it "SceneInfo". On this GO you can attach any info necessary to your level, like some description of the level, objectives, difficulty or song. You can use the Find() function to locate this GO then just access the component you need (like a component that saved the Music, then just pop-it on the AudioSource and Play() it).
Minor note: the scene GO must NOT be set as DontDestroyOnLoad(). Because you must not "carry" it in a different scene since each scene has its own stuff. And, of course, this should be different from your AudioManager GO.
This is prolly what you want for your approach.
data persistence
This is normally used to "carry" player-related stuff like the player icon, player name, health, inventory etc. from an earlier scene to this one. You can "carry" the music too, I guess, assuming it's a custom playlist OR if you want to continue the song. Otherwise, I don't recommend this for music if the songs are always different between levels.
This is basically what you did with your AudioManager.
storage (not recommended for what you need)
There would be a third option related to storage, by using PlayerPrefs, but I think this is overkill just for a song. But feel free to use this if you find it easier. As I said, it all depends on your use-case.
This option refers mostly to game preferences like volume, graphics settings etc. It's basically the "memory" of game settings.
Minor caveats:
I would set currentMusic; as public.
Your Awake() function has a weird flow. else Destroy(this); is missing a return. It's not wise to set the Instance to this since you're destroying the object.
Try this instead:
private void Awake()
{
if (Instance == null)
{
DontDestroyOnLoad(this);
Instance = this;
}
else if (Instance != this)
{
Destroy(this);
return;
}
if (musicList == null)
musicList = GetComponentInChildren<MusicList>();
}
You said: MusicList is a children of the AudioManager. I think you mean component instead of children, if the gameObject it's attached to is called "AudioManager", right? Else it doesn't quite make sense. A child class is something else.
Then the code in PlayMusic() is identical to ChangeMusic() (apart from stopping the previous song to update it). That means if you want to change something in the code for PlayMusic() or ChangeMusic(), you'll always have to do it twice (in each function). This is better:
public void PlayMusic(MusicId id) {
AudioSource musicToPlay = musicList.GetMusicSource(id);
musicToPlay.Play();
currentMusic = musicToPlay;
}
public void ChangeMusic(MusicId newMusicId) {
currentMusic.Stop();
PlayMusic(newMusicId);
}
... which can be simplified even more to this:
public void PlayMusic(MusicId id) {
currentMusic = musicList.GetMusicSource(id);
currentMusic.Play();
}
public void ChangeMusic(MusicId newMusicId) {
currentMusic.Stop();
PlayMusic(newMusicId);
}
And now there is no temp variable. It complicated the code for no reason.
(of course, assuming the id is always guaranteed to be in the list, which, of course, it should be). This way you only have one place to change your code or fix potential bugs.
While you can "force" serialization of C# objects, it's better to use Unity objects (anything from UnityEngine.object, but mostly rely on ScriptableObjects, Components and Prefabs).
The music init workflow is also weird. Instead of initializing the AudioSource on each music wrapper, you do it in a loop in the list. It's a bit "backwards", but not necessarily a bad thing. I just wouldn't do it.
I have this problem that I want to call an object from my first scene then call that object on my second scene . I tried doing this
if (instance == null)
instance = this;
else if (instance != this)
Destroy(gameObject);
DontDestroyOnLoad(gameObject);
and put it on the object I don't want to destroy then changed my scene on the
void Start(){
SceneManagement.LoadScene("Menu",LoadSceneMode.Single);
}
But it's not there on the heirarchy
Could someone help me out
EDIT:
Now when the next scene is loaded
The object I wanted is not there anymore. It is being destroyed
Create a persistent object
Create a preloader scene here you can place a splash screen or whatever you prefer but the important thing is loading things that should be persistent(maybe such as a network or gamemanager)
Create a script PersistentObject.cs and put the following code in it
private void Awake(){
DontDestroyOnLoad(this.gameObject);
}
Put this script on any object you initialize in the preloader
Access object from anywhere
If you want to access an object in another scene there are several ways but I will assume you do not have any specific reference to the object
So if we have a GameManager.cs and we created a Persistent cube in our preloader called Cube we can get a reference to the gameobject by saying GameObject cube = GameObject.FindGameobjectWithName("Cube");
Now you are able to do whatever you want by using cube
Write less, Do more with singletons
Creating a singleton will also be very useful as well
public static class Singleton<T>: MonoBehavior where T: MonoBehavior{
private static T instance;
//Notice the lower and upper case difference here
public static T Instance{
get{
if(instance == null){
instance = GameObject.FindGameObjectOfType<T>();
}
return instance;
}
}
}
You can then add this to your script make accessing properties easier and reduces the amount of code you have to write
public class Cube: Singleton<Cube>{
private string cubeName = "Jeff";
public void ChangeCubeName(string newName){
cubeName = newName;
}
}
To access this methods of this class you could now call the singleton from anywhere in your code
Example
public class GameManager: MonoBehavior{
private void Start(){
cube.Instance.ChangeCubeName("Joe");
}
}
So I have this manager in my start scene with DontdesoryOnload, that managers all the UIs, etc. It follows singleton pattern, so if I go to scene 2 and come back to my start-scene, the first manager will remain the same, and the manager in the newly opened start-scene will figure that there's already a manager, and destroy itself.
From here let's call the Manager remains alive Manager-Singleton and the manager that is destroyed as planned Manager-Dead.
The problem I'm having is that the references in my Manager-Singleton seem to false-reference.
When Manager-Dead is destroyed as planned, if I access a public GameObject under my Manager-Singleton, it will show me an error. Where if I click on those References fields in Inspector, it will lead me to the correct Gameobject which not not destoryed at all.
MissingReferenceException: The object of type 'GameObject' has been destroyed, but you are still trying to access it.
However, if I avoid Manager-Dead from being destroyed, (So there will be two managers in one scene), the code worked just fine.
I know you might be thinking, if there are two managers in the scene, there might be a UI overlap so that I might be clicking on ManagerDead's Button, and accessing its References. So after I got back to the Start scene, I manually disable the ManagerDead. And it turns out ManagerSingleton is changing ManagerDead's UI !
I really couldn't figure out where it went wrong. Any suggestions would be appreciated.
Following is some of my codes in case they might be useful:
[RequireComponent(typeof(UIManager))]
[RequireComponent(typeof(DataManager))]
[RequireComponent(typeof(StateManager))]
public class Managers : MonoBehaviour {
private static UIManager _UIManager;
public static UIManager UI
{
get { return _UIManager; }
}
private static DataManager _DataManager;
public static DataManager Data
{
get { return _DataManager; }
}
private static StateManager _StateManager;
public static StateManager State
{
get { return _StateManager; }
}
public string debugString = "";
void Awake(){
//Only one dataControl obj is allowed to exist and pass along.
if (GameObject.FindObjectsOfType<Managers> ().Length > 1) {
Destroy (gameObject);
} else {
DontDestroyOnLoad (gameObject);
}
_UIManager = GetComponent<UIManager> (); //!!!!! This is a Singleton class.
_DataManager = GetComponent<DataManager> ();
_StateManager = GetComponent<StateManager> ();
}
}
Problem solved, the Component referencing part is what's causing the trouble, Since _UIManager etc is static, that means when component referencing is called in Awake function of Manager-Dead, it will refer those reference to the GameObjects Under it even after it's destroyed.
This results in the static _UIManager from Manager-Singleton will be referred to the Destroyed Gameobjects under Manager-Dead.
I am doing a school project. I need to check the destroyed objects in my scene as soon as the scene starts. problem is I don't know how to make it load or where to attach the c# script.
public static class DestroyedObject {
static List<GameObject> objs = new List<GameObject>();
public static void Add(GameObject obj)
{
if(!objs.Contains(obj))
objs.Add(obj);
}
}
If you want it to run when you start the scene you need to attach it to a GameObject. Create empty and attach it as a component.
The code that you want to run on start should be in the:
void Awake
{
//Your code here
}
or
void Start
{
//Your code here
}
functions.
Start is called as soon as the class is instantiated and Awake is called when the scene is started. Depends where you want it in the call stack, but in your case i think it will do essentially the same thing.
I think what you're looking for is a way to "save" what objects have been deleted : you simply have to make your class inherit from MonoBehaviour and call DontDestroyOnLoad() so your object containing the script will exist between the scenes.
public static class DestroyedObject : MonoBehaviour
{
public static DestroyedObject Instance;
private static List<GameObject> objs = new List<GameObject>();
private void Awake()
{
if (!Instance)
{
Instance = this;
}
else
{
DestroyImmediate(gameObject);
}
DontDestroyOnLoad(gameObject);
}
public static void Add(GameObject obj)
{
if(!objs.Contains(obj))
objs.Add(obj);
}
public static List<GameObject> GetDestroyedObjects()
{
return objs;
}
}
Then you simply access your script using DestroyedObject.Instance.Add() or DestroyedObject.Instance.GetDestroyedObjects() (some people don't like this kind of design pattern but it has proven to be very effective when using Unity).
Also as #Sergey asked, why creating objects (on scene loading) in order to delete them afterward : you could do the revers operation (only instantiate the needed ones).
Hope this helps,
Can you describe what you are trying to achieve in total? Because it looks like your way is not the best way to do it ;).
If all you want to know is how to execute a script at scene start: create a script that inherits from MonoBehaviour (no need for static class), attach it to a gameobject in your scene, and thats it!
If you want to execute code as soon as the scene starts (and the gameobject is loaded), put your code in Awake() or Start(). You can read about the execution order of those functions here: https://docs.unity3d.com/Manual/ExecutionOrder.html
Making a script static means it will be active in all scenes and even before any scene is loaded.
Additionally, i would not recommend the use of static classes unless you really need them.
I need to group all my constants into one file and show them on the inspector. Here's what i've tried:
#define constants
#define speed 10.0f
#define hp 3
This doesn't work, no matter where i put them, Error:
Cannot define or undefine preprocessor symbols after first token in file
Use static
public static readonly float speed = 10.0f;
public static readonly int hp = 3;
It works, but when i attach it to the main camera, the constants do not show up in the inspector window. Well now I know inspector doesn't support static field.
Use Singleton as suggested
using UnityEngine;
using System.Collections;
public class GameConfig : MonoBehaviour {
private static GameConfig instance;
public GameConfig()
{
if (instance != null)
{
Debug.LogError("GameConfig Warning: unable to create multiple instances");
}
instance = this;
}
public static GameConfig Instance
{
get
{
if (instance == null)
{
Debug.Log("GameConfig: Creating an instance");
new GameConfig();
}
return instance;
}
}
Now if I add:
public float speed = 10.0f;
the GameConfig.Instance.speed IS accessible, but the mono editor does not pop out auto completion. And it get this message:
CompareBaseObjects can only be called from the main thread.
Constructors and field initializers will be executed from the loading thread when loading a scene.
Don't use this function in the constructor or field initializers, instead move initialization code to the Awake or Start function.
If I try:
public float speed = 10.0f;
public float Speed {get {return speed;}}
I get the same message.
But the game can still work, and variables show on inspector correctly.
Note: Even if i fix it, is there any other ways to do? since it seems redundant to write a constants with 2 names (property + field) and tedious work.
To use singleton in a gameObject in Unity by C#, you should not use the constructor of sub class(GameConfig), but create a GameObject and then add the needed component to it. Like this:
private static GameConfig _instance = null;
public static GameConfig instance
{
get {
if (!_instance) {
//check if an GameConfig is already in the scene
_instance = FindObjectOfType(typeof(GameConfig)) as GameConfig;
//nope create one
if (!_instance) {
var obj = new GameObject("GameConfig");
DontDestroyOnLoad(obj);
_instance = obj.AddComponent<GameConfig>();
}
}
return _instance;
}
}
By the way, in your method-2, you can get things done by setting up an inspect UI by your self. Build up a custom editor for GameConfig and the add things you want to inspect. Refer to CustomEditor attribute and Editor.OnInspectorGUI for more information. If you don't know how to customize an inspector or how to extend the default editor, you can find some useful guides in the Extending Editor in Unity's site (Custom Inspectors section may be suit for you).