[SOLVED] Number Wizard UI changing the max range?

Hey everyone, I am finishing up my Number program and there seems to be one problem when I try to implement the extra credit from Unit 2. In my game, I added an additional scene called setMax which asks the player what number the player would like to be able to guess up to (Player set Max). The problem is that when I transition onto the next scene, the max seems to reset back to the original “1000” instead of keeping to whatever number the player chose as their max.

I figured that this has to do with the numberwizard script starting over again, but I was hoping would be able to tell me how to keep the variables the same between scenes so that the max number does not get reset. If anyone has any tips for this I would greatly appreciate it!!!

In case you want to take a look, here is my code!

using UnityEngine;
using System.Collections;
using UnityEngine.UI;

public class NumberWizards : MonoBehaviour {
	// Use this for initialization
	int max;
	int min;
	int guess;
	
	public int maxGuessesAllowed = 5;
	public Text text;
	 
	void Start () {
		startGame();
	}
	
	void startGame () {
		max = 1000;
		min = 1;
		guess = Random.Range (min, max+1);
		
	}
	
	// Update is called once per frame
	
	public void increaseMax() {
		max = max *10 ;
		text.text = max.ToString ();
}
	
	public void decreaseMax() {
		max = max / 2;
		text.text = max.ToString ();
	}
	
	public void GuessHigher() {
		min = guess;
		NextGuess();
	}
	
	public void GuessLower() {
		max = guess;
		NextGuess();
	}
	
	void NextGuess () {
		guess = Random.Range (min,max+1);
		//guess = (max + min) / 2;
		text.text = guess.ToString ();
		maxGuessesAllowed = maxGuessesAllowed - 1;
		if (maxGuessesAllowed <=0) {
			Application.LoadLevel("Win");
		}
	}
}

I think it’s because you reinitialize the value in “void startGame()”.

It could also be because the “max” variable isn’t designated as public.

Try to take out the “max = 1000” and initialize it when it’s first created: “public int max = 1000;”

Not 100% sure this will help.

Hey @Eric_Holt,

Yeah, I believe this is occurring because all the objects that are created are reset after a new scene is loaded (the variables don’t carry over). Unfortunately, the above solution you left does not work either. If anyone has any other suggestions or knows how to carry over these variables please let me know!

Hi @atakori,

To do this you should make a new empty gameobject in the “Start” scene and call it something like “MaxRangeManager”. You should also create a text object (called “MaxRange” or something) that will be changed when you increase the range. Then you should create a new script component in the new gameobject. The script should look something like this:

public static int max = 1000;
public Text text;

void Awake() {
	DontDestroyOnLoad(transform.gameObject);
}

public void MaxUp() {
	max = max *10 ;
	text.text = max.ToString ();
}

public void MaxDown() {
	max = max /2 ;
	text.text = max.ToString ();
}

The DontDestroyOnLoad(transform.gameObject); makes it so that when loading a new scene the new gameobject won’t be destroyed. In the Start scene you should also add the two buttons that will increase and decrease max, but you seem to already have done that so the only thing you have to change is that you should put the new gameobject in the on Click method. Finally you should change the max = 1000; in the startGame() method of the NumberWizards script to max = MaxRangeManager.max;

I hope this helps! If you need more explanation please tell me.

This is great @Saerreaver!,

Using this method allowed me to get the maximum to carry over to the next loading scenes and I was able to easily follow your explaination as well! Two additional questions though,

  1. I made it lecture 64 where the instructors talk about statics. Do you think you can explain how making the max in this instance static helps the overall code work? I am having trouble understanding why just doing int max =1000 wouldn’t work since this number is carrying over and can be manipulated through the other functions.

  2. I am now running into an issue where after the game runs a full loop (game to end to play again). I am finding that the the buttons for increasing and decreasing the maximum guess does not work anymore. The confirm button works though and the actual game scene buttons work as well. There are no errors on the consoled, but the buttons just don’t work. Do you have at idea for why this is happening. I modified the code a bit to make sure the Max Range Manager gameObject does not duplicate as well. Here is my code:

    using UnityEngine;
    using System.Collections;
    using UnityEngine.UI;

    public class MaxRangeManager : MonoBehaviour {
    static MaxRangeManager instance = null;

    public static int max = 1000;
    public Text text;

     // Use this for initialization
     void Awake () {
     	if (instance != null) {
     	Debug.Log ("Max Range Manager Duplicate is self-destructing!");
     	Destroy(gameObject);
     	} else {
     	instance = this;
     	GameObject.DontDestroyOnLoad(gameObject);
     	}
     }
     
     public void increaseMax() {
     	max = max *10 ;
     	text.text = max.ToString ();
     }
     
     public void decreaseMax() {
     	max = max /2 ;
     	text.text = max.ToString ();
     }
    

    }

