Trying to get script to run in new scene based on button pressed from previous scene

So here is my script and a screenshot showing how I tried to do it. Reference this post…
Previous Problem
As my previous post shows, I was running separate scenes for my different ranges. Now I want to run one scene called game, but on load run my int setting method(game100, game1000 etc.) based on which button was pushed on my start screen. As my screen shot shows, I added a onclick function to the button, had it interact with NumberWizard obj, and selected the method that the button was(game100,game1000 etc.) Well, it doesn’t work.

Conceptually, I think I need to get the method to run during the loading of the new scene, not the end of the previous as the objects it interacts with are not built yet. My problem is I don’t know how to get the game scene to reference which button caused it to load. Thanks again for anyone who can help, and for my “bat signal” see @Rob :grin:


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

public class NumberWizard : MonoBehaviour {
	int max;
	int min;
	int guess;
	int maxGuess;	
	
	public Text guessText;
	public Text test;
	
	


	
	public void StartGame100(){
		min = 1;
		max = 100;
		guess = 50;
		guessText.text = guess.ToString();
		max = max + 1;
		maxGuess = 8;
	}
	
	public void StartGame1000(){
		min = 1;
		max = 1000;
		guess = 500;
		guessText.text = guess.ToString();
		max = max + 1;
		maxGuess = 9;
	}
	
	public void StartGame10000(){
		min = 1;
		max = 10000;
		guess = 5000;
		guessText.text = guess.ToString();
		max = max + 1;
		maxGuess = 10;
	}
	
	public void GuessHigher(){
		min = guess;
		NextGuess();
	}
	
	public void GuessLower(){
		max = guess;
		NextGuess();
	}
	
	void NextGuess(){
		guess = (min + max) / 2;
		guessText.text = guess.ToString();
		maxGuess = maxGuess - 1;
			if (maxGuess <= 0){				
				Debug.Log ("level load requested " + name);
				Application.LoadLevel("win");				
		}		
	}
}

1 Like

hehe, hi :slight_smile:

Your description of the concept is a good one, I would add maybe one step in between the clicking of the button and the loading of the game scene, this would be where we store the difficulty and persist it so that we can then access it when the game scene loads.

The following is a way of achieving what you want, there will be others also :slight_smile:


To keep things tidy, we will create a Difficulty.cs script;

public static class Difficulty
{

}

This class will store the player selection after they have clicked a button.

We can use an enum to represent our different difficulty levels, you will be familiar with the use of these from the Text101 game (States etc).

We will also need to add a variable to hold the stored selection and provide a way of both getting and setting it. Our variable will be static so that it belongs to our class, not an instance of our class, and can persist.

public enum DifficultyLevel : int { Easy = 100, Medium = 1000, Hard = 10000 };

public static class Difficulty
{
    private static DifficultyLevel _level;


    public static DifficultyLevel Level
    {
        get { return _level; }
        set { _level = value; }
    }
}

