Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Dismiss Notice

Invisible buttons?

Discussion in 'UGUI & TextMesh Pro' started by StarManta, Sep 8, 2014.

  1. StarManta

    StarManta

    Joined:
    Oct 23, 2006
    Posts:
    8,741
    Is there a "right" way to make an invisible button, or an easy way to "catch" a click/tap that's outside of the rest of the UI? I've done it by making a button behind everything and setting the alpha on its image to 0, but this feels dirty (and I suspect it's doing bad things to my fillrate).
     
  2. orb

    orb

    Joined:
    Nov 24, 2010
    Posts:
    3,033
    The simplest way I've found is to check for a click the old-fashioned way, and using EventSystemManager.currentSystem.IsPointerOverEventSystemObject() to check if the pointer is hovering over a control.
     
  3. greg-harding

    greg-harding

    Joined:
    Apr 11, 2013
    Posts:
    519
    hi,

    we are trying to figure out nice ways to do a similar thing - capture events when no UI has been clicked on etc. as a kind of fullscreen object that receives the otherwise unused events. There are some older posts that ask about similar things, and I think Tim C replied suggesting to use IsPointerOverEventSystemObject() to check if a pointer is over any UI objects. We initially had problems with this call with things getting in the way during raycasting and used Canvas Groups to create hierarchies to be ignored by the canvas raycaster, but it still didn't allow a nice way to capture the events without polling somewhere.

    Based on the work-in-progress source code I think Tim C posted (https://gist.github.com/stramit), we thought about making a normal UI object that would be invisible but part of the stack of UI objects. It looks like the EventSystem, InputModules and GraphicRaycaster only look for/raycast against graphic objects that are registered with the canvas to decide what to send events to, and a graphic has a rect that's used in the raycast (the image can use a different rect if it's kept aspect ratio correct, so it's not the final image rect itself which is used as the hit area). The graphic and image objects are pretty rendering specific (they build all the mesh stuff and handle materials etc.) so it seemed nasty to fake up an invisible graphic by ignoring all the unwanted stuff but still registering it with the canvas to be part of the raycasting... and why not just use an invisible sprite anyway, ignoring any rendering impact?

    So, this doesn't solve the general invisible button problem, but we ended up subclassing the GraphicRaycaster and using it as the raycaster for the canvas. During the normal raycasting calls made by the ui/event system, it just inserts a default 'fullscreen' object positioned at the back of the raycasting results and then calls the normal raycast. The fullscreen object effectively receives the events that don't get used by any other ui objects somewhere and is part of the normal events system rather than using polling. Glancing at the github wip code and quickly testing things, it doesn't appear that the event system knows any different. It also means that a custom InputModule isn't needed to process the events and pass them to the fullscreen object.

    Our test raycaster code is something like this - replace the canvas GraphicRaycaster with it. It's set up for a screen space overlay ui and graphic objects, as opposed to extending the other PhysicsRaycaster classes for use with 2d/3d colliders.
    Code (CSharp):
    1. public class GraphicRaycasterFullscreen : GraphicRaycaster {
    2.  
    3.     public GameObject fullscreenGameObject;
    4.  
    5.     public override void Raycast(PointerEventData eventData, List<RaycastResult> resultAppendList) {
    6.         // fullscreen result
    7.         if (fullscreenGameObject) {
    8.  
    9.             RaycastResult raycastResult = new RaycastResult() {
    10.                 go = fullscreenGameObject,
    11.                 module = this,
    12.                 distance = float.MaxValue,
    13.                 index = (float)resultAppendList.Count
    14.             };
    15.  
    16.             resultAppendList.Add(raycastResult);
    17.         }
    18.  
    19.         // normal raycast
    20.         base.Raycast(eventData, resultAppendList);
    21.     }
    22. }
    23.  
    And here's a fullscreen script attached to some gameobject to receive events. In the inspector, associate this gameobject with the GraphicRaycasterFullscreen's fullscreenGameObject above.
    Code (CSharp):
    1. public class CanvasFullScreen : UIBehaviour, IPointerClickHandler {
    2.  
    3.     public void OnPointerClick(PointerEventData eventData) {
    4.         Debug.Log("OnPointerClick!");
    5.     }
    6. }
    7.  
    This is just test code, but I hope it helps someone trying to do the same thing. It might not be the best way to do things but without knowing more about the ui/event code it's hard to know just yet. Also, the target fullscreen gameobject might need to be a different type or have some more checks around it for various event interfaces or other lifecycle events like enable/disable.

    cheers,

    // greg
     
  4. User340

    User340

    Joined:
    Feb 28, 2007
    Posts:
    3,001
    I got it! Instead of using a fully transparent image, just use a Text without any text :). Works like a charm.
     
    ryanterry and jxz12 like this.
  5. Fattie

    Fattie

    Joined:
    Jul 5, 2012
    Posts:
    475
  6. greg-harding

    greg-harding

    Joined:
    Apr 11, 2013
    Posts:
    519
  7. xodennisxo

    xodennisxo

    Joined:
    Mar 25, 2020
    Posts:
    16
    Start from Unity 2018.2, you don't need to do any walkaround like this any more:
    Just Set image's alpha to 0 and check cullTransparentMesh on CanvasRenderer component.
    This leads to an invisible button without any rendering (draw call)
     
  8. Fattie

    Fattie

    Joined:
    Jul 5, 2012
    Posts:
    475
    The only correct answer is simply Touchable.cs, as it has been for half a decade. I've never, ever seen a Unity project from big corporations to startups that didn't have a copy of Touchable in the project.

    https://stackoverflow.com/a/36892803/294884

    Code (CSharp):
    1. // file Touchable.cs
    2. // Correctly backfills the missing Touchable concept in Unity.UI's OO chain.
    3.  
    4. using UnityEngine;
    5. using UnityEngine.UI;
    6. #if UNITY_EDITOR
    7. using UnityEditor;
    8. [CustomEditor(typeof(Touchable))]
    9. public class Touchable_Editor : Editor
    10.      { public override void OnInspectorGUI(){} }
    11. #endif
    12. public class Touchable:Text
    13.      { protected override void Awake() { base.Awake();} }
    BUT be aware of this ingenious new suggestion for writing Touchable, rather than backfilling:

    https://stackoverflow.com/a/64074858/294884