Hello @atakori,
The way I did it was not the right way(Actually a pretty bad way) to do it. After looking through the code (and doing some research) this was what I came up with:

public int max = 1000;
public static DifficultyManager instance;
public Text text;

void Awake() {
instance = this;
}

void Start() {
	text.text = max.ToString ();
}

public void MaxUp() {
	max = max *10 ;
	text.text = max.ToString ();
}

public void MaxDown() {
	max = max /2 ;
	text.text = max.ToString ();
}

Now to make it work you have to change some things in the NumberWizard script, namely: max = MaxRangeManager.max has to be max = MaxRangeManager.instance.max. The MaxRangeManager does not need to have DontDestroyOnLoad(gameObject) in void Awake(). When Awake is called on the instance of the MaxRangeManager script attached to an object in the scene, it will set itself to the static variable “instance” so that any script can access the non-static variables it possesses. In this case only one instance of the MaxRangeManager will ever exist during a scene which is useful for managers in Unity. This is an example of a Singleton. That being said I’m not entirely sure why it’s able to access the max variable without needing the instance to exist in the Game scene.

So as to why you can’t just say int max = 1000; it’s because the object can’t just access the variable of another instance without having a reference or without having the variable be static.

I hope this fixed your problems and answers your questions (I’m actually glad you asked them, because now I have a better understanding of statics too)!

Hmmmmm… okay I think im starting to understand this more and more. So by this method, there is only one instance of the MaxRangeManger being accessed and (because it is static) we are able to use all of its public functions in Unity.

My next question would be why do we have to put [public static MaxRangeManager instance]? What purpose does making [instance = this] serve if we will not have the gameObject in the next scene through DonDestroyOnLoad?

I am also still confused as to how the script can take the Max from MaxRangeManager without having the gameObject appear in the game scene (I know you said you didn’t know, but if you figure it out let me know!)

By saying instance = this you set the instance to the MaxRangeManager object. If you don’t do that instance will be null and the other objects won’t be able to interact with the object properly. So MaxRangeManager.instance.difficulty won’t work in that case, because it can’t find a valid reference to the object. I think I understand why we’re able to access Max without MaxRangeManager existing in the Game scene. It’s because by making the Class static it becomes a singleton and global so you’re able to reference it in the NumberWizard code in another scene.

@Saerreaver, that’s great! I finally got it to work. I used that concept and tried to implement it along with the max Range manager gameObject continuing onto the next scene and it ended up working! I just had to create an additional separate script for the Increase and decrease max buttons (which called on the instance of the MaxRangeManager) and it finally ended up working! Thanks for all the help!

Hi mates! Here I found a way to make a configuration file using singleton. This is a good reference for understanding.