In the above we;

  • define our enum called DifficultyLevel

    • we specify that its underlying type will be int (this is the default in C# anyway)
    • we add our constants to it, Easy, Medium, Hard
    • as an added benefit, we can specify the integer value for each of these
  • define a variable called _level

    • setting it’s type to DifficultyLevel (our enum)
    • setting its scope to be private
    • and make it static
  • define a property called Level

    • setting it’s type to DifficultyLevel (our enum)
    • setting its scope to be public
    • and make it static
    • we add a getter, which returns the value from _level
    • we add a setter, which sets the value of _level

Next, we need to think about the interaction between the buttons and the game. To me, it doesn’t feel appropriate to have the interaction between the game objects and the Difficulty class, nor does it really feel appropriate for that interaction to happen within your NumberWizard class, as this is really the main game logic.

What we will do, at least for now, is create a new class called GameManager. This class will interact with our buttons, set the difficulty level for the game, and make a call to the LevelManager to load the game scene.

Note, there is somewhat of an overlap between the GameManager and NumberWizard classes, but for now, we will focus on creating the desired functionality and making it work with the existing code, you can alway consider refactoring afterwards. :slight_smile:

using UnityEngine;
using UnityEngine.EventSystems;

public class GameManager : MonoBehaviour
{

}

Note the addition of the UnityEngine.EventSystems directive at the top of this script, this will allow us to use methods to interact with Events raised by objects.

We will need to add a method to this class which we can add to the OnClick event for each of the buttons, we will build on this as we continue.

using UnityEngine;
using UnityEngine.EventSystems;

public class GameManager : MonoBehaviour
{
    public void NewGame()
    {
    }
}

Now, with the scripts saved, return to the Unity editor and open your start scene;

  • create an empty GameObject in the Hierarchy

    • set its transform to 0,0,0 in the Inspector
    • click on the Add Component button
    • add the GameManager script
      image
  • select all three buttons within the Hierarchy (you can use CTRL + left-click to select multiple objects)
    image

    • scroll down to the Button script component in the Inspector
    • within the On Click () section;
      • remove any existing event listeners by selecting the row and clicking the - icon
      • click the + icon to add a new event listener to the list
      • drag your GameManager from the Hierarchy into the Object field
      • select the NewGame method from the function field
        image
  • save the scene

We now need to return to our GameManager script and add the rest of the code.

We are going to add two more methods to this class and call them from NewGame. Update the NewGame method as follows;

    public void NewGame()
    {
        SetDifficulty();
        StartGame();
    }

The SetDifficulty method will do exactly that, it will determine, based on the name of the button that was clicked, which difficulty level the game should start in;

    private void SetDifficulty()
    {
        GameObject button = EventSystem.current.currentSelectedGameObject;

        if (button != null)
        {
            switch (button.name.ToUpper())
            {
                case "EASY":
                    {
                        Difficulty.Level = DifficultyLevel.Easy;
                        break;
                    }
                case "MEDIUM":
                    {
                        Difficulty.Level = DifficultyLevel.Medium;
                        break;
                    }

                case "HARD":
                    {
                        Difficulty.Level = DifficultyLevel.Hard;
                        break;
                    }
            }
        }
    }

In the above;

  • we get a reference to the selected game object, e.g. the button that was clicked
    • just as a precaution, we check that this hasn’t returned as null
    • we use a switch statement to determine which button was clicked
      • we uppercase the GameObject’s name and compare against upper-cased values
      • this test is based on the name of the GameObject (not ideal, but it will work for now)
    • in each of the cases;
      • we set the static variable _level in our Difficulty class, via our property Level, to equal the appropriate difficulty level from our DifficultyLevel enum.

Important Note: The above method relies on your buttons being name, “Easy”, “Medium” and “Hard”, if they are not, either rename them, or, change the values in each of the case blocks in the switch statement above to match your button’s names.

The second method, StartGame gets a reference to our LevelManager and calls the LoadLevel method, as your original button click(s) would have.

    private void StartGame()
    {
        LevelManager levelManager = GameObject.FindObjectOfType<LevelManager>();

        levelManager.LoadLevel("game");
    }

Next we integrate the difficulty setting into your NumberWizard script, which is achieved by adding this Start method;

    private void Start()
    {
        min = 1;
        max = (int)Difficulty.Level;
		
        NextGuess();
    }

We can also now remove the following, by just commenting them out initially;

/*
	public void StartGame100(){
		min = 1;
		max = 100;
		guess = 50;
		guessText.text = guess.ToString();
		max = max + 1;
		maxGuess = 8;
	}
	
	public void StartGame1000(){
		min = 1;
		max = 1000;
		guess = 500;
		guessText.text = guess.ToString();
		max = max + 1;
		maxGuess = 9;
	}
	
	public void StartGame10000(){
		min = 1;
		max = 10000;
		guess = 5000;
		guessText.text = guess.ToString();
		max = max + 1;
		maxGuess = 10;
	}
*/

the following lines can be removed also;

using System.Collections;
public Text test;

There is one final thing we need to do. You have been rather awesome an added a varying number of maximum allowed guesses, based on the difficulty level, so far, we haven’t implemented that. Let’s make a couple of changes so that we can provide that functionality too.

There are a number of places we could set this, but as the maximum number of allowed guesses relate to the difficulty level of the game, it makes sense to me to store this value in the same place, Difficulty.cs;

public enum DifficultyLevel : int { Easy = 100, Medium = 1000, Hard = 10000 };
public enum MaximumGuesses : int { Easy = 8, Medium = 9, Hard = 10 };

public static class Difficulty
{
    private static DifficultyLevel _level;
    private static MaximumGuesses _guesses;

    public static DifficultyLevel Level
    {
        get { return _level; }
        set { _level = value; }
    }

    public static MaximumGuesses MaximumGuesses
    {
        get { return _guesses; }
        set { _guesses = value; }
    }
}

In the above we additionally;

  • define an enum called MaximumGuesses

    • we specify that its underlying type will be int (this is the default in C# anyway)
    • we add our constants to it, Easy, Medium, Hard
    • as an added benefit, we can specify the integer value for each of these
  • define a variable called _guesses

    • setting it’s type to MaximumGuesses (our enum)
    • setting its scope to be private
    • and make it static
  • define a property called MaximumGuesses

    • setting it’s type to MaximumGuesses (our enum)
    • setting its scope to be public
    • and make it static
    • we add a getter, which returns the value from _guesses
    • we add a setter, which sets the value of _guesses

Next we need to look at our GameManager script, as this is where we set the difficulty level based on the button click;

    private void SetDifficulty()
    {
        GameObject button = EventSystem.current.currentSelectedGameObject;

        if (button != null)
        {
            switch (button.name.ToUpper())
            {
                case "EASY":
                    {
                        Difficulty.Level = DifficultyLevel.Easy;
                        Difficulty.MaximumGuesses = MaximumGuesses.Easy;
                        break;
                    }
                case "MEDIUM":
                    {
                        Difficulty.Level = DifficultyLevel.Medium;
                        Difficulty.MaximumGuesses = MaximumGuesses.Medium;
                        break;
                    }

                case "HARD":
                    {
                        Difficulty.Level = DifficultyLevel.Hard;
                        Difficulty.MaximumGuesses = MaximumGuesses.Hard;
                        break;
                    }
            }
        }
    }

In the above, we have added an extra line in each of the case blocks which set our maximum guesses for each of the three difficulty levels.

We then open our NumberWizard script and update our Start method as follows;

    private void Start()
    {
        min = 1;
        max = (int)Difficulty.Level;
        maxGuesses = (int)Difficulty.MaximumGuesses;

        NextGuess();
    }

As you can see, we now set the local variable within NumberWizard to the value set in our Difficulty script.


So, that was a bit of a lengthy response - please take your time to work through it step by step and let me know how things turn out.

Hope this helps :slight_smile:

ScriptFiles.zip (1.3 KB)

:boom::drooling_face::gun:

I will be back, but I have some time to spend in the manual and scripting guides to unity. I have typed out multiple responses, but I keep answering my first questions as I try and phrase my second. I want to grasp conceptually what I am doing before I put it in my game.

One question, you have spent a considerable amount of time helping me. Why?

1 Like

One question, you have spent a considerable amount of time helping me. Why?

I try to help as many people on the forum as I can :slight_smile:

When you create a new script, lets use NumberWizard as an example, it says…

public class NumberWizard : MonoBehaviour

Now, we are creating a class called NumberWizard, but the : MonoBehaviour is assigning the new class NumberWizard all of its(MonoBehaviour) class properties, as defined by…

using UnityEngine;

Am I close?

1 Like

Hi,

MonoBehaviour is the name of another class, one which is part of the namespace UnityEngine.

The : MonoBehaviour indicates that the class NumberWizard will inherit from MonoBehaviour, and, as you rightly said, will then have access to all of its members, variables, properties, methods etc.

You can see all of these members in the Unity documentation, also, if you look at the top of the documentation, you will see that it states MonoBehaviour also inherits, in this case from Behaviour, which also inherits from another class.

The structure would look like this;

  • Object
    • Component
      • Behaviour
        • MonoBehaviour
          • NumberWizard

Each class that is inherited by another would be referred to as a base class.

As you can see, the NumberWizard script, whilst it may not have a lot of our content, certainly has a lot of other stuff, great if you need access to it all, not necessary if you don’t.

Try clicking through the inherits links from the documentation link below :slight_smile:


See also;

Ok, so MonoBehaviour has the predefined variable of useGUILayout.

Behaviour has the predefined variable of enabled.

Is it fair to say MonoBehaviour has the predefined variable of enabled?

It would be fair to say that you can access the enabled property of a GameObject which is inherting from MonoBehaviour. It’s a subtle difference, but I wouldn’t say it “has” it etc.

Does that help?

Yes

The reason I am trying to make sure I can grasp this is because we got rid of all of it when we created the difficulty script, and I want to make sure I understand why we can do that. Namespaces are sets of classes, and the set classes are unique and also have inherited properties from a hierarchy of classes predefined by Unity. You can also create your own name spaces so that identically named classes can operate uniquely, as they are under different namespaces. Got it!

When we got rid of that, it created more work for us, because we had to define functions that unity may have already created, but it gave us the freedom to name and define as we saw fit because we removed the possibility of conflict from any Unity predefined classes?

So were we creating a new Engine component for the game?

The reason I was able to ditch the MonoBehaviour inheritance for the Difficulty script was for a couple of reasons, but primarily it just didn’t need to have any actual interactions with Unity. That class could be dropped into another application, and used again. It’s fairly light-weight and doesn’t require instantiation because it is using static properties/variables.

As I mentioned in the really long post, there were several ways we could have tackled the issue, we could have probably dump it all into NumberWizard but then that would have changed the way in which the scene was set up, for example, the start scene doesn’t have a NumberWizard GameObject, in-fact, you could take that scene and use it in another game. As you could with the LevelManager. They are fairly generic.

I wasn’t too worried about any predefined classes within Unity, but by breaking the difficulty out into a separate class like this, it should have a little more clarity. Also, from an extensibility perspective, just like in Text101 where you added additional states, you should be able to just add more difficulty levels and more maximum guesses, the NumberWizard game would work quite happily without further change. Your start scene may require additional buttons I guess, but perhaps at that stage a redesign of how a difficulty was selected would be considered - and so on. :slight_smile:

I wouldn’t say that this class was an “engine component”, the “engine” is really Unity and it’s set of classes that allows us to perform tasks/operations, you could perhaps consider the Difficulty.cs to be an extension of NumberWizard by refactoring out common/duplicated code.

Awesome, Thank you!

Now, how were we able to not attach difficulty.cs to an object in the start scene? Do all scripts in assets load? I understand that we have made it public, so something can see it, but why is it available to be seen? We put (int)Difficulty.Level to assign our max. So this tells the script to look for the _________(class, method, type?) Difficulty and then the variable Level?

I’m sorry for so many questions. I really am trying to use resources to understand this and not waste your time.

This part I don’t understand either. Why not simply create a variable name Level, with values defined by our enum list. Then let the GameManager set which one, and the NumberWizard read it. Like this

public enum DifficultyLevel : int { Easy = 100, Medium = 1000, Hard = 10000 };

public static class Difficulty
{
    public static DifficultyLevel Level;

}

What does the _level do?

Hey, just so you know, I’m about to sign off for a little while, I have flagged this conversation and will come back to you as soon as I can and I will happily answer these questions, just need to eat and spend some time with the better half :slight_smile:

Dude, no expectations. You have already helped so much. Thank you.

Hi,

Answering your questions from the posts above;

Now, how were we able to not attach difficulty.cs to an object in the start scene?

Our Difficulty script was not inheriting from MonoBehaviour, as such, it doesn’t need to be attached to a GameObject in the scene to be used.

If it had been inheriting from MonoBehaviour then yes, we would need to attach it to a GameObject and it would be under the same order of execution as any other scripts inheriting from MonoBehaviour.

Do all scripts in assets load?

All scripts get compiled when the project is built.

I understand that we have made it public, so something can see it, but why is it available to be seen?

It is available because it is in the same project, the class and its members are public.

We put (int)Difficulty.Level to assign our max. So this tells the script to look for the _________(class, method, type?) Difficulty and then the variable Level?

Difficulty is the class/type. When you create a class you are creating a new data type.
Level is our public property, the one which returns the variable _level.

I’m sorry for so many questions. I really am trying to use resources to understand this and not waste your time.

No need to apologise, and you are not wasting my time, I reply as often/frequently as possible, sometimes there may be delays whilst other tasks are performed :slight_smile:

This part I don’t understand either. Why not simply create a variable name Level, with values defined by our enum list. Then let the GameManager set which one, and the NumberWizard read it. Like this

You absolutely could do that, and you will see throughout the course that interacting with variables directly is often the method used.

Properties can be very useful, primarily for protecting the variables within your class from being changed by other means. For example, if we made a public variable, anything, within the project would be able to change its value, either intentionally, or by accident, if someone made a mistake. If you use properties to return the value from your variable and only allow the variable to be updated through a specific process, control of updating the variable’s value is more secure.

This is a little irrelevant in our specific case, as I simply provided you with a setter as well, so, at this time, you could argue that the same issue applies, someone would just have to use the property to set the variable to whatever they wanted.

So, in this specific case, you could remove the public property and just set the variables directly. It’s a practice I am personally not keen on, and, from my background, one I have always avoided.

What does the _level do?

Ah, the underscore has probably caused the confusion here. _level is just the name of the variable. I prefix my class level variables with an underscore, this is not really necessary, but it does help identify the class level (member) variables easily. You may often see class level variables prefixed with m_ also, the “m” standing for “member”.

I hope the above helps :slight_smile:


See also;

I guess my confusion is that we never use _level. We set Level with a button click, and read Level on scene load.

The get/ set with return/ value I don’t understand. Wouldn’t we set the button click to set the _level variable, not the Level variable? Wouldn’t that force it through the get/set function?

1 Like

The property can update the variable for you and retrieve it, depending on which accessor is used, e.g. get or set.

public DifficultyLevel Level
{
    get { return _level; }
    set { _level = value; }
}

So, breaking the above down, line by line;

public DifficultyLevel Level
  • defines a property with a public scope, of type DifficultyLevel and named Level
get { return _level; }
  • this defines the retrieving accessor, it simply returns the value of our _level variable
set { _level = value; }
  • this defines the setting accessor, it sets our variable _level to the value passed to the property by the calling code

So we do use the _level variable but indirectly. By using properties you can manage the values inside the object, and avoid any corruption or misuse from calling code.

This video helped me understand, so I am going to share it. Now that I grasp the concept, I realize that your teaching me good coding discipline, but in my example there are no restrictions on getting the variable or restrictions on setting the variable. Makes it hard to explain something that we should be doing without a reason why.

But, it did create a question for me. This variable, Level, is set to an enum list. Does that put restrictions on the setting of the variable without actually creating a property? Or would I need to put the enum list as a “if” of the setting accessor?

In other words, if I created a “Super Hard” range of 1-100000 and created the button and the command in SetDifficulty, would it even allow that since I haven’t created that option in my enum list?

Hi,

Glad you found the video useful and thanks for sharing it for others too.

but in my example there are no restrictions on getting the variable or restrictions on setting the variable

There are restrictions on getting it. The variable is _level, it is set to private, so nothing other than the Difficulty class is going to have direct access to it. We expose it via the property called Level, and only in that way, thus access to it is restricted.

With regards to setting it though, you are correct, there is no further validation taking place, so whatever value is passed into the property called Level will set the variable _level behind the scenes.

Does that put restrictions on the setting of the variable without actually creating a property? Or would I need to put the enum list as a “if” of the setting accessor?

Yes, and no. See below.

if I created a “Super Hard” range of 1-100000 and created the button and the command in SetDifficulty, would it even allow that since I haven’t created that option in my enum list?

That would depend a little on the how you did it.

In the SetDifficulty method, if we were to replace this line;

Difficulty.Level = DifficultyLevel.Easy;

with this;

Difficulty.Level = 5;

You would see a conversion error, because the enum list is of type DifficultyLevel, and 5 isn’t one of those, it’s an int. You could of course convert it though, as the underlying type for our enum list is an int anyway, you could do this;

Difficulty.Level = (DifficultyLevel)5;

By placing the desired type in front of the value/object like this we are attempt to cast it to a different type, in this specific case it will work, again, because our underlying type for our enum list is an int. Now, this could represent a problem.

We had enum options for Easy (100), Medium (1000) and Hard (10000), but there wasn’t anything that corresponds with a “5”, so what happens.

Well, when you get to the NumberWizard script, the max will be set to the value of the enum, so in this case, max = 5. Our maxGuesses however is still set correctly for the Easy option because of the line in the SetDifficulty method;

Difficulty.MaximumGuesses = MaximumGuesses.Easy;

One thing to consider at this point is, using MonoDevelop or Visual Studio, the intellisense would prompt and give you a selector for the enums after you start to type DifficultyLevel, the developer would, I think it’s fair to say, just pick one from the list, but this is where you would need to make a decision. Is that robust enough for you?

For example, I could have given this example to someone else who just followed it, not being quite so eager to understand and they would have their solution working and all would be good - until one day - when it goes wrong for some reason and by then so much time has passed it isn’t necessarily obvious as to why. So kudos to your inquisitiveness. I think we should probably go ahead and make this a little bit better.

So, the behaviour we have seen is that it is possible for a developer to bypass the values in our enum list, so, an obvious way to protect against that would be to check that the value being passed into our property called Level is that it is actually in our enumerated list, thus, it has been defined.

Whilst we are at it, we should probably consider the MaximumGuesses property also, as that has been designed the same way and will, therefore, exhibit the same issue.

We now know that we need a solution which can check for a value in an enum list and that we will need it more than once. As such, I personally wouldn’t want to write a lot of if statements within the set accessor of the property, we would be duplicating, so instead we can create a method which can validate the value being passed in which we can then use for both of our properties.

    private static void Set(Type enumType, object value, object variable)
    {
        if(Validation(enumType, value))
        {
            variable = value;
        }
        else
        {
            throw new ArgumentException(value.ToString() + " is not a valid argument for "  + enumType.ToString() + ".");
        }
    }

    private static bool Validation(Type enumType, object value)
    {
        return Enum.IsDefined(enumType, value);        
    }

In the above we;

  • define the method Set which receives the type of an object, a value and a variable

    • enumType the data type of the object we want to set
    • value is the value which we want to set, passed in as an object because it could potentially be anything
    • variable is the variable we want to set, again passed in as an object to make this method more generic
  • test to see whether a method called Validation returns true, or false

    • if true, validation was successful, we can therefore populate our variable with our value
    • if false, validation was unsuccessful, the value was not defined in our enumerated list, so we throw an execption
  • define the method Validation which receives the type of an object and a value

    • enumType is again the data type of the object to validate
    • value is that which we will check to see if it has been defined in our enumType

We then need to call this method in our set accessor of our properties;

    public static DifficultyLevel Level
    {
        get { return _level; }
        set { Set(typeof(DifficultyLevel), value, _level); }
    }

    public static MaximumGuesses MaximumGuesses
    {
        get { return _guesses; }
        set { Set(typeof(MaximumGuesses), value, _guesses); }
    }

In the above;

  • the set accessor now calls our Set method, passing it the type of each of our enumated lists, the value passed to the property and finally the variable which we want to populate.

If you were to now go back to the SetDifficulty method and try;

Difficulty.Level = (DifficultyLevel)5;

for example, you will receive an exception;

image

Note: The Validation method was added primarily for readability, you could have just as easily put the Enum.IsDefined check in the if statement within the Set method like so;

private static void Set(Type enumType, object value, object variable)
{
    if(Enum.IsDefined(enumType, value))
    {
        variable = value;
    }
    else
    {
        throw new ArgumentException(value.ToString() + " is not a valid argument for "  + enumType.ToString() + ".");
    }
}

Hope this helps :slight_smile:

Privacy & Terms