Best way to organize OnClick events for Unity buttons? - unity3d

enter image description here
Unity has a component called Button as part of its UI system which you can use to subscribe on-click events to it through the inspector which is incredibly useful.
However, when projects get larger, I run into trouble in many situations:
events subscribed this way in the inspector are not rearrange-able which makes buttons that have lots of events difficult to manage
changing the contents of the scripts used for events can cause the button to not recognize a function that was used for an event which means you have to re-reference it
if anything happens to the GameObject or prefab that stores the Button component such as it getting corrupt then all your events that were serialized onto the button would be wiped and you would need to re-reference all of them
the above points make debugging very very difficult
What are some ways I can work around the problems I've listed above?

Inject event functions inside the code:
public Button playButton; // set button in inspector
public void Start()
{
playButton.onClick.AddListener(() =>
{
transform.position = point1;
// do something..
});
}

Related

What's the simplest way to handle mouse click events for children of a GameObject in Unity?

I have created an empty GameObject (named FileCabinet) and assigned 4 prefabs as children (CabinetFrame, Drawer01, Drawer02, and Drawer03).
I would like to be able to click on any of the drawers to open them, but everything that I have tried thus far returns the parent GameObject FileCabinet as the item clicked rather than any of the children.
What's the best/simplest approach to call a method when any of the drawer child objects are clicked?
Update:
#LoïcLeGrosFrère provided a solution that ultimately worked. I initially had problems with events not firing because my UI was blocking raycasts to my 3D file cabinet object. I resolved the issue by adding a Canvas Group component to my Canvas and unchecking Blocks Raycasts, but this then disabled all my UI components. I then added Canvas Group components to individual UI panels and checked Ignore Parent Groups. I'm not sure if this is the correct way to handle the raycast conflict but it did work for me.
The simplest approach I see is:
Add a Physics Raycaster component to your camera.
Add a Event System to your scene, with a Input System UI Input Module.
Then, add this script to your drawers:
using UnityEngine;
using UnityEngine.EventSystems;
public class Drawer : MonoBehaviour, IPointerClickHandler
{
public void OnPointerClick(PointerEventData eventData)
{
Debug.Log($"Open {gameObject.name}.");
}
}

How do you automatically focus on an inputfield when opening/activating the UI in Unity3d?

I am making a game where you must open or activate the UI with the space bar. Now, this works perfectly fine, but it is pretty annoying that every time you open the UI you must click on the inputfield to write in it. Is there any way around this? So is there a way to open or activate the UI without having to click on the field to be able to write in it?
I looked for YouTube videos and tried to find similar problems in other forums, but wasn't able to find a script, nor was I able to find some Unity settings to do so.
You could use e.g.
private class SelectOnEnable : MonoBehaviour
{
private void OnEnable()
{
EventSystem.current.SetSelectedGameObject(null);
EventSystem.current.SetSelectedGameObject(gameObject);
}
}
and attach it to whatever object that should become the selected one everytime it is enabled. See EventSystem.SetSelectedGameObject
Can't test it right now but it might still require the User to hit Enter in order to also actually set the Input field into edit mode. The upper line only sets it as selected UI element (similar to using TAB in a browser).
Otherwise I think you would go through
yourInputField.DeactivateInputField()
yourInputField.ActivateInputField();
to directly set it active. See InputField.ActivateInputField. Might have to do both in combination - again can't test right now ;)
Thank you very much, derHugo! Everything works like a charm now! You saved me a lot of time. Referring to your last comment, I used both of them, and it seems to work very well for me. Here is the code I used:
`private void OnEnable()
{
EventSystem.current.SetSelectedGameObject(gameObject);
GameManager.GetComponent().inputFieldInMainUi.ActivateInputField();
EventSystem.current.SetSelectedGameObject(null);
GameManager.GetComponent<InputFieldComparision>().inputFieldInMainUi.DeactivateInputField();
}`

No function dropdown in AnimationEvent in Unity

