Here is fixed code (also now supports multitouch and touching two buttons at same time):
@tool
@icon("TouchScreenButtonControl.svg")
class_name TouchScreenButtonControl
extends TextureButton
const DefaultValues := {
"expand": true,
"ignore_texture_size": true,
"stretch_mode": TextureButton.STRETCH_KEEP_ASPECT_CENTERED,
"action_mode": TextureButton.ACTION_MODE_BUTTON_PRESS,
"focus_mode": TextureButton.FOCUS_NONE,
}
@export var use_default_values := true
@export var touchscreen_only := false
# The actual variable that holds the data
var input_action: String = ""
# 2. MATCHED the property name here...
func _get_property_list() -> Array:
var properties: Array = []
if Engine.is_editor_hint():
InputMap.load_from_project_settings()
# Get actions and format them for the dropdown
var actions = InputMap.get_actions()
var action_list = ",".join(actions)
properties.append({
"name": "input_action",
"type": TYPE_STRING,
"hint": PROPERTY_HINT_ENUM_SUGGESTION,
"hint_string": ",".join(actions),
"usage": PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_EDITOR
})
return properties
func _set(property: StringName, value: Variant) -> bool:
if property == "input_action":
input_action = value
return true
return false
func _get(property: StringName) -> Variant:
if property == "input_action":
return input_action
return null
# Track multiple simultaneous touches for multitouch support
var active_touches := {} # Dictionary: touch_index -> bool (true if pressed)
var initial_press_touches := {} # Dictionary: touch_index -> bool (true if this button was pressed on initial touch)
func _init():
if use_default_values:
for k in DefaultValues.keys():
self.set(k, DefaultValues.get(k))
# Set mouse_filter to IGNORE so the button doesn't consume touch events
# This allows multiple buttons to detect the same touch simultaneously
mouse_filter = Control.MOUSE_FILTER_IGNORE
if touchscreen_only and not DisplayServer.is_touchscreen_available():
hide()
func press():
var input_event: InputEvent = InputMap.action_get_events(input_action)[0]
input_event.pressed = true
Input.parse_input_event(input_event)
func release():
var input_event: InputEvent = InputMap.action_get_events(input_action)[0]
input_event.pressed = false
Input.parse_input_event(input_event)
func is_in(pos: Vector2) -> bool:
# Get the global rect of this Control node in screen coordinates
var global_rect = get_global_rect()
# Check if the touch position is within the button's global rect
if pos.x >= global_rect.position.x and pos.x <= global_rect.position.x + global_rect.size.x:
if pos.y >= global_rect.position.y and pos.y <= global_rect.position.y + global_rect.size.y:
return true
return false
func _input(event):
# Use _input to receive all touch events
# IMPORTANT: _input is called on ALL nodes in the scene tree, so multiple buttons
# can receive the same event. We don't consume the event so others can too.
if event is InputEventScreenTouch:
if event.pressed:
# Check if this touch is within this button's bounds
if is_in(event.position):
# Start tracking this touch for this button
if event.index not in active_touches:
active_touches[event.index] = true
initial_press_touches[event.index] = true # Mark as initial press
press()
else:
# Touch released - check if we were tracking it
if event.index in active_touches:
# This touch was being tracked by this button, release it
active_touches.erase(event.index)
initial_press_touches.erase(event.index)
# Only release the action if no other touches are active for this button
if active_touches.is_empty():
release()
elif event is InputEventScreenDrag:
# Handle touch drag with smart re-activation logic
if event.index in active_touches:
# We're tracking this touch
if initial_press_touches.has(event.index):
# This was an initial press - keep it active even if touch moves outside
# Don't release until touch ends
pass
else:
# This was a re-activation (touch moved back into bounds)
# Check if touch moved back out of bounds
if not is_in(event.position):
# Touch moved out of bounds, release this re-activated button
active_touches.erase(event.index)
if active_touches.is_empty():
release()
else:
# We're not tracking this touch - check if it moved into our bounds
# This allows "tapping" behavior - touch can re-enter and activate button
if is_in(event.position):
# Touch moved into bounds, start tracking (but not as initial press)
active_touches[event.index] = true
press()
Here is fixed code (also now supports multitouch and touching two buttons at same time):