Omar Shehata

Debugging double tap in Godot

From Omar's notebook.

Yesterday I fixed a bug in the Godot — Fix double & drag gesture on Android. It was my first contribution to the game engine. This post documents my journey navigating an unfamiliar codebase & avoiding getting stuck, mostly as advice & inspiration for my future self.

⭐ - denotes key moments/takeaways.

1 - Stumbling on the bug

I'm making a 2D game about snow. I wanted the player to be able to pick up & move the snow by double tapping and dragging. This worked when testing on my laptop but not on Android.

The bug was that double tap was correctly detected, but no subsequent drag events were emitted at all. Once you released your finger drag events started working again.

⭐ The first thing I tried was reproducing this behavior in a fresh project to make sure nothing in my game code was interfering with it. That confirmed that no input events were being triggered at all after the double tap as long as you left your finger pressed. This meant I couldn't work around this in any way without modifying the engine. So I filed a bug report:

I didn't get a response for a few days, so I decided to try and fix it for myself. This seamed like a feasible goal because I figured I could at least disable double tap detection in the engine (Godot 3, the previous major version, didn't have double tap detection, and this problem doesn't happen there.) And if I could get that to work, that would at least prove where the issue was, or narrow it down enough for an engine maintainer to take a look.

⭐ I think setting this realistic goal was really helpful — I wasn't confident I could fix it, but was confident I could comment out or revert enough code to unblock my game.

2 - Compiling Godot from source

⭐ I set a sub-goal for myself here: get any visible change in the engine source compiled & running on Android.

I had already followed the steps a while back to download & compile the Godot engine source for Windows. That meant I knew how to build a custom version of the engine & editor. But I didn't know how to do the Android export part. Thankfully, the Godot docs have a very nice tutorial on building for Android.

This step took me a long time. A few of the things I stumbled over:

That last one took me an especially long time, because I added logs with std::printf() but I couldn't see them. I had no idea if that was because I wasn't compiling my changes correctly, or I wasn't logging at the logs correctly, or both!

I've wasted many hours on this sort of thing in the past, so my quick sanity check here was to remove all code that obviously does something important (like remove all input code) and see if the change shows up. That worked, so I knew it was the logs that were the issue.

⭐ How did I know where to find the input code? I searched for past PRs on GitHub "double_tap Android" and found this one that pointed me to all the relevant files.

Searching the engine dev chatroom led me to print_line() as the right function for printing the logs. However, that is only available in C++ files. On the Java/Kotlin side, I asked ChatGPT and it pointed out that I need to use Log.d(), AND that you can't actually see the logs in Godot, you need to use adb logcat.

Ok, phew, now I finally got to a point where I can (1) compile the code & see changes (2) log stuff. Now I could actually start debugging.

3 - Debugging why double tap freezes all drag events

What I was looking for was (1) how Godot was listening to touch events from Android (2) how it was deciding when to emit an event. I expected there's just some bug in the logic where Android was correctly giving it the event, but Godot code was choosing to ignore it or not handle it. Something like this perhaps:

I found this in android_input_handler.cpp.

Some things I tried that failed here:

Finally, I did a codebase-wide search for this function to see where it being called. It wasn't obvious because it's a C++ function, called from Java, through a JNI interface. This led me to where the code looked like this:

Now when I added logs at the top of this function I could see it was triggering correctly for double tap & drag, but nothing was being emitted on the Godot side! So this was it, one of those return true statements was probably incorrectly declaring this gesture as handled, even though it was NOT being handled.

Here I thought I would compare: (1) in what codepath does a correct drag event get emitted? (2) what happens when you double tap & are dragging but NO events are emitted?

The confusing answer was: in BOTH cases it goes through this first return true:

if (this.gestureDetector.onTouchEvent(event)) {
	// The gesture detector has handled the event.
	return true;

So this gesture detector is saying that in both cases, it is handling the event and no further work needs to be done, but it's actually only handling the event in one case? What's going on?

4 - Learning about Android native gestures

I got stuck again here for a while because I couldn't find the code for gestureDetector.onTouchEvent at all. This is where I was lamenting my lack of IDE. I thought maybe it's implemented in some kind of weird inheritance/Java thing that I didn't understand.

I went for a walk & came back with a different approach: I googled how to detect double tap in Android. If I understood the native platform API, I could find the root of how this was implemented, and work backwards from there. It turns out that this function I couldn't find was itself the native Android gesture detector! When you call that gestureDetector.onTouchEvent function, it figures out what that gesture is, and then triggers a callback with info on that gesture. That code lives in GodotGestureHandler.kt

This was finally it! It was in this function that it was always returning true even though it only handles the ACTION_UP event.

The fix was to handle the move event in this event too:

Here was the final PR. The key insight for me here was learning from googling/the Android docs that drag events triggered after a double tap are emitted through the onDoubleTapEvent handler. The original implementation was assuming that a drag event would be emitted the same way regardless of whether it happened on a single or double tap.

I hope this demystifies a bit the winding process behind making a 1 line code fix like this! I'd love to see more write-ups like this that make transparent these kinds of processes, so reach out if you know of any :)

For more on my writing, see my newsletter or Twitter.