How to check for separate left-click and click-and-drag inputs?

:information_source: Attention Topic was automatically imported from the old Question2Answer platform.
:bust_in_silhouette: Asked By maforn

This is my first time using any engine, and I’m making a simple 2D puzzle game. I want the player to be able to click and drag pieces (colored triangles) around, but also to rotate them 90 degrees to the right with a single left-click.

Using the appropriate signal and the following bit of code solved the first problem:

onready var dragging := false

func _physics_process(_delta: float) -> void:
 if dragging:
  global_position = get_global_mouse_position()

func _on_ColoredTriangle_input_event(_viewport, event, _shape_idx):
 if event.is_action_pressed("click"):
  dragging = true
 if event.is_action_released("click"):
  dragging = false

I haven’t been able to figure out how to make the game also detect a single left-click, though. Closest I got was making it so that the triangle always rotates when clicked and, if the button is held, could then be dragged around. I want to make it so that it DOESN’T rotate if the player holds the button.

What am I missing here?

:bust_in_silhouette: Reply From: AuthorSan

I’m sorry but I don’t quite understand the question. You want to rotate the puzzle piece with a left-click and move them around while holding down right mouse button, right?

Sorry, I probably wasn’t too clear. The player’s supposed to be able to either rotate the triangle with a single left-click, OR drag it around if holding the left mouse button. jgodfrey’s answer actually explained it better than I did, though.

maforn | 2020-08-21 20:48

:bust_in_silhouette: Reply From: jgodfrey

To me, the problem boils down to understanding the difference between:

  • A “click”, intended to rotate the piece
  • A “press, drag, release” intended to move the piece, but not rotate it.

Since the drag operation will, ultimately, trigger both a press and release of the button (just like a click), you need to somehow differentiate the two operations.

It seems you could do that in at least a few obvious ways.

  • Calculate the time between the press and release of the button. With a “fast” time being interpreted as a click (so, rotate) and a slow time being interpreted as a drag (so, move the piece).
  • The distance the mouse is moved between press and release events. With a “short” distance being interpreted as a click (so, rotate) and a “long” distance being interpreted as a drag.
  • Some combination of both time and distance…

I’d guess that 2nd thing probably feels better in actual use, but you could try them all.

In all cases, you’d simply need to wire both the press and release event of the mouse. In the press event, you’d record either current time or the current position (depending on the method you choose).

Then, in the release event, you’d compare that recorded value to its current value (time or location).

Finally, you’d just need to calculate a delta between the two values and decide which side of the threshold it lies on (click or drag).

This is perfect, thanks a ton! I ended up going for a time-based solution using a Timer node, since I figured that, in a distance-based one, if a player held the button for a long time without moving and then released it, it would rotate the piece, which isn’t what I’m going for and would therefore also require a timer.

This is what I ended up with:

var dragging := false
onready var click_timer := $ClickTimer

func _process(_delta: float) -> void:
 if dragging:
	global_position = get_global_mouse_position()

func _on_ColoredTriangle_input_event(_viewport, event: InputEvent, _shape_idx) -> void:
 if Input.is_action_just_pressed("click"):
	click_timer.start(0.15)

 if event.is_action_pressed("click"):
	dragging = true

 if event.is_action_released("click"):
	dragging = false
	if click_timer.get_time_left() > 0.0:
		rotation_degrees += 90.0

maforn | 2020-08-21 20:42

Glad you got it working. One observation…

It looks like it’d be possible to quickly drag the piece some distance, release the mouse, and have it processed as a click (so, rotate). That would cause the piece to be both moved and rotated. Maybe it’s not really a problem in practice due to the relatively short 0.15 click timer.

However, you could mitigate it by recording the pieces position when the button is first pressed. Then, if you end up deciding the action was indeed a click (so, a rotate), just position the piece at the recorded (original) location. That way, if it was a click, it’d snap back to its original position if it did get dragged a little in the process.

jgodfrey | 2020-08-21 20:58

You’re right; I tested and it does happen, even if only when I’m deliberately trying to make it happen. Your suggestion also fixes it just right. However, in the long run, I think it won’t matter much; I’m planning on making it so that a piece’s position always resets to it’s original one unless it’s dragged to the correct slot.

maforn | 2020-08-21 22:43