I try to handle some events during animations, but everywhere I look, every tutorial have access to AnimatorEvent Inspector like this:
A nice simple field, where you can select a function, I want this!
But instead of this, I always getting this sick 5 fields view, and don't have any idea how to handle animation event in this case!
I tried to create function test() with debug log, but it didn't work anyway. Why I can't get access to this simple window where I can choose an function?
You will need to add this animation into a State in Animator Controller (via Animator Window).
In the object which contains Animator component, attach your script component to it.
Open the Animation window, select the object above, you will see a dropdown of animations (top left) which Animator Controller of this object contains. Choose and add event to the animation you want to.
Select the event in Animation Window, in the Inspector, you should see the dropdown of public functions of your script component attached above.
Answer by Aluminium18
Sometimes it doesn't work even if you do all things according to tutorials or advices in internet. I often leave Unity editor launched for a long time without any interactions with it. After several gibernations and several days it can get buggy - various errors appear, you can't see some functions from scripts and so on. So, just reloading the Unity editor solves many of such issues for me. And it continues to happen so for several years no matter what Unity version you have. I tried versions from 2019.x.x to 2022.x.x - all this time Unity behaves itself the same.

Back and volume buttons not working when app is resumed in certain conditions

I'm starting a new activity to display a leaderboard in my Android App.
When the leaderboard activity is displayed, and the user presses the home key and then resumes the app (so the leaderboard activity comes up again), and then navigates back to the main activity by pressing the back key, the back key and volume keys stop working).
I've attempted to override onBackPressed in my activity class. I can confirm that when this problem occurs, onBackPressed is not called (When back / volume is working, pressing back does trigger onBackPressed).
Normally I get a message in LogCat when I press the back key 'Unimplemented WebView method onKeyDown called from android.webkit.WebView,onKeyDown(WebView.java.2178)' - Again I can confirm that this message doesn't come up when the back key isn't working
Armed with the above information, I can only assume that it's something to do with the View not having focus or something along those lines. I would point out that touch input on the screen itself does work. It just seems to be the back and volume keys/buttons that have no effect.
It's an openGL ES 2.0 app so in my onPause() I'm calling view.onPause(); and in onResume, I'm calling view.onResume();
I really have no idea what's going on and I've been on this for 3 days straight so if anyone can point me in the right direction, that would be great.
If the user comes out of the leaderboard and back into the main app before they press the home key, then everything is OK, it's just if the home key is pressed while the leaderboard activity is displayed as described above.
So when I'm at the point where the back / volume keys aren't working, if I click my scores button and fire up the leaderboard activity again, they work. On returning to my activity, they stop working again.
Not sure if this is relevant at all but the following shows how I'm starting my leaderboard activity:
if (scoresButtonPresses){
displayLeaderBoard();
}
void displayLeaderBoard(){
//Display the leaderboard if already signed in
if (checkSignedIn()){
startActivityForResult(Games.Leaderboards.getLeaderboardIntent(getApiClient(), leaderboardID), 1);
}
//if not already connected, then set flag and connect to play services before displaying leaderbaord
else{
signInAction=SHOW_LEADERBOARD;
getGameHelper().beginUserInitiatedSignIn();
}
}
#Override
public void onSignInSucceeded() {
//If the flag is set, then display the leaderboard
if (signInAction==DISPLAY_LEADERBOARD){
startActivityForResult(Games.Leaderboards.getLeaderboardIntent(getApiClient(), leaderboardID), 1);
}
//Otherwise, reset the flag and take no action
else {signInAction=NO_ACTION;}
}
This is driving me crazy so any help or even a nudge in the right direction would be very much appreciated!!
Edit
After much testing I have learned a couple of things:
If I remove the view.onPause() && view.onResume() the problem seems to go away. So this appears to be something to do with the way key events are captured by the view. Pausing and resuming seems to mess something up.
I have also tried removing the view.onPause() and view.onResume() as above, but instead, putting in View.setVisibility(View.GONE); and then making it visible again in public void onWindowFocusChanged(boolean hasFocus). Again, I get the same problem. Interestingly, when I open the leaderboard, as expected the view's staus is 'gone' then hitting the home key and running the app again, it's set back to 'visible' - I don't understand this behaviour but I'll ask another question for that.
Lastly, and this I find really odd. If I put my app back to as I had it, then after hitting the home key and relaunching the app via Eclipse (and I can do this multiple times) the problem doesn't seem to occur. So in that respect, it appears to be something to do with touching the screen.
Edit
It appears as though this isn't limited to my app. I've tested this on a couple of other apps from the Play store and get the same result.
One app clearly uses a single activity model like mine and the back and volume stop working throughout the app.
The other may use a different activity for it menu and game. When I test on this app, the back/volume breaks but if you start a game (therefore a second activity), the back key starts working again, even when you return to the first activity (recreated this activity?!)
So maybe I can get around this by ensuring the activity is recreated? I thought i was already but maybe I'm not getting something. Maybe it's something to do with the stack.........
I have no solution to your problem but some additional insights since I'm facing the same bug/problem with the leaderboards and the back button (and it's driving me crazy).
I'm using a single activity with fragments from which I login to Google Play, open Leaderboards and Achievements in the exact way you are doing it (startActivityForResult). Always when I come back from any Google Play Service activity my back button is broken. Sometimes it's even enough if I try to login to Google Play for it to break, meaning the login popup sometimes breaks the back button (but not always).
Some more insights which might help to solve the problem:
I'm not using OpenGL, so it's not related to this - I'm just using a single activity and fragments
It's also breaking for me when I just use the back button for going back from any Google Play Activity (I don't even need to go via the home key, just normal back is enough).
When the back button is broken the Activity is not receiving any Key Events (OnKeyDown, OnKeyUp, dispatchKeyEvent) are all not working, although using the activity and touching it fully works. My guess is that some view is catching the events...
Edit:
I also tried checking activity.getCurrentFocus() and activity.hasWindowFocus() as well as the parent and context classes of activity.getCurrentFocus() to see if there is any difference between when it's working and when it's not - and there isn't any difference...
Edit:
It seems that a simple call to View.requestFocus() is fixing the problem. For my single activity (using fragments implementation) I have now added:
#Override
public void onResume(){
super.onResume();
Fragment fragment = getFragmentManager().findFragmentById(R.id.fragmentContainer);
fragment.getView().requestFocus();
}
and this seems to be fixing the problem. Sill have to do long term checking if it's always fixed. In your OpenGL implementation I guess you will have to get the main view of the activity in some other way since you are not using fragments.
I know it's pretty old question, but for clarification: accepted answer is correct, but incomplete. Focus requesting should be done in onResume, onActivityResult, onSignInSucceeded and onSignInFailed. In first two you have! to call super. Some code:
private void requestFocusAfterGooglePlay(){
if(gimmeFocus==null)
return;
View currFocus=getCurrentFocus();
gimmeFocus.setFocusableInTouchMode(true);
gimmeFocus.setFocusable(true);
gimmeFocus.requestFocus();
gimmeFocus.setFocusableInTouchMode(false);
gimmeFocus.setFocusable(false);
if(currFocus!=null)
currFocus.requestFocus();
}
#Override
public void onSignInFailed() {
requestFocusAfterGooglePlay();
}
#Override
public void onSignInSucceeded() {
requestFocusAfterGooglePlay();
}
#Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
requestFocusAfterGooglePlay();
super.onActivityResult(requestCode, resultCode, data);
}
#Override
public void onResume(){
super.onResume();
requestFocusAfterGooglePlay();
}
in my case gimmeFocus is a dummy View created as below:
<View
android:id="#+id/gimme_focus"
android:layout_width="0px"
android:layout_height="0px"
android:focusable="false"
android:focusableInTouchMode="false"/>
I have dynamic layout so I can't never be sure that any view exists in my layout, so I've created this dummy View, it might be also added programmatically. You might choose your fixed View for gaining focus, but rembember that changing focus might change view's look (drawable state).

