Create a custom TouchScreenButton

Godot Version

v4.2.stable.official [46dc27791]

Question

For the past week, I’ve been working on the controls for a mobile game but I’m having troubles with multi-touch and screen drag.

I have two arrows, one moves the player right, and the other moves him left. I need both of them to be clickable at the same time (eg. when the player presses the jump button while going left) and I’d like to implement “pass-by press” (like TouchScreenButton’s).

I can’t use TouchScreenButton though, since it has too few options and it isn’t a Control node.

I tried to make a custom one myself, and so far I’ve achieved this:

func _on_texture_gui_input(event):
	if event is InputEventScreenTouch:
		if event.pressed:
			pressed.emit()
			# Animate the button
			$Texture.scale = Vector2(1.2, 1.2)
			set_process(true)
		is_pressed = event.pressed

	elif event is InputEventScreenDrag:
		# Detect if drag is outside the texture
		if not Rect2(Vector2(0,0), $Texture.get_global_rect().size).has_point(event.position):
			# Cancel event
			pass
		else:
			pressed.emit()

Screenshot 2023-12-23 210456

It works with multi-touch using a texture internally but I can’t implement “pass-by press”, as this gif shows.

The code is able to detect when the finger is out of the button but I need a way to “cancel” the input so the other button can receive it. Thanks for the help.

_gui_input() gets attached to the Control that started the click/touch. You’ll need to use _input() in the TextureRect itself. Something like this may work:


extends TextureRect

signal pressed()

var is_pressed = false

func _input(event: InputEvent) -> void:
	if event is InputEventScreenTouch:
		if get_global_rect().has_point(event.position):
			is_pressed = event.pressed
			if is_pressed:
				pressed.emit()

	if event is InputEventScreenDrag:
		if get_global_rect().has_point(event.position):
			pressed.emit()

1 Like

Thank you for the answer, I applied your code to the texture node, and now the Control node “Button” only holds the ‘is_pressed’ boolean and 'pressed ’ signal.

I added some debugging to the code you provided…

# ...
if event is InputEventScreenDrag:
	if get_global_rect().has_point(event.position):
		get_parent().pressed.emit()
		print("Still pressed")
	else:
		print("Not pressed")

…and as shown in this clip, the button remains pressed even when the cursor is outside and has released.
So, as before, there’s a way to detect that (when the event is InputScreenDrag and the position isn’t in the bounding box) but I don’t know how to stop the event.

Keep the event index and change is_pressed when that event index gets released:

extends TextureRect

signal pressed()

var is_pressed = false
var touch_idx = -1

func _input(event: InputEvent) -> void:
	if event is InputEventScreenTouch:
		if get_global_rect().has_point(event.position):
			is_pressed = event.pressed
			touch_idx = event.index
			if is_pressed:
				pressed.emit()
		elif not event.pressed and event.index == touch_idx:
			is_pressed = false
			touch_idx = -1

	if event is InputEventScreenDrag:
		if get_global_rect().has_point(event.position):
			pressed.emit()
1 Like

Ah right, I always forget to use indexes :grin:.

By combining your samples I fixed my problem, using the _input function of the texture, and using indexes the “pass-by press” feature works.

Here’s what I used:

extends TextureRect

var touch_idx = -1

func _input(event: InputEvent) -> void:
	if event is InputEventScreenDrag:
		if get_global_rect().has_point(event.position):
			get_parent().pressed.emit()
			get_parent().is_pressed = true
		else:
			get_parent().is_pressed = false
	if event is InputEventScreenTouch:
		if get_global_rect().has_point(event.position) and event.pressed:
			touch_idx = event.index
			get_parent().pressed.emit()
			get_parent().is_pressed = true
		elif event.index == touch_idx:
			get_parent().is_pressed = false
			touch_idx = -1

It doesn’t work perfectly, I probably got the index part wrong, but it solved my initial issue, thank you for the help.

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.