Node2D double click detection in Godot
Clicks for Node2D
based nodes can easily be detected by using CollisionObject2D
‘s input_event
signal.
To capture the mouse clicks we can add an Area2D
node and a CollisionShape2D
or CollisionPolygon2D
as its child node.
Since we are using CollisionObject2D
‘s input_event
signal directly the Area2D
‘s Monitoring
and Monitorable
won’t be used and we can disable those.
Area2D
‘s ‘Collision > Layer
needs to be set to some value or the clicks won’t be detected.
The Area2D
node also needs to have Input > Pickable
set to True
.
Give the CollisionShape2D
a Shape
, eg. RectangleShape2D
, CircleShape2D
, CapsuleShape2D
, etc. and set its Size
to the dimensions you want. This defines the area’s size and shape that can be clicked with the mouse.
Finally set the CollisionShape2D
‘s Transform > Position
to the location where you want it.
Now we can add a script to the Area2D
node and in Area2D
‘s Node > Signals
connect the input_event
to the script.
See example below.
public void OnContainerBeingClicked(Node viewport, InputEvent @event, int shapeIdx) {
// Return if not mouse left button double click.
if (@event is not InputEventMouseButton mouseButtonEvent) return;
if (!mouseButtonEvent.Pressed) return;
if (mouseButtonEvent.ButtonIndex != MouseButton.Left) return;
if (!mouseButtonEvent.DoubleClick) return;
ToggleContainer();
}
The code is pretty self-explanatory, but let’s break it down.
In the method signature we need to return void
, and accept Node
, InputEvent
, and int
as parameters - even if we don’t use them all. It’s simply required to connect the signal.
Since event
is a reserved keyword we are required to place @
in front of it if we want to use that particular name in our parameters, ie. @event
instead of event
. But we might as well use any other name such as inputEvent
if it strikes your fancy - it really doesn’t matter. The parameter names can be what ever you want as long as the types match Node
, InputEvent
and int
. The signal will still work.
We cannot directly handle InputEventMouseButton
, so we need to use InputEvent
instead and cast it to InputEventMouseButton
:
if (@event is not InputEventMouseButton mouseButtonEvent) return;
Here we check whether @event
is or isn’t InputEventMouseButton
. If it’s not we simply return and do nothing further with that particular input event - someone else can handle that one, what ever it is.
If the @event
is InputEventMouseButton
we cast it as InputEventMouseButton
and expose the cast as a local variable called mouseButtonEvent
and continue executing our code. We can now access the InputEventMouseButton
mouse button event via mouseButtonEvent
and do further things.
if (!mouseButtonEvent.Pressed) return;
Here we immediately put it to use and check if the mouse button is not being pressed (ie. it’s likely being released instead), we will simply return and call it quits there - it’s not our concern.
if (mouseButtonEvent.ButtonIndex != MouseButton.Left) return;
We still don’t know what kind of mouse button press this was, so we can check by using the ButtonIndex
whether it was something else than a left mouse button press - in which case we simply return and call it quits again. It’s not our concern, since in this example we want to handle left mouse buttons clicks only.
if (!mouseButtonEvent.DoubleClick) return;
Finally we have a very handy way of checking whether a double click happened without having to deal with timers and special logic each time - we can simply check the DoubleClick
property. Godot will expose to us automatically the information whether it was a double click or not. If it’s not a double click, again, we return and call it quits there.
Now we’ve determined:
- It is a mouse button event.
- It is a mouse button being pressed down event.
- It is a left mouse button event.
- It is a double click
So we know the user did a left mouse button double click, so we can now do what ever we want with that information.
ToggleContainer();
In the above example what we do is we call our own custom ToggleContainer()
method which opens - or closes - an inventory container window for us.
But you can call your own methods or do what ever you want once you’ve ruled out all the cases you don’t want to react to like we did above one step at a time.
You can also combine the if-statements if you like. We have kept them separate in the example for additional clarity and readability.
But you might also do this:
if (@event is not InputEventMouseButton mouseButtonEvent
|| !mouseButtonEvent.Pressed
|| mouseButtonEvent.ButtonIndex != MouseButton.Left
|| !mouseButtonEvent.DoubleClick) {
return;
}
The IL code produced by either of these examples is slightly different, but not meaningfully so.
It’s also worth noting that when using return
s, like in the first example, the stack traces can be more meaningful when tracking down problems.
But generally the second example would execute faster. But this would be in the realm of nano optimizations and might be made a moot point anyway by future updates to the runtime.
It’s also worth noting that in Godot 4 you can implicitly cast the Node
by accepting Viewport
instead of Node
in the signal’s method signature as a parameter - and it will still work because Viewport
is a child of Node
.
So if you want to handle the input, for example, so it doesn’t propagate any further - and you don’t want to cast the Node
in your code explicitly to Viewport
which you need to access the SetInputAsHandled()
method, you can accept a Viewport
directly instead of Node
which is declared in the input_event
signal’s signature.
Eg.
public void OnContainerBeingClicked(Viewport viewport, InputEvent @event, int shapeIdx) {
// Return if not mouse left button double click.
if (@event is not InputEventMouseButton mouseButtonEvent) return;
if (!mouseButtonEvent.Pressed) return;
if (mouseButtonEvent.ButtonIndex != MouseButton.Left) return;
if (!mouseButtonEvent.DoubleClick) return;
viewport.SetInputAsHandled();
ToggleContainer();
}
This will also work. Otherwise we would have to:
public void OnContainerBeingClicked(Node viewport, InputEvent @event, int shapeIdx) {
// Return if not mouse left button double click.
if (@event is not InputEventMouseButton mouseButtonEvent) return;
if (!mouseButtonEvent.Pressed) return;
if (mouseButtonEvent.ButtonIndex != MouseButton.Left) return;
if (!mouseButtonEvent.DoubleClick) return;
((Viewport)viewport).SetInputAsHandled();
ToggleContainer();
}
That’s mostly it for handling mouse double clicks in Node2D nodes.