[SOLVED] Stop Raycast from firing if clicked on Button

Hi guys,

I am facing a problem on touch devices and here it is,

So in the game you can drag or click the arrow to move it and set the distance where the golf ball will go, and I am using raycast which hits on the ground and I set the position of the marker, but if I click the blue button “Shot View” to change the view, then the marker just goes where the “Shot view” button is, so I dont want the raycast to pass through the button and hit the ground but just simply behave like a button click.

The raycast as well as the button fire on the same “touchstart” event.

On desktop I solved this issue by using the OnHover property of the cursor so if the mouse was hovering on a element I wouldnt fire the raycast.

Regards,
Saad

1 Like

Hi @Saad_Haider,

One way to easily solve this is using a global variable that acts as a switch if a touchstart event has clicked on a UI element. If that’s is true for that frame cancel all other touchstart event handlers.

Let’s say this your UI button script:

var BtnStates = pc.createScript('btnStates');

BtnStates.buttonClicked = false;

// initialize code called once per entity
BtnStates.prototype.initialize = function() {

    // touch events
    this.entity.element.on('touchstart', this.onPress, this);
    this.entity.element.on('touchend', this.onRelease, this);
};

BtnStates.prototype.onPress = function (event) {

   BtnStates.buttonClicked = true;
};

BtnStates.prototype.onRelease = function (event) {

   // --- we put it in a zero timeout to have it propagate to the next frame
   window.setTimeout(function(){
      BtnStates.buttonClicked = false;
   }, 0);
};

Now in any other script you are having a touchstart event handler you can do this:

// initialize code called once per entity
MyScript.prototype.initialize = function() {

    // touch events
    this.app.touch.on('touchstart', this.onPress, this);
};

MyScript.prototype.onPress = function (event) {

   if( BtnStates.buttonClick === true) return false;

   // do your raycast 
};
1 Like

Yep, @Leonidas has a good suggestion.

In my game I have a popup widget, that appears during the game play, a friend request. I need to press Accept or Reject button, without affecting the actual gameplay. I do it similar to what is described (pseudo code):

 // in my input script
if (not UI click) {
    on 'touchstart' jump();
}

// in my button script
on 'touchstart' {
    UI click = true;
}
on 'touchend' {
    UI click = false;
}
1 Like

Do the UI touchevents always get processed first? I couldn’t work that out last night :thinking:

Good point, I think it may have to do with the order that you subscribe but I haven’t confirmed that.

In some cases where that isn’t the case, I can afford adding a zero timeout on non-UI clicks, like in this case for scene raycasting. So those clicks get handled in the next frame, after UI clicks.

This is hacky, so the best solution is to keep track of the order these events get executed. It would be good to get some insight on this.

Hmm, actually good point. I didn’t use the timeout hack, since it worked fine for me. Now that I think about it, I am not sure why it’s working the way it works now :slight_smile: There should have been a race condition, but I don’t see it. Maybe UI does get priority, as @yaustar mentioned? Not sure.

I’ve checked the code for Editor created apps and the input handlers for elements is handled before pc.Mouse and pc.Touch so you can use https://developer.playcanvas.com/en/api/pc.ElementTouchEvent.html#stopPropagation and https://developer.playcanvas.com/en/api/pc.ElementMouseEvent.html#stopPropagation

To stop the callback for pc.Touch#touchstart handler from being called.

Example project: https://playcanvas.com/project/694477/overview/block-world-input-with-ui

4 Likes

Nice! Thanks for sharing this @yaustar, it’s elegant and definitely the right way to handle this.

2 Likes