1
\$\begingroup\$

NOTE: See bottom of the question as I have managed to use a workaround which works to press the two buttons at once on Android (thanks to Douglas Potestas help)

I've struggled with this in the past and got it working. But its been a long time since I have used raycasts and I have mostly forgotten. Quick summary before people tell me to use OnPointerEnter etc is that on all of my Android games using pointer events you cannot press the two buttons (eg. imaging a NES gamepad pressing A and B with the same thumb like when you play Mario or something). You cannot slide the thumb to press both it only registers the first button hit, and this is not adequate for most of my game ideas.

Basically, I want to check if the thumb is over any amount of buttons at every frame of the game loop.

So I have this very basic code which I hoped would work, but the hit only returns anything if it hits a physical game object such as Cube or Capsule.

I want it to return if it hits a UI Image/Panel/Button (it matters not which one really).

I've tried adding 2D box collider and a 3D cube collider both as trigger and not trigger and made sure they're big enough. I've tagged the image/button. I've checked the "Raycast Target" box. Here is update loop of my OnScreenControlManager.cs

     void Update () {
         Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
         RaycastHit hit;
         float rayLength = Mathf.Infinity;
 
         if (Physics.Raycast(ray, out hit, rayLength))
         {
             Debug.Log("hit coll = " + hit.collider.tag);
             if (hit.collider.tag == "LeftButton")
             {
                 Debug.Log("hit coll = " + hit.collider.tag);
             }
         }
     }

I definitely had this working about a year ago in Unity 5. I'm now using Unity 2017, hope that isn't anything to do with it.

Have also tried Physics2d.Raycast:

private void Update()
{
    RaycastHit2D hit = Physics2D.Raycast(Camera.main.ScreenToWorldPoint(Input.mousePosition), Vector2.zero);

    if (hit.collider != null)
    {
        Debug.Log("Target tag: " + hit.collider.tag);
    }
}

