I am working on a project that requires around 30 buttons in a UI menu. Each of these buttons has a "Loader" script attached to it, and the overall scene has a GameManager object (which persists through scenes).
The GameManager holds an enum with a number of named states equal to the number of buttons.
The idea is I need to populate the next scene with specific content depending on the button pressed, so I thought to assign each of these buttons an enum value, and when clicked the Loader script will change the GameManager's enum to equal the button's value. This way I can avoid making 30+ scenes and instead change the single scene based on the state. When the next scene is loaded, it should determine what the current state is, and does stuff based on that.
The problem is, even though I am able to manually assign the enum value of each individual button in the editor, that doesn't seem to actually do anything. Here are my scripts (abbreviated for only the relevant methods), and sample output when clicking a button.
Here is my GameManager:
public class GameManager : MonoBehaviour {
//Used so MenuContent scene knows what random content to populate with
public enum MenuChosen
{
Tabs, Formal, Casual, Headwear, Accessories, Speakers, ComputerParts,
GameConsoles, MobilePhones, Furniture, Lighting, OfficeSupplies, Gadgets, Toys,
Summer, SchoolSupplies, ArtsCrafts, Checking, Savings, PayPal, OtherAccount, Visa,
MasterCard, AirMiles, OtherCards, Food, Retail, Digital, OtherCoupons,
PayTransaction, ScheduledPayments, LoanPayment, MobileRecharge
};
public MenuChosen chosen;
}
And here is my Loader class. It is attached to every button. In the editor, I selected four buttons, and manually changed their states to "Formal", "Casual", "Headwear", and "Accessories", via the drop-down list in the inspector for each of them.
The Loader class creates a reference to the GameManager object, and attempts to change its "chosen" enum variable to be equal to the value that was set in the inspector window (which is called "MenuOption" in the loader).
public class Loader : MonoBehaviour {
public GameManager gm;
public GameManager.MenuChosen MenuOption;
private void Start()
{
gm = GameObject.FindGameObjectWithTag("GameManager").GetComponent<GameManager>();
Debug.Log("Current Loader State: " + MenuOption);
}
public void LoadMenuContent()
{
Debug.Log("Button's State is: " + MenuOption);
gm.chosen = MenuOption;
SceneManager.LoadScene("MenuContent");
Debug.Log("Current state is: " + gm.chosen);
}
}
When I first run the game, the debug messages show that the buttons' enum values are indeed changed:
Current Loader State: Accessories
Current Loader State: Headwear
Current Loader State: Formal
Current Loader State: Casual
However, the logs printed in the LoadMenuContent() method show something different:
Button's State is: Tabs
Current state is: Tabs
So even though it shows that the buttons' MenuOption is properly changed, once it reaches LoadMenuContent(), which is called when the button is pressed, then somehow the state has changed back to the default state, "Tabs".
Any thoughts on why the enum might be changing?
I discovered what my issue was.
Due to the large quantity of buttons, I had resorted to copying and pasting them.
This meant that each button was identical, and had its own copy of the Loader script as a component. By doing this, I was able to change the individual values of the enum of each individual button in the inspector.
However, this also meant that each and every button, in the OnClick() method, were all still holding a reference to the original button that I had copied and pasted. So these buttons were independent, but they all referenced the same exact script from the same exact button, who's state had never been changed from the default.
In order to fix this, I had to go in and manually drag each button into its own OnClick() method as a reference to itself, so they were properly calling the correct function from their own individual Loader script.
Related
I know this is a very long question, but I tried my best to be as clear as possible with this weird problem. Any help or suggestions would be appreciated. Let me know if I can provide any more resources.
The Setup
I am trying to create a sort-of navigation system where the user can still see what path they are on. I have three sets of buttons:
Year Buttons - these are three static buttons that are always on the screen (year 1, 2 and 3).
Discipline Buttons - these are dynamically instantiated and destroyed based on which year button has been selected.
Module Buttons - these function the same as the discipline buttons, but take into account the year AND discipline button when instantiating and destroying.
How It Should Work
When the user clicks on a button, the button must change color and stay that color until a different button of THE SAME SET (mentioned above) is pressed.
Example
User presses "YEAR 1" button. Button changes from orange to green. Discipline instances are instantiated based on year = 1.
User then presses one of the instantiated DISCIPLINE buttons. This button changes from orange to green. The YEAR button should still stay green. Module instances are instantiated based on year=1 and discipline text.
User presses "YEAR 2" button. module and discipline instances are destroyed, and YEAR 1 button must change back to original color. YEAR 2 button must change to green and discipline instances are instantiated.
My Code
I have three lists: one for buttons, one for disciplines, one for modules. The button list is static, while the other two are destroyed and added to as needed (the list adding/clearing does work).
When I instantiate disciplines or modules, the previous ones are all destroyed (and the list is cleared) and then each instance is added to the list. Each instance has an onClick listener (made in the script, it does not work to make the listener Editor-side) which calls this general method:
public void ChangeAllWhite(List<GameObject> list)
{
foreach (var button in list)
{
button.GetComponent<Image>().color = orangeColor;
}
}
and then calls one of these three methods to change that instance color:
public void YearColorChange(GameObject buttonToChange)
{
ChangeAllWhite(yearButtons);
buttonToChange.GetComponent().color = selectedColor;
}
public void DisciplineColorChange(GameObject buttonToChange)
{
ChangeAllWhite(disciplineButtons);
buttonToChange.GetComponent<Image>().color = selectedColor;
}
public void ModuleColorChange(GameObject buttonToChange)
{
ChangeAllWhite(moduleButtons);
buttonToChange.GetComponent<Image>().color = selectedColor;
}
This is the code to instantiate the disciplines (same as module pretty much) after the previous ones have been destroyed:
foreach (string item in disciplines)
{
GameObject disciplineInstance = Instantiate(DisciplineItem);
disciplineInstance.transform.SetParent(DisciplineLayoutGroup.transform, false);
disciplineInstance.GetComponentInChildren<Text>().text = item;
disciplineButtons.Add(disciplineInstance);
disciplineInstance.GetComponent<Button>().onClick.AddListener(() =>
{
UnityEngine.Debug.Log("debug discipline");
AppManager.instance.classroomInfo.SetDiscipline(item);
StartCoroutine(AppManager.instance.web.GetModules(AppManager.instance.classroomInfo.year, item));
DisciplineColorChange(disciplineInstance);
//needs year, discipline
});
}
What currently happens
I don't get any errors, but the colors don't change. When I called the methods from an OnClick on the editor-side, it breaks and/or doesn't work. I think what is happening is that OnClick methods from previously instantiated (and now deleted) instances are trying to do something and then it just doesn't do anything. Any suggestions are welcome!
Your code seems incredibly over-complicated.
Why not use the various button-states-colors that are built in to Button ?
Alternately, you must build a class that has the colors (animations, images, fonts, whatever) you want as a property and add that component to your buttons and use that. (In an ECS system, "adding a component" like that is a bit like extending a class in normal OO programming; you're just adding more behavior to the Button.)
So you would have your own cool
`SugarfreeButton`
and then you can do stuff like
sugarfreeButton.Look = YourLooks.Possible;
sugarfreeButton.Look = YourLooks.CannotChoose;
sugarfreeButton.Look = YourLooks.Neon;
etc. You really HAVE to do this, you can't be dicking with setting colors etc in your higher-level code.
It's only a few lines of code to achieve this.
I've attached a completely random example of such "behaviors you might add to a button" at the bottom of this - DippyButton
When you do "panels of buttons" it is almost a certainty that you use a layout group (HorizontalLayoutGroup or the vertical one). Do everything flawlessly in storyboard, and then just drop them inside that in the code with absolutley no positioning etc. concerns in the code.
Unfortunately, u won't be able to do anything in Unity UI until extremely familiar with the two layout groups (and indeed the subtle issues of LayoutElement, the fitters, etc.).
You should almost certainly be using prefabs for the various button types. All of your code inside the "foreach" (where you add listeners and so on) is really not good / extremely not good. :) All of that should be "inside" that style of button. So you will have a prefab for your "discipline buttons" thing, with of course class(es) as part of that prefab which "do everything". Everything in Unity should be agent based - each "thing" should take care of itself and have its own intelligence. Here's a typical such prefab:
So these "lobby heads" (whatever the hell that is!) entirely take care of themselves.
In your example, when one is instantiated, on its own, it would find out what the currently selected "year" is. Does it make sense? They should look in the discipline database (or whatever) and decide on their own what their value should be (perhaps based on their position (easy - sibling index) in the layout group, or whatever is relevant.
You will never be able to engineer and debug your code the way you are doing it, since it is "upside down". In unity all game objects / components should "look after themselves".
You should surely be using a toggle group as part of the solution, as user #derHugo says. In general, anything #derHugo says, I do, and you should also :)
Random code examples from point (1) ... DippyButton
using UnityEngine;
using UnityEngine.UI;
// simply make a button go away for awhile after being clicked
public class DippyButton : MonoBehaviour
{
Button b;
CanvasGroup fader;
void Start()
{
b = GetComponent<Button>();
if (b == null)
{
Destroy(this);
return;
}
b.onClick.AddListener(Dip);
fader = GetComponent<CanvasGroup>();
// generally in Unity "if there's something you'll want to fade,
// naturally you add a CanvasGroup"
}
public void Dip()
{
b.interactable = false;
if (fader != null) { fader.alpha = 0.5f;
}
Invoke("_undip", 5f);
}
public void _undip()
{
b.interactable = true;
if (fader != null) { fader.alpha = 1f; }
}
}
I create a set of prefabs at runtime via script. They are held in an array called newObj. Each one has some text UIs and some buttons, which I retrieve with GetComponentsInChildren. When a user clicks the first button in the prefab, I want to run a function that changes the text of the button and highlights that button.
Everything is working except the button doesn't highlight.
public void SelectPlayer(int rowSelected)
{
var buttons = newObj[rowSelected].GetComponentsInChildren<Button>();
var texts = newObj[rowSelected].GetComponentsInChildren<Text>();
texts[0].text = "1";
buttons[0].Select();
buttons[0].OnDeselect(null);
}
Oops. I'm still new to Unity and forgot that Unity sets the default highlight color to white (for some reason). Once I changed it for my prefab using the editor, all was well.
I need to change the items placed in UI on basis of items clicked on.
User have to click on Name of the game then it will unable the existing item and enable the chapters item to show chapters.
Note: I don't need to change scenes, I know how to change scene with buttons.
I have attached the screenshot of the main menu.
Just create a reference of the gameObjects you want to Activate/Deactivate.
Create a button and use GameObject.SetActive to activate/deactivated the objects you want when the user press it.
You can make the button invisible so the user thinks he's clicking the title but actually he clicks a button.
I Hope this helps. :D
I suggest creating a button for every menu interaction in general.
To handle your buttons OnClickEvents you need to create an empty gameObject on your scene and attach to it a script that your buttoms will use to do whatever you want when you click them.
For example:
//You can name your method however you like.
public void ButtomClicked(){
//Hide the UI on the screen expect the Back button.
//Show chapters to the player
}
Create a button and from the inspector select the Empty Gameobject this script is attached to and then select the ButtomClicked method. When you press the buttom the code in the method will run.
To avoid activating/deactivating all this buttons one by one, you can attach them to a panel(UI element) and activate/deactivate the panel istead. So, lets say you have 3 panels.
The Main menu panel, the Chapters panel and the Options panel.
When the player wants to see the chapters, you diactivate the main menu panel and activate the chapters panel. To make this feel really polished you can add transition animations later on.
This is how i handle my UI without never changing scenes. It gives a really smooth and polished feel to the user.
If you have more quastions about UI, plz watch this turtorial, it helped me a lot to understand the basics.
I can't get my button to change the scenes.
Trying to change scene when a UI button is clicked. I have a script called SceneRemote.cs that just does this:
using UnityEngine;
using UnityEngine.SceneManagement;
using System.Collections;
public class SceneRemote : MonoBehaviour
{
public void Change(string scene)
{
SceneManager.LoadScene(scene);
}
}
This script was then added to the Canvas holding the button, then I pulled in that Canvas into the OnClick() component where it is set up in the following structure:
Runtime Only -> Canvas and SceneRemote.Change -> Scene1
Note: Both scenes have been added to the build settings.
0 -- CHECK your canvas is intact: MUST have EventSystem, MUST usually be 'scale with screen size'
1 -- CLICK "+" on the drag area of the button
2 -- DRAG to the button (in your case drag the holder of SceneRemote)
3 -- SELECT the correct function ("Change" for you)
4 -- ENTER the argument if any (in your case there will be a text field where you will enter the scene name)
5 -- LOG add a Debug.Log("yo... "+scene) statement inside your routine Change. Play, click button, look at console
6 -- SCREENSHOT the same as the one above and edit in to your question
I created a game in c# for windows 8 and everything works like it is supposed to but after the user plays the game the score is displayed in a text box and it is supposed to also display on the following screen displaying the score and text and thumbs up images depending on how high it is.
The problem is the score isn't being carried over. The code I use gets the constructor from the game page then the score, the code is below:
Var arcade = new arcadeMode();
ScoreNum.text = arcade.score.toString();
//then sample code to display images based on score
if(arcade.score < 100)
{
Usermessage.text = "try again.";
Thumbdown.visibility = visibility.visible;
}
I think my problem is actually getting the saved score from the game because it must only be getting the textbox value from before the game started. Not really sure how to save the score without a database though. Any suggestions on how to go about this?
It looks like ArcadeMode is essentially your data model, but gets the constructor from the game page doesn't really have meaning. Assuming you have an instance of ArcadeMode on your game page and it includes the actual score, with the code above, you've just created a brand new second instance of that same class with the score initialized to 0.
One quick way, since it appears it's just a simple piece of data you want to pass, is use the second parameter of Navigate. Wherever it is you navigate to the thumbs-up page, add the current value of your 'score' variable as the second parameter, for example:
Frame.Navigate(typeof(ThumbsUpPage), score)
Then in the OnNavigatedTo event of ThumbsUpPage you can access the value you passed via the Parameter property of the argument, for example:
protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
String score = e.Parameter;
...
Another option is to give the ArcadeMode class instance a larger scope (i.e., not make it a variable on the main page). You could make it a member of the App class, for instance, and then it would be available for all the pages in your app.