Godot treats mouse and touch somewhat interchangeably, but "somewhat" is the word that bites you. This lesson gets you to working touch controls on an Android device, and calls out the gotchas that cost people weeks.
| Event class | Fires on |
|---|---|
InputEventMouseButton | Click/release on desktop. Also on Android if emulation is on. |
InputEventMouseMotion | Mouse move. Also finger drag when emulation is on. |
InputEventScreenTouch | Finger down/up. Android/iOS native. Has index for multi-touch. |
InputEventScreenDrag | Finger drag. Has index, position, relative. |
InputEventKey | Keyboard. |
InputEventAction | Abstracted action mapped in Input Map. Prefer this. |
For a Card scene with a touch target, you have three handlers. Know when to use each.
# 1. Control.gui_input โ fires when input hits a Control node's rect.
# This is the cleanest option for UI. Fires only for events *on* this control.
func _gui_input(event: InputEvent) -> void:
if event is InputEventMouseButton and event.pressed:
_on_card_tapped()
# 2. Area2D signals โ fires for physics-based touch targets in Node2D-space.
# Use for game-world entities that aren't Controls.
func _ready() -> void:
$TouchArea.input_event.connect(_on_touch)
func _on_touch(_viewport, event: InputEvent, _shape_idx: int) -> void:
if event is InputEventMouseButton and event.pressed:
_on_card_tapped()
# 3. Node._input โ global handler, fires for every input event in the tree.
# Use sparingly โ for global hotkeys, pause menus, debug shortcuts.
func _input(event: InputEvent) -> void:
if event.is_action_pressed("pause"):
pause_game()
Don't hard-code KEY_SPACE in your game logic. Map it to an action.
Project Settings โ Input Map โ add action end_turn. Bind it to Space, or a touch zone, or a controller button. In code:
func _input(event: InputEvent) -> void:
if event.is_action_pressed("end_turn"):
turn_manager.advance()
# Also useful:
if Input.is_action_pressed("move_right"): # continuous
if Input.is_action_just_pressed("jump"): # one-frame trigger
For a word/card game you won't have many actions โ maybe end_turn, submit_word, clear_selection. Map them, keep your game logic device-agnostic.
Touch is a stream of events. To recognize "tap vs drag" you track state.
extends Control
var drag_start: Vector2
var dragging := false
var DRAG_THRESHOLD := 10.0
func _gui_input(event: InputEvent) -> void:
if event is InputEventMouseButton:
if event.pressed:
drag_start = event.position
dragging = false
else:
if not dragging:
_on_tap()
else:
_on_drag_end(event.position)
elif event is InputEventMouseMotion:
if event.button_mask & MOUSE_BUTTON_LEFT:
if event.position.distance_to(drag_start) > DRAG_THRESHOLD:
dragging = true
_on_drag(event.position)
func _on_tap() -> void: print("tap")
func _on_drag(pos: Vector2) -> void: print("dragging at ", pos)
func _on_drag_end(pos: Vector2) -> void: print("drag end at ", pos)
With "Emulate Touch From Mouse" on, this same code handles both desktop and phone.
Godot has built-in drag-drop on Control nodes. Override three methods:
func _get_drag_data(_pos: Vector2) -> Variant:
# Return data to carry. Return null to cancel.
# Also set the drag preview here.
set_drag_preview(_make_preview())
return { "card": self, "letter": data.letter }
func _can_drop_data(_pos: Vector2, data: Variant) -> bool:
return data is Dictionary and data.has("card")
func _drop_data(_pos: Vector2, data: Variant) -> void:
var card: Card = data.card
accept_card(card)
func _make_preview() -> Control:
var ghost := TextureRect.new()
# configure ghost to look like the card
return ghost
This pattern handles all the event plumbing for you โ including multi-touch and cross-control drops. For Lexicon Duel, this is how cards will drag from the hand into a "word being built" slot.
Apple's Human Interface Guidelines and Google's Material guidelines both recommend ~44pt minimum tap targets. On a 1080x1920 reference viewport that's at least 88px. Cards should be 140+px wide to tap comfortably. Buttons 88ร88px minimum.
Tap something important? Buzz the phone. Godot exposes a cross-platform haptic call:
Input.vibrate_handheld(40) # 40ms buzz on mobile; no-op on desktop
Use sparingly โ every tap is annoying; a tap that finishes a word is delightful.
Once you install the Android export template (Module 7 โ don't do this yet), Godot can push a live build over USB. Flow:
We'll do this end-to-end in Module 7. Right now, just know it exists and is fast.
scenes/card.tscn from Module 1. If you made it a Node2D with an Area2D, you have option 2 input. Keep it.CanvasLayer to your main scene, with a Control (Full Rect), containing a Button in the top right anchored Top Right, labeled "End Turn".pressed signal to a method that prints "turn ended".