The idea is:
First create a singleton. In my case, class Cfg.
(Concepts involved: Singleton Design Pattern; C# get set; try catch blocks)

public class Cfg {
    protected static Cfg instance = null;

    protected int max = 1000;
    protected int min = 1;
    protected int maxGuessAllowed = 5;
    public string ErrMsg = string.Empty;
    public const int LMT_MAX = 9999;
    public const int LMT_MIN = 1;

    public int Max { get { return max; } }
    public int Min { get { return min; } }
    public int MaxGuessAllowed { get { return maxGuessAllowed; } }		

    protected Cfg(){}

    public static Cfg Instance
    {
	get
	{
	    if(instance==null)
	    {
		instance = new Cfg();
			
                // You don't use this function below because UnityEngine.Object != System.Object.
                // DontDestroyOnLoad() only accepts UnityEngine.Object.
                // Object.DontDestroyOnLoad(Cfg.instance);  
                }
                return instance;
	}	
    }

    public string SaveCfg(string maxStr, string minStr, float maxGuessAllowedValue)
    {
        ErrMsg = string.Empty;
        int maxToSave, minToSave, maxGuessToSave;
        try 
        {
             maxToSave = System.Convert.ToInt32(maxStr);
             if(maxToSave>LMT_MAX) throw new System.Exception();
	} 
	catch (System.Exception ex) 
	{
             Debug.LogError(ex.Message);
             ErrMsg= string.Format("Max guess is invalid. Please enter a number less than {0}.",LMT_MAX);return ErrMsg;
	}
	try 
	{
             minToSave = System.Convert.ToInt32(minStr);
             if(minToSave<LMT_MIN) throw new System.Exception();
	} 
	catch (System.Exception ex) 
	{
             Debug.LogError(ex.Message);
             ErrMsg = string.Format("Min guess is invalid. Please enter a number greater than {0}.",LMT_MIN);return ErrMsg;
	}
	if (maxToSave <= minToSave) 
	{
             ErrMsg = string.Format("Min guess shall be less than max guess. Please check."); return ErrMsg;
	}
	try 
	{
             maxGuessToSave = System.Convert.ToInt32(maxGuessAllowedValue);
	} 
	catch (System.Exception ex) 
	{
             Debug.LogError(ex.Message);
             ErrMsg = string.Format("Fail to convert slider value to integer. Please check."); return ErrMsg;
	}
	max = maxToSave;
	min = minToSave;
	maxGuessAllowed = maxGuessToSave;
	return ErrMsg;
    }
}

As you might have noticed, this Cfg class is not a MonoBehavior – Cfg doesn’t derive from MonoBehavior class. So actually a Cfg instance is a traditional C# object which won’t be automatically destroyed by Unity. Setting it to be a singleton enables it to be accessed via mere class – Cfg.Instance. Hence we can get access to the only one instance of Cfg from other UnityObjects (like a MonoBehavior instance). If the instance is created first time, the values will use the default ones (new Cfg(), constructor).

Here I use a SaveCfg() to save all the things needed in the Cfg instance. It checks the requirements and returns an error message. Here I set two limits for maximum and minimum.

Usage:

public class CfgManager : MonoBehaviour {
    public Cfg cfg = Cfg.Instance;

    public InputField MaxUI;
    public InputField MinUI;
    public Text MaxGuessAllowedUI;
    public Slider MaxGuessAllowedSlider;
    public Text ErrMsg;		

    public LevelManager levelManager;

    // Use this for initialization
    void Start ()
    {
    	LoadCfg ();
    }

    public void LoadCfg ()
    {
    	MaxUI.text = cfg.Max.ToString ();
    	MinUI.text = cfg.Min.ToString ();
    	MaxGuessAllowedSlider.value = cfg.MaxGuessAllowed;
    	ErrMsg.text = cfg.ErrMsg;
    	OnSliderValueChanged ();
    }

    public void SaveCfg ()
    {
    	ErrMsg.text= cfg.SaveCfg(MaxUI.text, MinUI.text, MaxGuessAllowedSlider.value); 
    	if(ErrMsg.text.Trim()==string.Empty)
    		levelManager.LoadScene ("Start");
    }

    public void OnSliderValueChanged ()
    {
    	int value = System.Convert.ToInt32(MaxGuessAllowedSlider.value);
    	MaxGuessAllowedUI.text = value.ToString ();
    }        
}

It’s purpose is pretty clear: This script (Since it is a MonoBehavior, this class is attached to a Unity GameObject) contains a Cfg instance which is a singleton existing in the whole life time of the game. When the Start() is automatically called by Unity LoadCfg() will read values from Cfg and then display on UI.

The effect on Game Bucket.io: here

So here is the idea: We use a common class object (instead of an Unity object) to store configuration information and make it a singleton. Then we read and save it every time we need to in our Unity scripts.

Hope this help.

Clivic