Looking at some of the answers in the Unity forums and Q&A site, the answers for how to make an invisible button do not work because taking away the image affiliated with the button makes it not work.
How do you get around this and keep the invisible property while allowing the button to actually work?
This is one of those weird things about Unity...
100% of real-world projects need this, but Unity forgot to do it.
Short version:
You need Touchable.cs in every Unity project:
// file Touchable.cs
// Correctly backfills the missing Touchable concept in Unity.UI's OO chain.
using UnityEngine;
using UnityEngine.UI;
#if UNITY_EDITOR
using UnityEditor;
[CustomEditor(typeof(Touchable))]
public class Touchable_Editor : Editor
{ public override void OnInspectorGUI(){} }
#endif
public class Touchable:Text
{ protected override void Awake() { base.Awake();} }
Use Unity's ordinary 'Create Button' editor function
As you know, the editor function adds two components for you automatically. One is a Text and one is an Image...
Simply delete them both
Drop the above script Touchable.cs on the Button
You are done. That's all there is to it.
It cannot "decay" with Unity upgrades.
You can actually "buttonize" anything in .UI by dropping Touchable on top of it.
Never again "add a transparent Image" to make a button.
Unity forgot to abstract a "touchable" concept in the OO chain.
So, us developers have to make our own Touchable class "from" Unity's classes.
This is a classic "backfilling" problem in OO.
When "backfilling" the only issue is that: it must be perfectly auto-maintaining. There is only one good solution, Touchable.cs, which everyone uses.
So in all real-world Unity projects a button looks like this:
ONE You have Unity's Button.cs
TWO you have to add Touchable.cs
Some teams make an editor function "Create Better Button" which simply makes a game object, with, Button.cs + Touchable.cs.
Important tip...
Say you may have a very complex UI panel. So it resizes or even has an animation.
In fact, you can just drop "Button+Touchable" on to anything like that, and it will work.
Just set the Button+Touchable so as to expand to fill the parent. That's all there is to it.
In this example image, "resume" and "quit" could be anything. (An animation, a complicated panel with many parts, text, sprites, something invisible, a stack - anything.)
In all cases, just drop a Button+Touchable underneath and you have a flawless button.
In fact: this approach is so simple, you'll probably use it for even simple cases.
Say your button is a trivial image. It's much easier to just have an image, and then drop a Button+Touchable on it. (Rather than use the confusing and problematic "Button" function in the editor.)
Understanding the situation...
1) Unity's Button.cs class is fantastic.
2) But the editor function "make a Button" is garbage...
3) It makes an "upside down" button,
4) i.e., it puts a text/image under Button.cs
5) "Button-ness" is something you should be able to add to anything at all. This is precisely how it works with Button+Touchable.
6) So - quite simply -
1. Have anything you want. Text, image, panel, invisible, animation - whatever.
2. Drop Button+Touchable on it - you're done.
That's how everyone does all buttons in Unity!
Historic credit: I believe Unity forum user "signalZak" was the first to think this out many, many years ago!
As a possible improvement to Fattie's answer, changing Touchable's base class to Graphic and overriding protected void UpdateGeometry() seems to work quite nicely white reducing the (admittedly minor) overhead associated with Text.
public class Touchable:Graphic
{
protected override void UpdateGeometry() { }
}
My first solution was to enable and disable the components like below:
void showButton(Button buttonToShow, bool show)
{
Image bImage = buttonToShow.GetComponent<Image>();
Text bText = buttonToShow.GetComponentInChildren<Text>(); //Text is a child of the Button
if (bImage != null)
{
bImage.enabled = show;
}
if (bText != null)
{
bText.enabled = show;
}
}
but that didn't work. If the button's image and text components are both disabled, the button click event will NOT fire. One of them MUST be enabled in able for click events to be sent.
The solution is to set the alpha of both the image and text components to 0 to hide and to 1 to show again. They will be hidden but not disabled and click events will work.
public Button button;
void Start()
{
//Show Button
showButton(button, true);
//Hide Button
//showButton(button, false);
}
void showButton(Button buttonToShow, bool show)
{
Image bImage = buttonToShow.GetComponent<Image>();
Text bText = buttonToShow.GetComponentInChildren<Text>(); //Text is a child of the Button
if (bImage != null)
{
Color tempColor = bImage.color;
if (show)
{
tempColor.a = 1f; //Show
bImage.color = tempColor;
}
else
{
tempColor.a = 0f; //Hide
bImage.color = tempColor;
}
}
if (bText != null)
{
Color tempColor = bText.color;
if (show)
{
tempColor.a = 1f; //Show
bText.color = tempColor;
}
else
{
tempColor.a = 0f; //Hide
bText.color = tempColor;
}
}
}
I fired up Gimp (that free coder graphic tool). Created new image (any size, I chose 10 pix x 10 pix), selected from advanced (in create dialog) that it's backgroud should be transparent. Saved the file. Exported it as png with save backgroud color selected. Dragged it into Unity as sprite. Put that to the button graphic. Disbaled the text-component of the button. No code required ... just don't draw anything while in Gimp (that was the hardest part).
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 have four GameObjects in the game I am making and when I click on each one a dialogue box opens.
My problem is that I am using OnMouseDown and the first one always need to be clicked twice. It happens just on the OnMouseDown, I have buttons on the game that work just fine.
public static GameObject selectedClient;
public bool isBuying;
private Manager manager;
private void Start()
{
manager = GameObject.FindObjectOfType(typeof(Manager)) as Manager;
}
private void OnMouseDown()
{
if (isBuying == true)
{
selectedClient = this.gameObject;
manager.Talk();
}
else if (isBuying == false)
{
manager.NotTalk();
}
}
The Talk() and NotTalk() are as follows:
public void Talk()
{
dialogueBox.SetActive(true);
Time.timeScale = 0f;
actualClient = Client.selectedClient;
}
public void NotTalk()
{
dialogueBox.SetActive(false);
Time.timeScale = 1f;
}
Someone knows why this happens?
Thanks in advance.
Assuming manager.Talk() opens the dialog, perhaps isBuying is false initially, and is toggled when you click once?
Edit: User updated question with Talk and NotTalk methods.
Since your say that initially, only the time stops, but the dialogueBox doesn't show, by any chance, is this because it isn't populated, or correctly initialized? Can you show is code of what your expect to show up?
It may just be that dialogueBox has nothing to display, and therefore, doesn't, however, by the second click, it somehow it's correctly populated and can display.
If I understand correctly, it seems that OnMouseDown() and Talk() is being called successfully on the first mouse click, but a second mouse click is required to get the dialogueBox to show.
If dialogueBox.SetActive(true) is being called successfully, you could take a look at the Unity Scene Hierarchy window to see if it became active like you expect after the first mouse click. If it did, then you can examine that object to see why it's not appearing on screen. (As another poster mentioned, it may be related to dialogueBox initialization.)
Also, these questions might help us debug and figure out the issue:
What error do you get when you "click on one object and then on other"?
What happens if you click the buttons in a different order? If there are 4 buttons, is it the first button that is always affected, or the first button that you click?
In general, as other posters have suggested, using Debug.Log and Debug.Assert should help you narrow down the issue. Similarly, you can set breakpoints in the code and step through to see if the code is running like you expect. And as I mentioned in this post, the Unity Scene Hierarchy window is useful for debugging as well.
Add a Debug.log(isBuying); above (isBuying == true).
Then you'll know if the OnMouseDown() is being called in the first place and if it proceeds like you expect it to. After that, you should probably be able to fix it yourself, otherwise, please let me know what you found.
I've been struggling with IMGUI (Legacy and Editor GUI system) a lot.
Problem is I cannot get my head around this Instant way and all the different events.
I built a simple example to demonstrate one of the many frustration I'm having with this system.
bool toggleValue;
void OnGUI()
{
if (toggleValue = GUILayout.Toggle(toggleValue, "Toggle"))
{
EditorGUILayout.LabelField("This is a label");
}
}
Alright, so, in this very simple code, I have a native toggle that shows or not a label depending on it's state. This works perfectly.
Now, I'm writing a piece myself.
bool toggleValue;
void OnGUI()
{
if (toggleValue = Toggle(toggleValue, "Toggle"))
{
EditorGUILayout.LabelField("This is a label");
}
}
bool Toggle(bool state, string label)
{
GUILayout.Label(label, state ? EditorStyles.boldLabel : GUIStyle.none);
if (Event.current.type == EventType.MouseDown && Event.current.button == 0 && GUILayoutUtility.GetLastRect().Contains(Event.current.mousePosition))
return !state;
return state;
}
My goal with this one is to manually reproduce the behaviour of a toggle: My clickable label is bold when active and normal when disable. Similarly to the checkbox graphic of the native toggle. Then, if I catch a click on it's rect, I return the opposite of the state.
In my head, this is suppose to work. However, I'm aware that there's a lot going on with the OnGUI function like the events Repaint and Layout for which the layout needs to be consistent during a frame. Here the error is
ArgumentException: Getting control 1's position in a group with only 1 controls when doing MouseDown
but often also errors like this one
ArgumentException: GUILayout: Mismatched LayoutGroup.MouseDown
I know that these errors are when you change the content between Layout and Repaint. However I cannot find a fix for my simple control. So here is my question.
With the same arguments given to my function Toggle, what do I need to take care of so the toggle works?
I feel like if I have the answer to this, I might be able to understand the key to this system.
Thank you very much
Oh my god I can't believe it. The ONLY thing I was missing was
Event.current.Use();
When the control is clicked.
That's all! Nothing about the layout being changed between Layout and Repaint or anything of the sort.
So if you've got the same problem, just consume the event when it succeeded.
In case of a Unity mobile app, would it be good (at all) for performance, to deactivate the navigation through canvas objects like TextFields and Buttons?
When selecting e.g. a Button or TextField in the sceneview, the inspector shows the option "Navigation", which can be set to different things and can also be visualized. This feature is usually used to tab through input fields, like you would expect it on a website. On my mobile game I don't need this "tabbing". Would there be any sort of performance increase if I deactivated it everywhere?
Would deactivating this rather be a waste of time, or even make
performance worse for some obscure reason?
Usually there are 3 ways to show and hide UI. I will list them from worst to best.
1.Instantiate (Show), Destroy (Hide).
public GameObject uiPanelPrefab;
void Start()
{
//Show
Instantiate(uiPanelPrefab);
//Hide
Destroy(uiPanelPrefab);
}
This creates and destroys Objects each time. (Not Recommended).
2.Activate (Show) or Deactivate (Hide) the GameObject of that UI.
public GameObject uiPanelPrefab;
void Start()
{
//Show
uiPanelPrefab.SetActive(true);
//Hide
uiPanelPrefab.SetActive(false);
}
This creates garbage each time it is set to active and also cause quick freezes. You will notice this a lot when making VR apps. Fine on Desktops. Not good or recommended on mobile devices.
3.Enable (Show) or Disable (Hide) the Component of that UI.
public GameObject uiPanelPrefab;
void Start()
{
//Show
uiPanelPrefab.GetComponent<Image>().enabled = true;
//Hide
uiPanelPrefab.GetComponent<Image>().enabled = false;
}
This simply enables and disables the components. This method requires a custom function for each UI Control such as Button, Text, InputField as each Control has different components attached to them. This is the recommended method especially on mobile devices.
After long experiment with these, I came to conclusion that #3 is the best and should be used.The downside is that you have to make a function that will disable every components of each UI Control.
For example, the Button Control has more than 1 components. Simply disabling the Button component will not do it. You have to write a simple function that will disable Button, Image and Text components. Something like below:
void showButton(GameObject button, bool show, bool includeInactive = false)
{
Button bt = button.GetComponentInChildren<Button>(includeInactive);
bt.enabled = show;
Text txt = button.GetComponentInChildren<Text>(includeInactive);
txt.enabled = show;
Image img = button.GetComponentInChildren<Image>(includeInactive);
img.enabled = show;
}
Usage:
//Show
showButton(uiPanelPrefab, true);
//Hide
showButton(uiPanelPrefab, false);
I can confirm, that deactivating the navigation of UI elements has no performance influence. Behind the scenes there's just a handler, that gets triggered, when you hit "tab" on pc, or analogeously on mobile.