Best Practices GWT Event Handling

I have a question regarding the event handling on client side in GWT.
In our application we have a quite complex structure of different modules and pages which are communicating via the gwt eventbus on client side. Now the amount of events is growing to fast for my opinion. E.g. I am opening a popup I need:
An event for opening the popup
An event for asking some data within the client
An event for getting back the data and fill in the dialog
An event for closing the popup
An event for handling the save Button
Am I thinking a little bit to complicated or missing something in the EventBus implementation? I just wanted to have some feedback out of the community as you are facing the same issues.
For what it's worth, I have lots of events and more growing. And yes, I wonder if I can do with less, but when I skip an event and link elements directly, I regret it.
Here's an example that I just fixed up yesterday. I have a DataGrid widget. I also support re-ordering of columns, hiding of columns, re-sizing columns, and coloring columns with a popup dialog. You click on a configure button, and a popup with the columns listed shows, and the user can click checkboxes to show or hide columns, click on a Move Up / Move Down button to re-order columns, and so on. Hit Apply on the popup and the popup disappears and the DataGrid re-configures.
Except that it didn't. You'd click on Apply and the popup would just sit there, the user would wonder what was going on, the DataGrid would re-configure underneath, and then the popup would go away. We're only talking a short amount of time -- maybe a second or a bit more -- but it was so so noticeable. Why was it happening? Because I got lazy and tied the popup directly to the configure button, and the Apply button directly to the DataGrid. You'd hit Apply, for example, and the call would be made to the DataGrid with the new configuration information. Only when the call returned would the popup would be torn down.
I knew it was bad when I did it, but I was being lazy. So I took the 20 minutes I needed to write up two messages and associated handlers in my mediator singleton. One message is issued by the DataGrid to start the configuration dialog, and one is issued by the popup to configure the DataGrid. Now the widgets are de-coupled, and the performance is much snappier. There is no sense of "stickiness".
Now to your example, can you not combine (1) and (2)? And also (3), (4), and (5)? When the user clicks the configure button on my app, the event carries with it the current configuration information (including a reference to the DataGrid that originated the request). You can call this information the "payload". When the user clicks the Apply button on the popup, the event payload includes all the new configuration information (including a reference to that original target DataGrid) that the event handler feeds to the target DataGrid when the event is handled. Two events -- one to kick off the configuration and one to apply the end result.
Yes there are a plethora of events in any app that does something interesting, but events can carry a lot of information, so I would look at whether your event organization is too fractured.
As a extra bit, here is the code I use. I shamelessly copied elements of this pattern from one of Google's examples.
The user can ask for help using a menu item:
#UiField
MenuItem help;
help.setCommand(new Command() {
#Override
public void execute() {
BagOfState.getInstance().getCommonEventBus().fireEvent(new MenuHelpEvent());
}
});
For the event (in this case, the event fired when the user clicks on the Help menu item):
public class MenuHelpEvent extends GwtEvent<MenuHelpEvent.Handler> {
private static final Type<Handler> TYPE = new Type<Handler>();
public interface Handler extends EventHandler {
void doMenuHelp();
}
#Override
public GwtEvent.Type<Handler> getAssociatedType() {
return TYPE;
}
#Override
protected void dispatch(Handler handler) {
handler.doMenuHelp();
}
public static HandlerRegistration register(EventBus eventBus, Handler handler) {
return eventBus.addHandler(TYPE, handler);
}
}
I have a singleton called Mediator in which ALL events are registered:
MenuHelpEvent.register(BagOfState.getInstance().getCommonEventBus(),
new MenuHelpEvent.Handler() {
#Override
public void doMenuHelp() {
new MenuHelp().execute();
}
});
Every event is mated with a Command object to do the work:
public class MenuHelp implements Command {
#Override
public void execute() {
new InfoMessage(BagOfState.APP_MSG.unimplementedFeatureCaption())
.setTextAndCenter(BagOfState.APP_MSG.unimplementedFeature());
}
}
Everything is decoupled. The menu widget is bound to a command that executes and then completes. The command fires the event on the bus then completes. The event fires off the execution of a Command and the completes. The Command shows the popup help panel (in this case, an "unimplemented" message to the user -- yeah, I'll get to it soon). Every interaction with a user's input is handled extremely quickly and resolves. It can kick off a series of events to perform a long action, but never tying up the GUI to do so. And of course, since the elements are decoupled, I can call the same elements in other places (for instance, call the help Command through a button push as well as a menu item).