int variable resetting when loading another scene in unity - c#

I created a football score calculator. I have a button that changes the scene, but when I change the scene and reopen it the score value is reset to 0. Here is the code:
public class Main : MonoBehaviour {
public Text plusUp;
public int value = 0;
public void button(int sc)
{
SceneManager.LoadScene(sc);
}
public void plus()
{
value++;
plusUp.text = value.ToString();
}
}

1. You can use the method Object.DontDestroyOnLoad on the object that hold the variable you want to save. It will keep the GameObject this script is attached too alive when another scene is loaded:
void Awake()
{
DontDestroyOnLoad(this.gameObject);
}
See the documentation for more informations
You can also make a Singleton but this design pattern is a little more complex as Unity will destroy it when you load another scene. You still have to use DontDestroyOnLoad see how to implement this pattern on their GitHub page
2. Or you can save the value on the disk before loading another scene and then load the value with PlayerPrefs helpers methods:
public int value = 0;
void Awake()
{
//Load the saved score (this value will be saved even if you restart the app)
value = PlayerPrefs.GetInt("Score");
}
public void button1(int sc)
{
//Save your value before you load the scene
PlayerPrefs.SetInt("Score", value);
SceneManager.LoadScene(sc);
}
See the documentation for more informations on the types.

There's a few core concepts that would prove useful to understand this issue.
Scope: Each scene operates in it's own scope. Any variables, objects, or changes that occur in one scene do not automatically transfer to another scene. When you start a scene, all objects in the scene are instantiated and initalized, and their Awake()/Start() methods are called if they are Monobehaviours.
Initialization - When an object is instantiated, it is initialized with constructors or default values. Monobehaviours do not have constructors, so any variables will defer back to default values.
Data persistence - When you change scenes, all game objects in the previous scene are destroyed, while all objects in the new scene are instantiated and initialized. Because all objects in the previous scene are destroyed, any values set on those objects disappear. You can prevent a GameObject from being destroyed with DoNotDestroyOnLoad(), but that does not overwrite the objects defined in the new scene. It is usually not advised to use DoNotDestroyOnLoad() as a core part of your game logic, as it often results in scenes being dependent on one another ("scene 1 has to define the values of a GameObject and pass it to scene 2 to be usable" = bad practice).
Solving your problem
It looks like you want score to persist as a value regardless of scene. Since all GameObjects and Monobehaviours are scoped within the scene, you can:
Force the object to be scene-analagous using the Singleton pattern.
Store score data to a file whenever it changes, and read from that file in the Start() method.
My recommended approach: Use a ScriptableObject to hold the score, and refrence that object when changing the score and updating your gameObjects. ScriptableObjects are scoped at the project level, so they automatically persist between scenes.

Related

How do I keep score between scenes in Unity dynamically?

I have a UI bug that I can not figure out. I wish to be able to carry my score over into each scene, while also having each scene independent of one another. If the player chooses to start the game in level 6 he should be able to do so with a clean scoreboard, same as starting at level 1. The score should proceed unitl to transfer until the player dies at which point the score should be set back to 0. To do this I've prefabbed my scoreboard and dropped it into every scene using the singleton pattern to make sure that there are no duplicate scoreboards in the scene. While trying to access my score text UI of type Text using UnityEngine.UI I receive a null reference error when the class is called after loading a new scene. Here are the C# classes defining the methods used in order to implement the functionality and screes snippets of my hierarchy in Unity 2019.4.1f1. I'm also attaching a short youtube video to show you that the score does work in the first scene, however it does not work in any other scene after. I do know that I'm destroying the Text UI at the beginning of every scene if there are multiple. What I do not understand is why the Text UI nested under the Scoreboard Canvas is not finding it's reference on Awake() for every instance created and why it's being called null. I truly appreciate anyone help!
Here are the screen snippets and videos in this order:
Youtube video of bug
Hierarchy of Scoreboard Canvas
Hierarchy of Score Text
Scoreboard singleton class (Attached to Scoreboard Canvas)/ ScoreText class (Attached to ScoreText UI)
Implementation of classes used to add score upon completion of level in CollisionHandler class
UI Bug Video
Hierarchy of Scoreboard Canvas
Hierarchy of ScoreText
Scoreboard Classes
CollisionHandler class used for implementation
If these don't help, here is the github link to see the project code:
Github Project Files
Store your score in a monobehavior that calls DontDestroyOnLoad(this.gameObject);
This will preserve that object between scene changes. https://docs.unity3d.com/ScriptReference/Object.DontDestroyOnLoad.html
As for why it is not working, I'm not really sure.
But I can tell you some ways around it:
You could try to make a public static instance of your ScoreText inside the script of ScoreText.
public static ScoreText _instance;
public static ScoreText instance { get { return _instance; } }
private int score;
private Text scoreText;
private void Awake()
{
if (_instance != null && _instance != this)
{
Destroy(this.gameObject);
}
else
{
_instance = this;
}
}
void Start()
{
score = 0;
scoreText.text = score.ToString();
}
And access its functions with:
ScoreText.instance.AddToScore();
It would be cleaner and easier to use, in my opinion.
If you would like to simplify a bit more, you could update scoreText in the update function as well. But since you only add points upon landing in those green zones, your way works fine as well.
Hope I could help.

