1
NGUI 3 Support / Fat finger multitouch problems
« on: March 02, 2014, 04:12:54 PM »
A year ago, when we shipped Rise of the Blobs, we got a large number of reports of problems involving the pause screen popping up in the middle of the game. We tried forever to reproduce this without success. We were able to do it once or twice (in ~2000 attempts) so we knew it was a legit bug, but we could not figure out the reproduction steps.
I finally had a flash of insight and realized that the bug was caused by a race condition. Two different mutually-exclusive things were happening the same frame ("start the game" and "back out of the current dialog") which was leaving the game in a bad state. And the reason that this happened is that people were pressing two buttons at the same time. Two buttons that, on devices with small screens, are in close physical proximity were being depressed and released simultaneously, causing two OnClick() messages to be sent on the same frame, causing the race condition.
Once we realized this was the problem we were able to break almost every dialog box in our game (of which there are a huge amount in that game) simply by pressing multiple buttons at once.
I think the OnClick() via SendMessage() is fundamentally problematic because it encourages writing small button scripts that know nothing about what else might be getting pressed in the system. In fact, I think you almost never want to send more than one OnClick() for UI; it might be necessary for HUD buttons, but in the general case it seems more correct for the first button that responds to OnClick to eat the event.
Now, I know that I could just turn off UICamera.allowMultiTouch. But that is a poor solution from a UX perspective, because now any stray finger on the screen will cause all of the buttons to stop working. I'm not sure if you've ever seen somebody hold their phone such that their thumb slightly touches the side of the screen, but when this happens in apps that do not allow multitouch it causes the whole app to feel broken.
I can get Temple Run 2 menus to freak out a bit by double pressing buttons. Subway Surfers has allowMultiTouch turned off. I suspect this is a problem that affects a lot of NGUI-based games on mobile devices.
On Rise of the Blobs our fire-fighting fix was to visit every single OnClick() script in the game and add a check via a global singleton. The singleton would allow one click per frame and then would return false for all subsequent requests. This way, a stray finger that wasn't touching a collider would not prevent other buttons from working, but you were unable to simultaneously click more than one button at a time. This works but it's not a very classy fix.
Now, a year later, I'm about to ship our new game, Wind-up Knight 2, and lo and behold, despite being a lot more careful about potential vectors for race conditions we can still cause mega breakage by hitting buttons at the same time.
So, my question is: what's the best way to fix this in the short term? I don't mind getting my hands dirty in NGUI code, but since the event is broadcast via SendMessage() I didn't have many good ideas for how to alter the existing code to stop sending OnClick() after the first one succeeds. Secondly, what could we do to make this not possible in future versions of NGUI?
Thanks!
I finally had a flash of insight and realized that the bug was caused by a race condition. Two different mutually-exclusive things were happening the same frame ("start the game" and "back out of the current dialog") which was leaving the game in a bad state. And the reason that this happened is that people were pressing two buttons at the same time. Two buttons that, on devices with small screens, are in close physical proximity were being depressed and released simultaneously, causing two OnClick() messages to be sent on the same frame, causing the race condition.
Once we realized this was the problem we were able to break almost every dialog box in our game (of which there are a huge amount in that game) simply by pressing multiple buttons at once.
I think the OnClick() via SendMessage() is fundamentally problematic because it encourages writing small button scripts that know nothing about what else might be getting pressed in the system. In fact, I think you almost never want to send more than one OnClick() for UI; it might be necessary for HUD buttons, but in the general case it seems more correct for the first button that responds to OnClick to eat the event.
Now, I know that I could just turn off UICamera.allowMultiTouch. But that is a poor solution from a UX perspective, because now any stray finger on the screen will cause all of the buttons to stop working. I'm not sure if you've ever seen somebody hold their phone such that their thumb slightly touches the side of the screen, but when this happens in apps that do not allow multitouch it causes the whole app to feel broken.
I can get Temple Run 2 menus to freak out a bit by double pressing buttons. Subway Surfers has allowMultiTouch turned off. I suspect this is a problem that affects a lot of NGUI-based games on mobile devices.
On Rise of the Blobs our fire-fighting fix was to visit every single OnClick() script in the game and add a check via a global singleton. The singleton would allow one click per frame and then would return false for all subsequent requests. This way, a stray finger that wasn't touching a collider would not prevent other buttons from working, but you were unable to simultaneously click more than one button at a time. This works but it's not a very classy fix.
Now, a year later, I'm about to ship our new game, Wind-up Knight 2, and lo and behold, despite being a lot more careful about potential vectors for race conditions we can still cause mega breakage by hitting buttons at the same time.
So, my question is: what's the best way to fix this in the short term? I don't mind getting my hands dirty in NGUI code, but since the event is broadcast via SendMessage() I didn't have many good ideas for how to alter the existing code to stop sending OnClick() after the first one succeeds. Secondly, what could we do to make this not possible in future versions of NGUI?
Thanks!