- Home /
How to detect interactable state change in selectable objects (Buttons, etc.)
Pitching this one out there to the community - let's say, for the sake of argument here, I have a button on-screen.
I have some additional behaviors that I'd like to trigger upon changing a selectable GUI control's interactable state. I don't want to disable the button object, but I do want to change it's text color, for example, when the button becomes non-interactable.
I understand that I could just test to see if the interactable boolean has changed within the update loop, and trigger off of that - but I really don't think this would be the optimal way to do so, considering multiple buttons would likely be pounding the crap out of the Update loop in the process, needlessly checking for its state change.
I also understand that I could write a custom function that performs these actions in addition to setting that variable - but I was hoping to have it more reactive to the state, rather than driving it.
Does anyone have some suggestions or tips on how I might achieve this sort of detect-and-react approach? Or should I just begin writing my custom function to handle this?
Thanks for any suggestions!
Answer by dkjunior · Oct 13, 2015 at 03:26 AM
Not exactly the answer to your question, but you can customize button's appearance in non-interactable state using Transitions. Check them out in the inspector. If color change is all you need than it's pretty simple - keep the default Color Tint transition type and change the Disabled color property (which corresponds to non-interactable state). You can customize appearance even further by selecting Sprite Swap or Animation transition types.
@dkjunior - I understand the color tint/sprite state change stuff - this was more speaking to a custom composition of a GUI button that includes labels & icons, and when the button turns interactable off, then the idea would be to change the label color to a disabled color as well as changing the icon asset's alpha or something ...
Was just hoping there'd be an integrated event or function that triggers upon that interactable boolean's value change.
Thanks for the input.
There's a way to do that through extending the classes you want. Warning: in many cases it may be an overkill, but if you want to add substantial amount of custom logic, this may be an elegant way to go.
public class $$anonymous$$yButton : Button {
public event Action<bool> InteractableChanged;
public bool $$anonymous$$yInteractable
{
get
{
return interactable;
}
set
{
if (value != interactable)
{
interactable = value;
OnInteractableChanged(interactable);
}
}
}
protected void OnInteractableChanged(bool interactable)
{
if (InteractableChanged != null)
InteractableChanged (interactable);
}
}
Obviously, you would want to choose better names than $$anonymous$$yButton & $$anonymous$$yInteractable.
Nice - good point! Thanks, this is definitely something I'll investigate!
Answer by free-divbyzero · Apr 06, 2016 at 11:41 AM
Hi.
Try this.
public class MyButton : Button
{
protected override void DoStateTransition(SelectionState state, bool instant)
{
if (state == SelectionState.Disabled)
{
}
else if (state == SelectionState.Normal)
{
}
}
Answer by JoshuaMcKenzie · Oct 13, 2015 at 11:30 AM
typical rule of thumb I go by is that other game objects shouldn't know that a UI even exists (unless its also a UI and only if there's no better way). This rule of thumb is there to help prevent deadlocks in logic behavior, or some nasty hard to trace bugs.
For example, I wouldn't have my AudioController ask if the UI mute button is turned on or off, I do it the other way around... my AudioController tracks the mute state and the button checks what state the mute is. If muted show the unmute icon, otherwise show the mute icon. when the button is pressed it triggers a call to the AudioController to change its state via a public function in the AudioController. The AudioController has no knowledge whatsoever that a mute button even exists, nor does it need to.
so to answer your question, I would instead have whatever you have checking to state of the button check the source of what ever is affecting that button's state. if the logic you have that determines the state of the button self contained in the script connected to the button, I would push it out to an external controller class.
And in this external class set the state change to an event so what ever needs to work of it can just subscribe themselves to the event. This will further decouple your code (classes know less about other classes and thus are less prone to break from code changes) and keeps it fast (instead of having to ask that class every frame what it's state is, the class will simply tell all that cares to listen when it's state has changed).
I don't know exactly what you need to track the button state, but this is how I would code it, going back to my previous example....
public class AudioController : Monobehavior
{
public delegate void MuteEvent();
public static event MuteEvent OnMute;
public static AudioController instance;
private bool _isMuted = false;
public bool IsMuted
{
get { return _isMuted;}
set
{
_isMuted = value;
// only call if someone is listening,
// otherwise you'll get a crash
if(OnMute!=null)
{
OnMute();
}
}
}
void Awake()
{
// set the static reference so that other
// scripts don't have to do any complex searching
instance = this;
}
}
public class MuteButtonScript : Monobehavior
{
public Button btn;
public Sprite muteSprite;
public Sprite unmuteSprite;
void Awake()
{
Button btn = GetComponent<Button>();
}
void OnEnable()
{
//subscribe to the AudioController's On Mute event
AudioController.OnMute += OnMute;
}
void OnDisable()
{
//unsubscribe from the AudioController's On Mute event
AudioController.OnMute -= OnMute;
}
// this function will always run whenever the class is subscribed
// to the AudioController and the AudioController runs "OnMute()"
void OnMute()
{
//update the button sprite depending on the AudioController's Mute State
btn.image.sprite = AudioController.instance.IsMuted ? muteSprite : unmuteSprite;
}
}
here the button's image is changing state depending on when the AudioController issues the OnMute event. So instead of having whatever you're planning to listen to this button, have it listen to the event from the audio controller. Neither the AudioController nor the MuteButtonScript needs to know what this script is, which makes it very easy and lightweight to extend functionality.
by using events you can have other classes (subscribers) attach themselves to an event that is issued by a single source (provider) and the subscribers don't have to constantly check every update loop, the provider simply tells the subscribers when the event happens.
so whatver event that is causing your button to become non-interactible have your other UI stuff also listen to that same event and have then change their color based on the same decision making
This is where my train of thought was taking me, I figured I'd make a custom function attached to the GUI object itself that another function/object can simply call (or even use the event/delegate system) to trigger those types of changes.
The idea was that the font color, and image assets attached to the GUI button would flip to their "disabled" states under those circumstances.
I was just fishing out to see if there was a function or something that triggers on change of the "interactable" boolean state (kind of like the OnEnable/OnDisable functions).
Thanks for the input though - I think I'll likely go the route of making a function that is present on most GUI objects that can simply be called to handle these nuances.