Suppose I have an audio manager singleton (present in all scenes) and all scenes have a button to enable and disable audio.
Suppose I'm in the scene A and I go to scene B.
In scene B, the buttons that referenced the functions of the singleton lose their reference because the audio manager object is destroyed, because a previous audio manager comes from scene A and the one from scene B is destroyed (since there cannot be 2 of it).
Is there a smart way to solve this problem?
Of course I can create a class whose methods call singleton functions and let them in all scenes, but that doesn't seem smart.
On mobile so sorry for the formatting. One way I can think of doing this is having an Init() function in your singleton. In the way you are describing it, this object is DontDestroyOnLoad() as well. In the method of Start() or Awake() you're find all other instances of this object and if another exists deleting the new one so the old one persists.
Inside of this function if another object is found, simply call a new Init() function that takes all the references you need as parameters so the old Singleton gets all the information of the new one, then the new one is deleted.
public class myClass : MonoBehaviour {
public static myClass i;
[SerializeField] private Gameobject myReference = null;
void Awake () {
if(!i) {
i = this;
DontDestroyOnLoad(gameObject);
}else
i.Init(myReference);
Destroy(gameObject);
}
public void Init(GameObject myNewRef)
{
myReference = myNewRef;
}
...
Hope the snippet adds a bit more clarify. Again formatting might be iffy.
Related
This question already has answers here:
How to pass data (and references) between scenes in Unity
(6 answers)
Closed 3 months ago.
I started to learn Zenject + Unity. I learned the Zenject readme but I have no unity experience I need shared data between scenes and I want to have some code over the scene.
I try to inject some GameData class in two scenes.
My scenes have GameObjects with code where I use injections.
The first scene has the installer and the first scene loads the second scene as an additive
I make bind so:
public class MainInstaller : MonoInstaller
{
public override void InstallBindings()
{
Container.Bind<GameData>().AsSingle().NonLazy();
}
}
I guess that I will have one instance GameData
First scene:
private GameData _gameData;
[Inject]
public void Construct(GameData gameData)
{
_gameData = gameData;
SceneManager.LoadScene("Menu", LoadSceneMode.Additive);
}
private void Start()
{
_gameData.CurrentState = GameStates.Menu; // Makes some changes
}
Second scene
private GameData _gameData;
[Inject]
public void Construct(GameData gameData)
{
_gameData = gameData;
}
The injection works ok. But I don't see my changes in log. And I think that exist two instance of GameDate.
If second scene do't have another context, bind is taken over i think. But if not, I'm afraid maybe I don't graps your project current situation.
When bind is't taken over, you have other aproach.
standard way to share the Bind between two (or more) scenes.
1.Project Context
If now you use scene context, and Main Installer is attached it,
you can create ProjectContext. And you can make ProjectContext bind GameData instead.
(Maybe Main Installer's InstallBindings() Method become empty. But empty method and installer is required)
https://github.com/modesttree/Zenject#global-bindings-using-project-context
2.Context Parenting
This is easier way but remain Scope issue. because ProjectContext is global.
If scope issue annoy you, Scene Context Parenting is another way.
https://github.com/modesttree/Zenject#scene-parenting-using-contract-names
=supplement=
Zenject's bindng scope follow context.
Context has some types, ProjectContext , SceneContext, GameObjectContext.
Project Context is always top and global.
SeneContext is lower level than ProjectContext.
And it can be other SeneContext's parent (or child).
GameContext is lower level than SceneContext.
Hope this answer helps.
I'm trying to create a static GameAssets class where I can drag into it's references my Prefabs in order to manage every GameObject of my game.
The problem I have here is that when I start the game, the instance of my GameAssets is null (which I don't want) and it's instantiating a clone of GameAssets without the references linked to it.
Code of the GameAssets class
public class GameAssets : MonoBehaviour
{
private static GameAssets _i;
public static GameAssets i
{
get
{
if (_i == null)
_i = Instantiate(Resources.Load<GameAssets>("GameAssets"));
return _i;
}
}
public GameObject ProjectileLaserBall;
}
Hierarchy & Inspector
We can see that I have an empty GameObject called GameAssets with prefabs linked to it's references already!
How can I make Unity understand to use the existing GameAssets instead of creating a clone of it without it's references?
(As asked in my Script, a clone is created)
Clone of class
In this case as shown in your hierarchy image, you don’t need to create a new instance of your class. You already have an instance in your scene.
As such, when you start the game, you want the static reference to point to the already instantiated class that Unity already created after the scene was loaded.
The code would then look like:
public class GameAssets : MonoBehaviour
{
private static GameAssets _i;
public static GameAssets i
{
get
{
if (_i == null)
_i = this;
return _i;
}
}
public GameObject ProjectileLaserBall;
}
Your code does make use of the Resources folder. This make me think that you’ve also got a prefab if this GameAssets item in there as well. In this case, maybe your intention was to not define a GamesAssets object in your scene, but to load one in at runtime.
With prefabs, you can’t add references to scene objects (in the common sense), but you can add references to other prefabs. So, if you want to do it that way instead, make sure that your ProjectileLaserBall is also a prefab, instantiate the GameAssets object, add make sure you don’t already have a GameAssets object in the scene (as a new one is going to be created). The code for this class would still be the same as I’ve shown above, but you’d instantiate the asset from a different class altogether. If you’ve done everything correctly, you should have a single GameAssets item in the hierarchy that can be accessed by GameAssets.i
I found a way around that problem, still it is not solving it.
So instead of including my GameAssets directly into the scene, I leave it in the project folder and assign the references in the prefab GameAssets.
This works as it is creating a clone of GameAssets with the references when I'm calling it.
The problem here is that when I call it from a script, because it is not instantiated, the function doesnt show after GameAssets.i.
And we can't get the instance of a static class with the 'this' keyword so I really don't know how to get this to work properly
I am trying to implement a simple racing game in Unity 2021.2 and I have a GameManager Object that holds state across different scenes (i.e. races). To achieve this I use this code in my GameManager:
public static GameManager instance;
private void Awake()
{
if (instance == null)
{
DontDestroyOnLoad(gameObject);
instance = this;
} else if (instance != this)
{
Destroy(gameObject);
}
}
This works fine as long as the fields of the GameManager are of primitive types, such as int and string. However, my GameManager also has a field of type RacerData[], where RacerData is a class that I created in a separate script, which holds data associated with a racer, such as the name, score, etc.
Using this approach I ran into the following problem: When I transition from the first race to the second, i.e. when a new scene is loaded, the GameManager keeps the values of all the fields of primitive types, but loses the references to the RacerData objects.
My first attempt to solve this was that I turned my RacerData script into a MonoBehavior. Instead of just instantiating RacerData Objects in GameManager I created a prefab with the RacerData script attached and instantiated GameObjects from this prefab in my GameManager.
GameObject myRacerDataInstance = Instantiate(myRacerDataPrefab, …, …);
DontDestroyOnLoad(myRacerDataInstance);
instead of:
RacerData myRacerData = new RacerData();
This had the desired effect fo keeping the RacerData game objects when loading a new scene, however the references of the GameManager to those game objects is still lost when a new scene is loaded. I then proceeded to write a hacky workaround to reassign the objects in the next scene which, so far, I failed to make work. However, I'm less interested in making this workaround work rather than to understand why the references to the RacerData gameObjects are lost in the first place and what better way there is to keep data across scene loads.
This appears to be a very common use case to me and I can't quite believe that a developer is supposed to rewire the references manually, especially since those could form a much more complex object graph than mine.
So: Is there a way to preserve those references across scenes? Or is DontDestroyOnLoad() not the right mechanism in the first place and in that case: what better option is there to preserve data (including object references) accross scenes?
It turns out the problem wasn't quite what I thought it to be: The references weren't set on the GameManager object at the time I tried to access them, i.e. from the Start() method of another game object called RaceManager.
A quick fix to the problem was to simply wait for the references to be recreated like so (in RaceManager):
void Start()
{
StartCoroutine(WaitForRacers());
}
private IEnumerator WaitForRacers()
{
while (racers == null)
{
yield return new WaitForSeconds(1);
gameManager = FindObjectOfType<GameManager>();
racers = gameManager.GetRacers();
}
InitializeRace(); // initialization logic that relies on racers to be != null
}
I want to keep my player when switching scenes but without getting lots of copies of it , so I found out about singletons.You do something like this:
public class Player : Monobehaviour
{
private static Player instance = null;
void Start()
{
if (instance == null)
{
instance = this;
DontDestroyOnLoad(gameobject);
}
else
Destroy(gameobject);
}
}
And it's pretty hard for me to understand.I get how static variables work , they belong to the class not to the instance.So , first my objects is in scene A.I switch to scene B,a new gameobject will be created with the script Player on it.Now , since instance holds the reference to the gameobject in scene A , why won't the else statement get called and a new gameobject will actually be created?Also , if you could explain a little bit more detailed than unitydoc how DontDestroyOnLoad() works , I'd be thankful.
This code runs after the object is created. In fact it runs on a script on the GameObject. The else statement will run, and it will destroy the GameObject this script is attached to, which is what you want because you already had another copy in the world from previously (the one that is referenced by the "instance" variable).
JSchiff is correct that this code will run after an instance of this object is created, storing the current instance in your static variable for unconditional access to "the current instance" or destroying new instances if there is one already. The else branch in your code will run.
To answer your extended question about DontDestroyOnLoad and it's interaction with this static variable: all the objects in the Engine are held in memory by the Engine. A Scene can be thought of as a "collection of objects in memory" by the engine. When a scene is unloaded, all objects in that collection are destroyed, unless the engine has been told DontDestroyOnLoad (as in un-load, although documentation refers to it as a "swap". You can load scenes without unloading scenes, this might have been terminology which just grew outdated but that's conjecture on my part).
These ideas are combined to make your Singleton, a highly accessible (in this case static instance variable), persistent (through DontDestroyOnLoad), one of a kind (through destroying any instance after the first) object. This isn't the only way to apply the Singleton pattern, but it is a common one in Unity.
I have a script with a public UnityEvent and I am trying to drag a scene object to the object slot so I can access its methods but its not accepting the object. I'm not sure what i'm doing wrong.
The scene object is a prefab instance and I did try unpacking the prefab but it didnt make a difference.
In a comment you mentioned the script you are talking about is a StateMachineBehaviour.
StateMachineBehaviour does not inherit from MonoBehaviour but rather from ScriptableObject
ScriptableObject instances "live" in the Assets not a certain Scene
You (usually) can not have any Scene references in any assets like Prefabs or ScriptableObjects.
There are some workarounds however that still allow you to do that. You can e.g. create ScriptableObject containers for every value you want to pass. Something like e.g.
[CreateAssetMenu]
public class GameObjectReference : ScriptableObject
{
public GameObject gameObject;
public void SetActive(bool value)
{
if(gameObject) gameObject.SetActive(value);
}
}
such an instance of GameObjectReference now is a ScriptableObject and thus also "lives" in the Assets => you can reference it in any other asset as Prefabs and other ScriptableObjects.
So all you need to do is make sure that you set this value from within your scene e.g. using
[ExecuteInEditMode]
public class GameObjectReferenceSetter : MonoBehaviour
{
public GameObjectReference gameObjectReferenceAsset;
private void Awake()
{
gameObjectReferenceAsset.gameObject = gameObject;
}
}
You can create such a reference-asset and setter pair for each type you need and transpass the required public methods so you can call them from the UnityEvent.
Having the [ExecuteInEditMode] this should also be set already in EditMode. However since "officially" it is still not possible to have a scene reference on a ScriptableObject field the value usually says Type missmatch but references the correct object as you should see when you click on the field.
Another alternative is using some kind of dependency-injection (for Unity an often mentioned and free solution is e.g. Zenject). It is a bit complex to set it up the first time but once you got it working it is more flexible and better scaleable since you wouldn't need to implement a wrapper for each type you want to pass in to a StateMachineBehaviour.
More information and how-tos about Zenject can be found on their github page