Solution which works using new Rects and the Overlaps method. (Note , you cannot use this if the buttons are small and close together

public class OnScreenController : MonoBehaviour
{

    Rect touchRect;
    public float touchRectExtents;

    Bounds buttonLeftBounds;
    public GameObject buttonLeft;
    Rect buttonLeftRect;

    Bounds buttonABounds;
    public GameObject buttonA;
    Rect buttonARect;

    Bounds buttonBBounds;
    public GameObject buttonB;
    Rect buttonBRect;

    public Text aPressed, bPressed;


    private void Start()
    {
        buttonLeftBounds = buttonLeft.GetComponent<Collider2D>().bounds;

        buttonABounds = buttonA.GetComponent<Collider2D>().bounds;
        buttonBBounds = buttonB.GetComponent<Collider2D>().bounds;
    }

    private void Update()
    {
        touchRect = new Rect(Input.mousePosition.x - touchRectExtents, Input.mousePosition.y - touchRectExtents, touchRectExtents * 2, touchRectExtents * 2);

        buttonLeftRect = new Rect(buttonLeftBounds.center.x - buttonLeftBounds.extents.x, buttonLeftBounds.center.y + buttonLeftBounds.extents.y, buttonLeftBounds.size.x, buttonLeftBounds.size.y);

        buttonARect = new Rect(buttonABounds.center.x - buttonABounds.extents.x, buttonABounds.center.y + buttonABounds.extents.y, buttonABounds.size.x, buttonABounds.size.y);
        buttonBRect = new Rect(buttonBBounds.center.x - buttonBBounds.extents.x, buttonBBounds.center.y + buttonBBounds.extents.y, buttonBBounds.size.x, buttonBBounds.size.y);

        if (touchRect.Overlaps(buttonLeftRect))
        {
            Debug.Log(" LEFT PRESSED");
        }
        if (touchRect.Overlaps(buttonARect))
        {
            aPressed.text = "A PRESSED";
        }
        else
        {
            aPressed.text = "A NOT PRESSED";
        }

        if (touchRect.Overlaps(buttonBRect))
        {
            bPressed.text = "B PRESSED";
        }
        else
        {
            bPressed.text = "B NOT PRESSED";
        }
    }
}

EDIT: Still doesn't work :[ although I can now press the two buttons at once with the same touch, I need to add a way to check for other touches. For example if I am using the D-Pad with left thumb, this code doesn't even detect the hit on the buttons because Mouse.inputPosition is still over at the D-Pad.

\$\endgroup\$

2 Answers 2

3
\$\begingroup\$

I faced the same issue and found some other solution that could solve it, so probably it will be helpful for someone. Used unity version: 2022.2.
First of all, I need to say that I’m using input actions (new input system), not an input manager.

My input actions look like this:

enter image description here

You also should have an EventSystem on your Project Hierarchy. Probably you already have one if you add a Canvas or some UI element. I need to mention that I didn’t change an EventSystem or its properties, except just applying Input System UI Input Module, so it looks like this:

enter image description here

My Canvas has a DPad GameObject with 4 buttons (up, left, right and down).

enter image description here

enter image description here

DPad object has a DPadController script as a component.

public class DPadController : MonoBehaviour
{
    [SerializeField] private EventSystem eventSystem;

    private GameObject _lastPressedButton;
    private Dictionary<string, GameObject> _customPointerUpEventCalls;

    public void Awake()
    {
        _customPointerUpEventCalls = new Dictionary<string, GameObject>();
    }

    public void PointerDown(GameObject pressedButton)
    {
        _lastPressedButton = pressedButton;
    }
    
    public void PointerEnter(GameObject pressedButton)
    {
        if (!_lastPressedButton)
        {
            return;
        }

        _customPointerUpEventCalls.TryAdd(_lastPressedButton.name, _lastPressedButton);
        ExecuteEvents.Execute(_lastPressedButton, 
            new PointerEventData(eventSystem), ExecuteEvents.pointerUpHandler);
        ExecuteEvents.Execute(pressedButton, 
            new PointerEventData(eventSystem), ExecuteEvents.pointerDownHandler);
        _lastPressedButton = pressedButton;
    }

    public void PointerUp(GameObject pressedButton)
    {
        if (_customPointerUpEventCalls.ContainsKey(pressedButton.name))
        {
            // remove and do nothing because we already called 'up event' earlier in PointerEnter
            _customPointerUpEventCalls.Remove(pressedButton.name);
            return;
        }

        if (!_lastPressedButton)
        {
            return;
        }
        _customPointerUpEventCalls.TryAdd(_lastPressedButton.name, _lastPressedButton);
        ExecuteEvents.Execute(_lastPressedButton, 
            new PointerEventData(eventSystem), ExecuteEvents.pointerUpHandler);
        _lastPressedButton = null;
    }
}

As you see, you would need to attach EventSystem to an appropriate variable in UnityEditor. In this script, methods are just ordinary methods (not events) and are called from DPadButtonController script. In DPadController we are saving the last pressed button in ‘PointerDown’ method and if we enter some new button area (during the already pressed event) then we explicitly call PointerUp event on the last pressed button, mark it as our own custom event invocation and call PointerDown on the newly entered button. We need to mark custom event invocation; otherwise, the PointerUp method will be called indefinitely.

Each of the movement buttons has a DPadButtonController script as a component.

public class DPadButtonController : MonoBehaviour, IPointerDownHandler, IPointerUpHandler, IPointerEnterHandler
{
    [SerializeField] private GameObject dpadControllerObject;
    private DPadController _dpadController;

    public void Awake()
    {
        _dpadController = dpadControllerObject.GetComponent<DPadController>();
    }

    public void Start()
    {
        // to make buttons clickable only where image is
        GetComponent<Image>().alphaHitTestMinimumThreshold = 0.1f
    }
  
    public void OnPointerDown(PointerEventData eventData)
    {
        _dpadController.PointerDown(gameObject);
    }

    public void OnPointerUp(PointerEventData eventData)
    {
        _dpadController.PointerUp(gameObject);
    }

    public void OnPointerEnter(PointerEventData eventData)
    {
        _dpadController.PointerEnter(gameObject);
    }
}

In order for the DPadButtonController script to work, you would need to attach the DPad GameObject to an appropriate variable in UnityEditor. And here we have only DPadController’s method invocations.

In my case, I need one button to stop working and another to start working during the same thumb touch. But the provided solution could be easily adapted for the case presented in the question.

I hope this was helpful.

\$\endgroup\$
1
\$\begingroup\$

You probably want Rect.Overlap()

It allows you to check if one rect overlaps another rect.

So you would want to have a rect for the players touch. Which you can crate using the position and radius of Input.Gettouch(). Then do a fingerRect.Overlap(aButtonRect) instead physics.raycast.

If you are only trying to do a specific point instead of an area you can use Rect.Contains().

Which would look something like AbuttonRect.Contains(Input.mouseposition)

\$\endgroup\$
10
  • \$\begingroup\$ that sounds like it could work. So basically not using Raycast at all? \$\endgroup\$ Commented Nov 28, 2017 at 3:28
  • \$\begingroup\$ Would it be possible to create the new Rect every frame which would cover the entire thumb? or is it going be just the point at where the touch registered? \$\endgroup\$ Commented Nov 28, 2017 at 3:29
  • \$\begingroup\$ You would create a rect every frame, or alter it each frame. \$\endgroup\$ Commented Nov 28, 2017 at 3:35
  • \$\begingroup\$ sure , i understand that. But The variable "Input.mouseposition" only returns a Vector3 point, which of course cannot cover the both buttons at one, it can only be over one. I think your solution with the rectangle is defintely a good idea though I am going to try it. However it may give me problems if a player tried pressing say down , and the button is too close to up and hits that at same ttime. Still, I think you've given me enough to get something working. Coz at the moment me and my friend have both tried for days with Event Triggers and cant press both A and B at same time. thanks \$\endgroup\$ Commented Nov 28, 2017 at 3:38
  • 1
    \$\begingroup\$ You can set the size of the rect to be the diameter of the touch. The position of the touch would need to be offset like this offset = Vector2.one*Input.gettouch.radius; then you would do new Rect (input.gettouch.position-offset, offset*2); to get your rect for finger. \$\endgroup\$ Commented Nov 28, 2017 at 3:42

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .