Unity - How to properly reference the GameManager in different scenes [duplicate] - c#

This question already has answers here:
How to pass data (and references) between scenes in Unity
(6 answers)
Closed 1 year ago.
I am new to Unity, and starting on creating my first simple projects. For some days already I've been facing a problem and doing a ton of research but I still cant get it to work, here is it:
As far as I know, it is a smart idea to have a "GameManager" object with a "GameManager" script that holds the main functions of the game (please correct me if it isn't best practice). So lets put as an example I have Scene 1 and Scene 2:
Scene1:
-GameManager
-Label
in GameManager there is a function called ChangeLabelText()
Scene2:
-Button
-Scene2Script
in Scene2script there is a function called ButtonOnClick()
and here is my problem: how do I get ButtonOnClick() to call GameManager.ChangeLabelText()?
I always get a reference error.
I've tried to do it within the same Scene and it works perfectly, but not between different scenes.
Any ideas?
Thank you !

Changing scenes in Unity results in Unity destroying each instance. If you want to keep a specific instance/GameObject across several scenes, you may use the DontDestroyOnLoad method. You can pass the GameObject, this specific GameManager instance is attached to, as the parameter. You'd probably want to use a singleton pattern as well for two reasons:
You may not want to have two or more instances of the GameManager class at once. A singleton will prevent that.
You have to find a way to reference this instance nonetheless, since the components in your other class are totally new instances and they have no idea where this GameManager component is.
Example for a singleton pattern:
GameManager.cs
public static GameManager Instance; // A static reference to the GameManager instance
void Awake()
{
if(Instance == null) // If there is no instance already
{
DontDestroyOnLoad(gameObject); // Keep the GameObject, this component is attached to, across different scenes
Instance = this;
} else if(Instance != this) // If there is already an instance and it's not `this` instance
{
Destroy(gameObject); // Destroy the GameObject, this component is attached to
}
}
Example.cs
private GameManager gameManager;
void Start() // Do it in Start(), so Awake() has already been called on all components
{
gameManager = GameManager.Instance; // Assign the `gameManager` variable by using the static reference
}
This is of course only the basic principle. You may apply that in various slightly different variations if you need to.

Related

Unity DontDestroyOnLoad function for multiple objects

DontDestroyOnLoad script for mutliple objects, Without the objects being duplicated.
Code
I tried using this script, But it only works on one object, Whenever I use it for another object the first one gets destroyed.
You can follow these:
1- Create an object to the scene and add this script to this object.
public static YourClass Instance;
private void Awake()
{
DontDestroyOnLoad(this);
if (Instance != null && Instance != this)
{
Destroy(this);
}
else
{
Instance = this;
}
}
2- Then all you need to do is put anything you want to make Singleton and DontDestroy under this parent object in the hierarchy. Please check this image:
enter image description here
Note That: If you want to make singleton two different objects in two different scenes, then you need to create another class.

Singleton not getting Referenced in Start() Function after Scene Change

I am trying to understand why I am getting a missing reference exception after a scene change when using singletons.
So, I have two GameObjects in the first scene. A main camera with a GameManager script attached and another shop object with a Purchaser script attached. Both scripts are also singletons created like this for example:
public static Purchaser Instance
void Awake(){
Instance = this
}
They then both reference each other in the Start() function, again like this for example:
void Start(){
game = GameManager.Instance
}
Before a scene change, both scripts use each others singleton references to call methods from one another and everything seems to be working fine. Once I change scenes neither of those objects is in the next scene so they both get destroyed. However, once I go back to the main scene I receive a missing reference exception when the purchaser script attempts to call a method from GameManager using the singleton reference it gets from Start(), that changes the text of a text object attached to the main camera. This function is called after a button is pressed that is attached to the shop object that calls this function in the Purchaser script:
UpdateMoney(){
game.UpdateMoney(100);
}
I read around and was seeing that this may be because Start() will only being called once throughout the whole game run. Which meant the GameManger singleton instance of the Purchaser script was still the old one from before the scenes changed and that instance was destroyed. But, I just tested to see if this was true by putting a debug log in each scripts Start() function, and saw that after each scene change back to the main scene the debug log would go through from both scripts Start(). So, would it be right to say Start() is only ever called once for the life of script but not for the whole game run right? Shouldn't this also mean that once the game changes back to the main scene and both GameObjects are created again, the Purchaser script should now have an updated reference to the newly created GameManager script since Start() was called again?
What I also found was that this worked instead of using the game reference in UpdateMoney():
UpdateMoney(){
GameManager.Instance.UpdateMoney(100);
}
So, why does this work instead of using the game reference retrieved in Start()? Does this mean when Start() is called GameManager.Instance is still the old GameManager.Instance which is why game = GameManager.Instance does not work? Sorry this is very wordy. Any help is much appreciated.
Based on what I understand, you have a singleton made in the main scene. The game object where the singleton is attached to is destroyed after switching scenes.
First of all, is there a reason why you need the singleton to be a MonoBehaviour and attached to a game object? Because you can just make a class with
private static MyClass instance = null
public static MyClass Instance
{
get {
if(instance == null)
instance = new MyClass();
return instance;
}
}
This way, your singleton will always have a value and can be passed thru scenes unless instance is set to null.
Second, Start() is called once only. However, I encountered issues before that if the MonoBehaviour is attached to a disabled game object, the Start() will not be called. You can check if this is what's happening to you when you switch back to the main scene.
Third, if you really need the singleton to be a MonoBehaviour, you can use DontDestroyOnLoad(instance.gameObject) so the game object of the singleton will not be destroyed even after a scene switch. However. I assume the game objects are set on the scene. If it is not from a prefab, you can just do something like this
private static MyClass instance = null;
public static MyClass Instance
{
get {
if(instance == null){
GameObject inst = new GameObject("MyClass Singleton");
instance = inst.AddComponent<MyClass>();
DontDestroyOnLoad(inst);
}
return instance;
}
}
If you do this, then you can remove the preset game objects from the main scene and let the first call to MyClass.Instance make the game object for you.
Lastly, if you do not want to do that, you should set instance = null; on the game object's OnDestroy() so that when you enter the main scene, the new instances will be set to the singleton. This would mean the singleton will not have a value after you switch scenes outside of the main scene.

Accessing a script from another script at runtime in Unity C#

Like in here, but the difference is that it's supposed to be done from an instantiated prefab, so I can not drag the GameObject, that has the script with the variable I want to access, into this script.
This was working
public ScriptA script;
void Update() {
if (script.varX < 0) {
// . . .
}
}
But now I'm getting "Object reference not set to an instance of an object" error, which I think comes from the fact that the script trying to access ScriptA, is attached to an instantiated prefab.
How do I attach scripts and/or GameObjects at runtime?
Looks like you need to find your script type first, if it already exists in the scene:
public ScriptA script;
void Start()
{
script = GameObject.FindObjectOfType<ScriptA>();
}
void Update()
{
if(script.variable...)
}
You want to use AddComponent, like:
ScriptA script = gameObject.AddComponent<ScriptA>() as ScriptA;
See the docs here:
https://docs.unity3d.com/ScriptReference/GameObject.AddComponent.html
Best way to satify the links is fill the fields in the very next lines after you instantiate, that way you can avoid ugly and expenstive Find* calls (I am assuming the script that does the instancing can be made aware of what the target objects are, after all it knows what and where to instantiate)
Its worth noting that newly instantiated scripts' Awake() method will be called before Instantiate() returns, while its Start() will be called at the start of following frame, this is a major difference between the two calls, so if your instantiated script needs the refectences in Awake() you should either refactor (move stuff to Start()) or use Find* as suggested earlier.

Getting an Object Reference for an animation and executing [duplicate]

This question already has an answer here:
Error in C#: "An object reference is required for the non-static field, method, or property"
(1 answer)
Closed 5 years ago.
Doing a quick 2d game to get familiar with Unity and I came across an issue that no one else seems to have a problem with.
This is the Error I was originally receiving:
Assets/scripts/Receiver.cs(33,13):
error CS0120: An object reference is required to access non-static member
`UnityEngine.Animator.SetTrigger(string)'
I have updated the code from Animator.SetTrigger to myAnimator.SetTrigger and it has removed the error and I can test the game, but the animation does not come through.
I am looking to have these objects animate and disappear after being hit, but these are non moving objects and do not have an Idle animation. I set a trigger "IsDead" to activate the animation. I think I missed something or just not 'getting it'. I have tried to find similar problems to try and connect the dots (I'm new to c#) and ended up finding old functions from previous versions of Unity that didn't work or even exist in the same way.
before trying to add the animation, the game worked fine. I just need to see how the animation looks in-game.
I'm not trying to do individual sprite swaps. I am trying to understand how to make a death animation that connects to this prefab script that I can adapt to future prefabs. Also, can I reuse the "IsDead" trigger for separate animators in different prefab objects that have different animators attached to them?
the line in question is :
if(timesHit >= maxHits) *{Animator.SetTrigger ("IsDead");}
would this work as is with the reference?
working with prefabs and similar reacting objects in the future, this is something I want to have a good understanding of.. thank you for your time.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Receiver : MonoBehaviour {
public int maxHits;
private LevelManager levelManager;
private int timesHit;
Animator myAnimator;
void Start () {
var animator = gameObject.GetComponent<Animator>();
timesHit = 0;
levelManager = GameObject.FindObjectOfType<LevelManager> ();
}
void Update () {
}
void OnCollisionEnter2D (Collision2D col) {
timesHit++;
if (timesHit >= maxHits) {Animator.SetTrigger ("IsDead");}
}
void OnCollisionExit2D (Collision2D col) {
if (timesHit >= maxHits) {Destroy (gameObject);}
}
}
There are two kinds of Functions in strictly OOP Languages:
Static Function, that you call on the class. The Console Class functions are textbook examples for this.
Instanc functions, that you call on a specific instance. You want to SetTriggers for this specific instance.
There sometimes are static overrides - usually those require you hand in the instance as one of the arguments.
But thsi all has nothing to do with Unity or game development. That is just basic programming that you could have learned in a simple Console Appliation.

Duplicates because of DontDestroyOnLoad()

I have a strange problem with DontDestroyOnLoad. I have a map as a starting scene. From there, the user can click on certain map-objects and a new level is loaded with Application.LoadLevel() When the level is finished the map is loaded again. But, the objects with DontDestroyOnLoad attached are duplicated on the second load.
Current script:
void Awake()
{
DontDestroyOnLoad(transform.gameObject);
}
I searched online and found this possible solution to remove the duplicates:
public class NoDestroy : MonoBehaviour {
public static NoDestroy instance;
void Awake()
{
if (instance == null)
{
instance = this;
}
else
{
Destroy(this.gameObject);
return;
}
DontDestroyOnLoad(this.gameObject);
}
}
The above script simply does not work. How can I fix the problem?
Because the above script uses a static instance, it'll only work if a `single GameObject has it attached - worse yet, it'll delete other objects which try to use the same behavior.
The biggest problem is that whenever you load a scene, all objects in that scene get loaded. If they weren't unloaded (thanks to DontDestroyOnLoad) they'll be duplicated, since the scene doesn't know that they "already exist"
The above script might work better if you try to box your persistant objects under an umbrella, and only the umbrella object (usually called Toolbox) isn't destroyed. This is mostly appropriate for manager scripts, however.
If you know the objects that are meant to not be destroyed, consider loading them via a "Loading" scene. Since this moves the persistent objects out of your map scene, they won't get duplicated when reloading the map scene. Bonus to this pattern since it makes it easier to implement curtain drop.
If you want to implement this as a simple behavioural script, consider adding an ID like so
public class NoDestory : MonoBehaviour
{
private static Dictionary<string, GameObject> _instances = new Dictionary<string, GameObject>();
public string ID; // HACK: This ID can be pretty much anything, as long as you can set it from the inspector
void Awake()
{
if(_instances.ContainsKey(ID))
{
var existing = _instances[ID];
// A null result indicates the other object was destoryed for some reason
if(existing != null)
{
if(ReferenceEquals(gameObject, existing)
return;
Destroy(gameObject);
// Return to skip the following registration code
return;
}
}
// The following code registers this GameObject regardless of whether it's new or replacing
_instances[ID] = gameObject;
DontDestroyOnLoad(gameObject);
}
}
This will prevent the duplication of an object with the same ID value as one that already exists, as well as allowing recreation if said object has been Destory-ed elsewhere. This can be further refined by adding a special Editor script to hand over a new ID each time the script is implemented.
You could possibly make an "Initializer" scene that's the starting scene of the project, and all your "Don't Destroy On Load" objects get initialized in there. Then you immediately change to the real starting scene (your map screen) and all your objects exist and aren't duplicated. If you have a starting screen already, you might be able to use that instead of creating a whole new scene.
In case if anyone still needs the answer:
Answer which is available everywhere (WRONG ONE):
private static Sample sampleInstance;
void Awake()
{
DontDestroyOnLoad(this);
if (sampleInstance == null)
{
sampleInstance = this;
}
else
{
DestroyObject(gameObject);
}
}
Tl;dr Jump to solution.
What happens here is, when new scene is loaded, everything's fine. But as soon as you go back to previous scene, your original "Sample" game object is NOT destroyed and the new "Sample" game object which get created, is destroyed. So, what happens is, wherever you have referenced your Sample script (like in button onclick etc.) start referencing the duplicate script (which was destroyed of course), due to which they don't reference any script. Hence button.onClick no longer works.
So, the correct solution is to destroy the original Sample game object and not the duplicate, hence making the duplicate as the new original.
Here it is (THE CORRECT SOLUTION):
private static GameObject sampleInstance;
private void Awake()
{
if (sampleInstance != null)
Destroy(sampleInstance);
sampleInstance = gameObject;
DontDestroyOnLoad(this);
}
I have tested it. This works for me!!
if (Instance==null)
{
Instance = this;
DontDestroyOnLoad(gameObject);
}
For use cases where you have some startup logic that only needs to be initialized once, consider creating a Startup scene that only loads once at the beginning of your game. That way, whatever scene switching you do from that point on won't create duplicates of the game objects created with the Startup scene.
In relation to networking, that's what Unity did in their Boss Room example:
https://github.com/Unity-Technologies/com.unity.multiplayer.samples.coop/blob/main/Assets/Scripts/ApplicationLifecycle/ApplicationController.cs#L94

Categories

Resources