Search Unity

Best Practices for implementing buttons

Discussion in 'Project Tiny' started by reallyhexln, Mar 25, 2019.

  1. reallyhexln

    reallyhexln

    Joined:
    Jun 18, 2018
    Posts:
    69
    Hi, guys.
    I'm know there is a component MouseInteraction to determine is the mouse pointer over / click some component.
    I'm know about component CustomButton shipped with MatchThree game example.
    I'm know about methods family UIControlsService.addOnClickCallback.
    How to properly work with these things? How to separate the logic of button behaviour from the logic of game behaviour?
    Should I use events? For example, I can create ButtonsSystem to register all buttons, their handlers for Click / Over / Out / Normal events, then dispatch such events when it's needed. Is it a valid approach?
    Are there any existing solutions for that?
     
    AAK_Lebanon likes this.
  2. AAK_Lebanon

    AAK_Lebanon

    Joined:
    May 30, 2015
    Posts:
    59
    I have exactly the same question, I found today accidentally something called UIControlService.addOnClickCallback, and I played with it and I wrote this code:

    Code (JavaScript):
    1.   @ut.executeAfter(ut.Shared.InputFence)
    2.     export class MySystem extends ut.ComponentSystem {
    3.         OnUpdate(): void {
    4.             let btn = this.world.getEntityByName("Entity");
    5.             if (this.world.exists(btn)) {
    6.  
    7.                 ut.UIControls.UIControlsService.addOnEnterCallback(this.world, btn, () => console.log("hi there"));
    8.             }
    9.         }
    10.     }
    however, it seems that it does nothing! I can't see any message on the browser console and I have no idea about how to use these stuff...
     
  3. reallyhexln

    reallyhexln

    Joined:
    Jun 18, 2018
    Posts:
    69
    AAK_Lebanon, I think, you should try to add UTiny.UIControls.MouseInteraction component to your entity.
     
    AAK_Lebanon likes this.
  4. AAK_Lebanon

    AAK_Lebanon

    Joined:
    May 30, 2015
    Posts:
    59
    yeah actually it is worked !! but I also added my entity under a canvas...

    what I hate about ECS for now (and Tiny project in general) is the lack of good documentation, we should test and play with things and that take our valuable time...
     
  5. Deleted User

    Deleted User

    Guest

    Currently, I prefer MouseInteraction with a simple a component like "PlayButton", "ToggleAudioButton", etc.
    The code will be more "ECS like" than using callbacks, and you will keep using the same pattern than the rest of the game
     
  6. reallyhexln

    reallyhexln

    Joined:
    Jun 18, 2018
    Posts:
    69
    Thank you for your response.
    Do you use "PlayButton" and "ToggleAudioButton" just as a tag without any properties to mark some entity?

    Currently, I'm also use your approach for case when my button has some global meaning.

    When the button belongs to some reusable component, I create parent component that contains EntityReference to my buttons, like "NextButton", "PrevButton", etc, and attach to all buttons MouseInteraction component.

    In code, it is looks like:

    Code (JavaScript):
    1. this.world.forEach([game.Dialog], (dialog) => {
    2.     this.world.usingComponentData(dialog.nextButton, [ut.UIControls.MouseInteraction], (mouse) => {
    3.         let isCursorOverMouse: boolean = mouse.over;
    4.     });
    5. });
    6.  
    I'm not sure this is a best solution but it's currently working for me.
     
    Deleted User likes this.
  7. Deleted User

    Deleted User

    Guest

    Yes, those components are just a tag.

    But sometimes I use the same approach as you.
    Usually with simple UIs like a "Pause Menu" who just have a Continue and Quit button and the actions have a specific context.

    Nice to see someone thinking the same as me hehe
     
    reallyhexln likes this.
  8. Deleted User

    Deleted User

    Guest

    The only thing i like to add, i'm using this method to detect just a click, since checking only for "mouse.over" will fire the action over and over

    Code (JavaScript):
    1.  
    2. // The user was just ended his input?
    3. if (!Input.getMouseButtonUp(0) && (Input.touchCount() == 0 || Input.getTouch(0).phase != TouchState.Began))
    4.    return
    5.  
    6. this.world.forEach([PauseGameButton, MouseInteraction], (_pauseGame, mouse) => {
    7.    if (mouse.over)
    8.       GameService.setPauseOn()
    9. })
    10.  
     
    Last edited by a moderator: Mar 27, 2019
    reallyhexln likes this.
  9. reallyhexln

    reallyhexln

    Joined:
    Jun 18, 2018
    Posts:
    69
    I agree with you.

    I have reached the same behavior, but I just copy CustomButtonSystem from Match3 example, and made some changes needed for me.

    Code (JavaScript):
    1. public OnUpdate(): void {
    2.     let deltaTime: number = this.scheduler.deltaTime();
    3.     this.world.forEach([game.Button, ut.UIControls.MouseInteraction, ut.Entity],
    4.         (button, mouseInteraction, entity) => {
    5.             button.isPressed = mouseInteraction.down && button.isInteractable;
    6.             button.justDown = !button.lastDown && mouseInteraction.down && button.isInteractable;
    7.             button.justUp = button.lastDown && !mouseInteraction.down && button.isInteractable;
    8.             button.justClicked = mouseInteraction.clicked && button.isInteractable;
    9.  
    10.             if (button.isPressed) {
    11.                 button.timePressed += deltaTime;
    12.             }
    13.  
    14.             if (button.justDown || button.justUp || mouseInteraction.over !== button.isPointerOver ||
    15.                 button.lastIsInteractable !== button.isInteractable) {
    16.                 button.lastIsInteractable = button.isInteractable;
    17.  
    18.                 if (!button.isInteractable && button) {
    19.                     this.setButtonDisabledState(entity);
    20.                 } else if (mouseInteraction.over && button.isPressed) {
    21.                     this.setButtonDownState(entity);
    22.                 } else if (mouseInteraction.over) {
    23.                     this.setButtonOverState(entity);
    24.                 } else {
    25.                     this.setButtonNormalState(entity);
    26.                 }
    27.             }
    28.  
    29.             button.isPointerOver = mouseInteraction.over;
    30.             button.lastDown = mouseInteraction.down;
    31.             button.lastOver = mouseInteraction.over;
    32.         });
    33. }
    34.  
    Here is the game.Button component is almost the CustomButton from Match3 example with some minor changes.

    And, the usage is:

    Code (JavaScript):
    1. this.world.forEach([game.Button, game.OkButton], (button, tag) => {
    2.     if (button.justDown) {
    3.         this.goToNextLevel();
    4.     }
    5. });
    But I can see, you use touch API, and perhaps this is even the best solution than my, because I don't know how MouseInteraction component working inside.
     
  10. Deleted User

    Deleted User

    Guest

    Wow, this is interesting...
    I had taken a look at Match3 and other samples while learning the basics, but not had paid attention to that.

    I had an issue with mouseInteraction.clicked because it works fine on desktop, but not in mobile.
    This is why I check for touch begin
    Click using callback works fine, but I would recommend avoiding mouseInteraction.click for future mobile compatibility.
     
    reallyhexln likes this.
  11. reallyhexln

    reallyhexln

    Joined:
    Jun 18, 2018
    Posts:
    69
    Wow, It's very interesting for me, because I have planned to publish my game in Android apk inside of WebView container.
    Thank you for information.