Search Unity

Empty Rect to catch clicks?

Discussion in 'UGUI & TextMesh Pro' started by Democide, Feb 3, 2016.

  1. Democide

    Democide

    Joined:
    Jan 29, 2013
    Posts:
    315
    What's the best way to build an empty, invisible rect that still catches clicks? So far I've been using the TEXT component without anything in it, just to get the click callbacks, but that seems terrible. Is there a better way?
     
  2. eses

    eses

    Joined:
    Feb 26, 2013
    Posts:
    2,637
    Empty Image? :)

    Is it any better? :)

    or use some class that derives from Graphic class, and implements some of click interfaces.

    I've just used empty images.
     
  3. Trav3l3r

    Trav3l3r

    Joined:
    Sep 16, 2014
    Posts:
    60
    Hard to say, i'd probably go with a Button without the Image component.
    Does 'best' mean, when it gives you the best performance or when it's the easiest to use?
     
  4. eses

    eses

    Joined:
    Feb 26, 2013
    Posts:
    2,637
    Trav3l3r:

    Image is enough, if you add Image, you add Image component, if you add Button, You add Image and Button... when only Image component is needed to capture clicks.

    If using custom script you only add your script, no need for Image component then.
     
  5. jinxed_byte

    jinxed_byte

    Joined:
    Mar 29, 2014
    Posts:
    17
    Whether or not a UI-object captures clicks depends on the "Raycast Target" option in the component.
     
  6. SimonDarksideJ

    SimonDarksideJ

    Joined:
    Jul 3, 2012
    Posts:
    1,689
    The bare minimum you need is a RectTransform, An graphic based component, a Script implementing the pointer handler events you want to trap.
    The base event system uses the graphical raycaster which needs an image to "bounce" off, although the image can be blank. Using the Text component is a cool idea as it doesn't show anything by default.
    But if you created your own component inheriting from Graphic (or MaskableGraphic) you could achieve the same thing (and even have your event handlers in the same script)

    Hmm, might just test/play with that.
     
  7. SimonDarksideJ

    SimonDarksideJ

    Joined:
    Jul 3, 2012
    Posts:
    1,689
    OK, had a play and created this script, it doesn't require any other components other than a RectTransform and be a child of a Canvas:
    Code (CSharp):
    1. using UnityEngine;
    2. using UnityEngine.EventSystems;
    3. using UnityEngine.UI;
    4.  
    5. public class EmptyClicker : Graphic, IPointerClickHandler
    6. {
    7.     public void OnPointerClick(PointerEventData eventData)
    8.     {
    9.         Debug.Log("You clicked the magic box");
    10.     }
    11.    
    12.     protected override void Awake()
    13.     {
    14.         this.color = new Color(0,0,0,0);
    15.     }
    16. }
    Acts as an empty area to click on. Don't really need the awake function if you configure the colour of the image in the editor.

    *Note
    Remember though, this will block any clicks passing through the RectTransform area, so anything behind it (higher in the hierarchy) will not be interactable. But I'm sure everyone here already knows that :D

    Would be interesting to pair this up with either the new clipping framework or a shapable mask to control where in the rect you can click. Or possibly have a click-test against an image/spec to further refine the clickable area.
     
  8. Trav3l3r

    Trav3l3r

    Joined:
    Sep 16, 2014
    Posts:
    60
    That's pretty neat
     
    SimonDarksideJ likes this.
  9. SimonDarksideJ

    SimonDarksideJ

    Joined:
    Jul 3, 2012
    Posts:
    1,689
    You should see what I'm trying to work up for the next UI extensions update :p
     
    Democide likes this.
  10. Trav3l3r

    Trav3l3r

    Joined:
    Sep 16, 2014
    Posts:
    60
    I just viewed some of your latest videos on Youtube. Your project looks really nice and very useful, too. :)
     
    SimonDarksideJ likes this.
  11. SimonDarksideJ

    SimonDarksideJ

    Joined:
    Jul 3, 2012
    Posts:
    1,689
    Thanks @Trav3l3r it's a project of passion unifying all the good UI components I can find, whipping them up in to a unified library, then ading my own controls.
    Plus all the packaging / recording and tutorials on it all :D
     
  12. iivo_k

    iivo_k

    Joined:
    Jan 28, 2013
    Posts:
    314
    Unfortunately that's still a draw call. :/ I've been wondering what would be the best way to create modal dialogs where you prevent clicking outside the object. On mobile it's not optimal to use a full screen graphic for that, since it's drawn even with 0 alpha and fillrate is the bottleneck.
     
  13. SimonDarksideJ

    SimonDarksideJ

    Joined:
    Jul 3, 2012
    Posts:
    1,689
    Everything is a draw call, even an empty rect, this is because everything in the UI system resolved down to a quad on the Canvas. (else interactivity won't work as it's based on Raycasts)
    If you want reaction without a draw call, then you'll have to use the physics raycasters but that will involve a whole heap of more work lining it up where you want it to interact.

    The draw calls for empties should be batched however so it shouldn't be to big of a hit
     
  14. iivo_k

    iivo_k

    Joined:
    Jan 28, 2013
    Posts:
    314
    An empty rect is not a draw call but an empty graphic is. It's not the extra draw call that's the problem either, it's fill rate on mobile, since AFAIK even "empty" graphics with 0 alpha are drawn.
     
    Rodolfo-Rubens likes this.
  15. eses

    eses

    Joined:
    Feb 26, 2013
    Posts:
    2,637
    @iivo_k - Hi, if what I suggested to OP is not good enough, I was thinking about this (simple solution) - how about using just RectTransformUtility?

    Figure out if user is inside your panel rectangle and use that info, for example keeping list of other button etc Image components, and disable raycast on them or something else when popup is active.

    It would be not using EventSystem at all, no Image/Graphic component needed, however you'd have to check it in some Update method.

    Even CanvasGroup needs Graphic component to block clicks. But I just wonder is it really so expensive to render empty Image panel that is screen size. I have to admit I've got no idea if it wastes fill rate.
     
  16. Chris-Trueman

    Chris-Trueman

    Joined:
    Oct 10, 2014
    Posts:
    1,261
    You could write your own Input module that handles this functionality. It is the input module that does the actual ray casting, you could create a system that doesn't use ray casting and only check if the pointer is inside the RectTransform. Kinda reinventing the wheel, but allows for more flexibility.
     
  17. Democide

    Democide

    Joined:
    Jan 29, 2013
    Posts:
    315
    I have an even simpler empty rect, that takes clicks and is less performanec intensive than using empty text:

    Code (CSharp):
    1. using UnityEngine;
    2. using UnityEngine.EventSystems;
    3. using UnityEngine.UI;
    4.  
    5. public class EmptyGraphic : Graphic
    6. {
    7.         protected override void OnPopulateMesh (VertexHelper vh)
    8.         {
    9.                 vh.Clear ();
    10.         }
    11. }
     
  18. mh114

    mh114

    Joined:
    Nov 17, 2013
    Posts:
    295
    Hey, that's actually quite neat! I too have been trying to find a way to have empty blocker objects that catch clicks but do not consume fillrate for ages. Seems like this trick works, thanks!
     
  19. eses

    eses

    Joined:
    Feb 26, 2013
    Posts:
    2,637
    @mh114: hi, and If I may ask, how is this any different than what is already mentioned above?

    "use some class that derives from Graphic class and implements some of click interfaces" or Simon's spelled out version? Actually it is the same solution, and Simon's code is almost 1:1 what I refered to.

    (I expect that you did read all the posts - sorry if not!)

    AFAIK for this to work, if class is derived from Graphic, then it has to have canvas renderer, so it has a fill (which, you of course can make transparent) but it still renders something I think?

    Like I mentioned, I've used Image component as a lazy person solution as you anyway have to have filled rect, so it's quite the same as having Image component, but a little bit less overhead, because Image is derived from Graphic class...

    or is there some difference I missed?
     
  20. mh114

    mh114

    Joined:
    Nov 17, 2013
    Posts:
    295
    But that is indeed the trick here; even though Graphic requires canvas renderer, that custom empty graphic doesn't output any vertices, hence nothing gets rendered. It is the best solution so far and I'm beating myself for not figuring it out myself! :)

    EDIT: And to clarify why this matters: on mobile you want to avoid overdraw as much as you can, especially on devices like iPhone 4 which have way too high resolution for their S***ty GPUs to handle. And when you have fullscreen "empty" quad covering the whole screen, you're introducing yet more overdraw for every single pixel! On fill-rate limited devices you'll see the FPS plummet when you do this. For PC-like platforms it doesn't really matter that much.
     
    Last edited: Feb 25, 2016
    eses likes this.
  21. SimonDarksideJ

    SimonDarksideJ

    Joined:
    Jul 3, 2012
    Posts:
    1,689
    True enough @Martin Sharkbomb using vh.clear in that way instead of applying a transparent colour should be more efficient. Still need the event handler though to accept clicks :D
     
    eses likes this.
  22. Democide

    Democide

    Joined:
    Jan 29, 2013
    Posts:
    315
    Well, that depends on what you want to do with it. For me that's just a graphic component replacement, and so should not itself collect any clicks. if you want to do that, you add a second component, that handles that.
     
    mh114 likes this.
  23. SimonDarksideJ

    SimonDarksideJ

    Joined:
    Jul 3, 2012
    Posts:
    1,689
    Now I'm confused @Martin Sharkbomb , If you don't need it for clicks / interactions, why bother putting a graphic on it at all?
    If you just need a container then a simple RectTransform on its own. Only reason to have an empty grphic is to enable hit testing with the Event system input module and their ray casts (unles I missed your point)
     
  24. mh114

    mh114

    Joined:
    Nov 17, 2013
    Posts:
    295
    Can't speak for Martin, but personally I need something that blocks raycasts (think modal dialog that should block any other input than in the dialog itself, or a tooltip that closes when clicking anywhere on screen), without resorting to drawing a transparent fullscreen quad which is horrible for mobile. This empty graphic trick does the job perfectly!
     
    eses likes this.
  25. SimonDarksideJ

    SimonDarksideJ

    Joined:
    Jul 3, 2012
    Posts:
    1,689
    Ironically, that is what the CanvasGroup component is for, however (even though it's not documented) for it to work it needs an image. So using the above technique is useful for just blocking, then using a CanvasGroup will enable you to switch the Raycast blocking on / off without affecting the children of the group.

    Alternatively (providing it's higher in the hierarchy) this could be used to block off portions of the UI completely
     
  26. Democide

    Democide

    Joined:
    Jan 29, 2013
    Posts:
    315
    Simon, no I do need something that reacts to the click, but without a graphic, even adding a component with the OnPointerClick interface isn't receiving clicks. So the emtpyRect is an empty graphic that can be added so that the GO gets clicks and other components can then work with those.
     
  27. mh114

    mh114

    Joined:
    Nov 17, 2013
    Posts:
    295
    Indeed, CanvasGroup requires a Graphic which would result in wasteful overdraw, but with the empty graphic that submits no vertices the problem is neatly solved. But if you're not doing mobile dev, all this doesn't matter that much.
     
  28. SimonDarksideJ

    SimonDarksideJ

    Joined:
    Jul 3, 2012
    Posts:
    1,689
    I think I got that bit in my original example, hence:
    Code (CSharp):
    1. EmptyClicker : Graphic, IPointerClickHandler
    So it inherited from Graphic. But I really did like your VH.clear option. Although I might (just remembered something) have something without that even, (need to test) as I did some work on a UI component that used the underlying mesh renderer which negated the need for the onPopulateMesh. hmm.

    If you want interaction or a way to block it, you can't get away without a graphic call. Both @Martin Sharkbomb and my suggestion just find the smallest shim to get through that dependency. The CanvasGroup wouldn't add to that apart from enabling you allow clicks when you wanted quickly and easily.

    Will see about adding the proposals here to the UI Extensions project with a few examples.

    (I did wonder if building a better CanvasGroup component would be preferable but in hindsight it would likely hamper things, better to work with it rather than against.)
     
  29. mh114

    mh114

    Joined:
    Nov 17, 2013
    Posts:
    295
    I feel like I'm repeating myself quite a lot in this thread, but did you actually try Martins trick? It precisely allows you to get the click without actually making a draw call! Which is exactly what I've been looking for; it always bothered me having to waste fillrate on something as simple as a "blocker object" for the background (for example with modal dialogs).
     
  30. Fattie

    Fattie

    Joined:
    Jul 5, 2012
    Posts:
    476
    you have to catch ALL of them, not just "clicks", if you have something underneath which drags, etc.

    Code (CSharp):
    1. using UnityEngine;
    2. using UnityEngine.EventSystems;
    3. using UnityEngine.UI;
    4.  
    5. public class Antitouchable : Graphic,
    6.         IPointerClickHandler,
    7.         IBeginDragHandler, IDragHandler, IEndDragHandler
    8. {
    9.     public void OnPointerClick(PointerEventData eventData)
    10.     {
    11.     }
    12.     public void OnBeginDrag(PointerEventData eventData)
    13.     {
    14.     }
    15.     public void OnDrag(PointerEventData eventData)
    16.     {
    17.     }
    18.     public void OnEndDrag(PointerEventData eventData)
    19.     {
    20.     }
    21. }
    22.