Enable a gameObject that is present in another scene in Unity?

How do I set a gameObject true/false GameObject.SetActive(true); that is present in another scene while I do the above through a script from my current scene?
As #Eddge said, you can't access objects from scenes that aren't loaded.
Scene objects are actually created when the scene is loaded, so it's not possible to access them before.
The problem being, when the new scene is loaded, the old scene (with the script that wanted to call SetActive in your case) has been unloaded.
DontDestroyOnLoad
Do not destroy the target Object when loading a new Scene.
The load of a new Scene destroys all current Scene objects. Call Object.DontDestroyOnLoad to preserve an Object during level loading.
-- https://docs.unity3d.com/ScriptReference/Object.DontDestroyOnLoad.html
This is suitable if having an object from your current scene persist in the loaded scene helps solving your issue.
LoadScene additively
For cases when DontDestroyOnLoad doesn't cut it, it's possible to load your new scene without unloading the old one, giving you a chance to pass information between the two.
(this is actually what DontDestroyOnLoad does, as it puts the target object into a "scene" called DontDestroyOnLoad, that never gets unloaded)
You can use SceneManagement.LoadScene with the mode LoadSceneMode.Additive to achieve that. Be aware that both scenes will be active at the same time until you unload the first one!
See LoadScene, LoadSceneAsync and UnloadSceneAsync.
Statics
You can also simply store data in static fields, which are kept throughout your app's lifetime, to retain data and references across scenes.
That might be a simpler solution to pass a few primitive types, but remember that they are globals, and thus suffer the same drawbacks.
See Singleton pattern
There is a hacky way I just found out recently, scriptable objects will also work fine if you want to send data from scene to scene because scriptable is saved as an asset in the project so it is independent of the scene. Just create a scriptable object, then you can update your data from one scene and access them from another scene.
Example Code:
Simply create a scriptable object code:
[CreateAssetMenu(fileName = "StoreData")]
public class StoreData : ScriptableObject {
public bool objectActive;
}
Then access your scriptable object in the first scene and set your boolean in the scriptable object to true:
public class FirstSceneClass : MonoBehaviour {
public StoreData storeData;
public SetObjectActive() {
storeData.objectActive = true;
}
}
Then when you change your scene you can access your scriptable object again and set your gameObject to be active or not based on the boolean:
public class SecondSceneClass : MonoBehaviour {
public StoreData storeData;
void Start() {
if (storeData.objectActive) {
// Set your game object active here
}
}
}

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.

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

Unity MissingReferenceException when loading same scene for second time

I'm trying to create Arkanoid 3d game using Unity with C#. I've created simple Menu (Scene 0), where I can start my game, my main scene where actual game takes place(Scene 1) and Scoreboard (Scene 2), which is shown after losing all 3 balls Player has at start. After pressing any key i go back to Menu and can start game again. And this is where problem begins.
During second game after loosing 1st ball, my game goes crazy. I get loads of "MissingReferenceException"s like one below (but some linked to other objects (like GUIText's etc):
MissingReferenceException: The object of type 'Player' has been destroyed but
you are still trying to access it.
Your script should either check if it is null or you should not destroy the object.
Player.BallLost () (at Assets/Player/Player.cs:164)
GameEventManager.TriggerBallLost () (at Assets/Menagers/GameEventManager.cs:30)
Ball.Update () (at Assets/Ball/Ball.cs:47)
I noticed loads of MissingReferenceExceptions that are casued by not assigning variables. But this feels kinda diffrent for me since it all works perfectly during "1st play". What can cause this problem? I cheked in inspector after launching game for the second game and all variables are assigned to objects.
I'm not sure if shoudl insert game code since it has grown rather big and is split into >10 scripts.
In my case, the problem were two static events. One assigned to call a method whenever it was raised (created by another class), and one created in this class to inform other classes for the occurance of something.
So I just added the following two in the OnDestroy() method:
OtherClass.onNewX_event -= X_eventHandler;
for the fist one (where OtherClass was the other class which was raising the onNewX_event and the current class was handlening it)
onThisClassEvent = null;
for the event created and raised in this class.
I'm guessing you used Application.loadLevel(xx). This is what I found out about it:
Reloading the scene should reset all the variables unless you are using static variables because logically creating a new instance of every object would reset its values to their initial state.
Static variables on the other hand are not destroyed because they are part of a class, not an instance. You have to reset these manually.
DontDestroyOnLoad() is a little different. It tells Unity not to destroy an object when you load a new scene. So these objects won't be reset either because they aren't being destroyed and recreated.
The only way to reset them is just to manually go through and turn the variables back to some initial state. It is your choice how you do that. You can either save all the initial values, or copy the values over from a newly instantiated class.
As an addition I'd like to say if you use static variables, it might be more useful to put them all in a Singleton or change them into non-static variables.
Include below function in your GameEventManager class
public static void Nullify(){
GameStart = null;
GameOver = null;
LevelWon = null;
GamePause = null;
GameResume = null;
BallLost = null;
}
and call this function (GameEventManager.Nullify();) in Menu(scene0) before loading other scenes ;
GameEventManager.Nullify();
Application.LoadLevel("Scene1);
Hope this help...... :-)

Categories

Resources