Controles Touch no Godot 4: Joystick Virtual e Botoes na Tela

Aprenda a criar controles touch no Godot 4 com joystick virtual analogico e botoes na tela, lendo toque, multitouch e direcao em Vector2 normalizado.
Controles Touch no Godot 4: Joystick Virtual e Botoes na Tela
Fazer controles touch no Godot e diferente de fazer controle de teclado por um motivo simples: no celular nao existe tecla. Existe um dedo que toca, arrasta e solta num ponto da tela, e cabe a voce transformar essa coordenada em intencao de jogo. Este tutorial monta o pacote completo para mobile no Godot 4: detectar o toque, criar botoes na tela e construir um joystick virtual analogico que devolve um Vector2 de direcao, igual ao analogico de um controle de verdade.
A boa noticia e que o Godot ja entrega o toque pronto. Voce nao precisa de plugin nem de configuracao especial para o evento de dedo chegar no seu codigo. O trabalho real esta em interpretar esse evento com ergonomia, em fazer o multitouch funcionar para mover e atirar ao mesmo tempo, e em desenhar um controle mobile que nao esconda o personagem atras do dedo do jogador.
Como o Godot entrega o toque na tela
Todo toque no Godot 4 chega por dois eventos. O InputEventScreenTouch dispara quando um dedo encosta na tela (pressed == true) e quando solta (pressed == false). O InputEventScreenDrag dispara enquanto o dedo se move sem soltar. Os dois trazem position, a coordenada do toque, e index, o numero do dedo, que e a chave do multitouch.
A leitura crua acontece no _input. Repare na tipagem: todo argumento e variavel tem tipo, e isso e o padrao que voce deve seguir no Godot 4, porque o editor avisa erro cedo e o codigo roda mais rapido.
extends Node2D
func _input(event: InputEvent) -> void:
if event is InputEventScreenTouch:
var toque: InputEventScreenTouch = event
if toque.pressed:
print("Dedo ", toque.index, " encostou em ", toque.position)
else:
print("Dedo ", toque.index, " soltou")
elif event is InputEventScreenDrag:
var arraste: InputEventScreenDrag = event
print("Dedo ", arraste.index, " arrastando para ", arraste.position)
O index comeca em 0 para o primeiro dedo, 1 para o segundo, e assim por diante. Quando um dedo solta, o Godot pode reaproveitar o index para o proximo toque, entao nunca assuma que o dedo 0 e sempre o mesmo dedo: assuma que ele e o dedo que esta com index 0 agora.
Emular mouse e touch nas configuracoes
Em Project Settings > General > Input Devices > Pointing voce acha duas opcoes que poupam tempo. Emulate Touch From Mouse faz o clique do mouse virar InputEventScreenTouch, util para testar o controle touch no PC sem subir build no celular toda hora. Emulate Mouse From Touch faz o caminho inverso, transformando toque em evento de mouse, o que ajuda se voce tem codigo ou UI antiga que so escuta InputEventMouseButton.
Para um jogo mobile de verdade eu deixo a emulacao de touch a partir do mouse ligada durante o desenvolvimento e testo o multitouch no aparelho real, porque o mouse tem so um ponteiro e nunca reproduz dois dedos ao mesmo tempo.
Botoes na tela com TouchScreenButton e InputMap
Para botoes de acao (pular, atirar, interagir) o no dedicado e o TouchScreenButton. Voce arrasta ele para a cena, define uma textura normal e uma de pressionado, ajusta o shape (a area que responde ao toque) e mapeia ele a uma acao do InputMap pelo campo Action. A partir dai a acao se comporta como qualquer outra: o resto do seu jogo le Input.is_action_pressed("atirar") sem saber que veio de um toque.
Se voce ja organiza seu input por acoes, esse mapeamento e o que une teclado, controle e touch num so lugar. Vale revisar como montar essas acoes no guia de Input Map e controles no Godot, porque o TouchScreenButton so brilha quando aponta para uma acao bem nomeada.
Quando voce quer estados visuais ricos, layout que se adapta a varias telas ou navegacao de menu, use Button dentro de Container em vez de TouchScreenButton. O Button e um no de Control, entao herda ancoras e responsividade. Para gameplay puro, porem, o TouchScreenButton ganha por ter passband (deixa o toque continuar para outros nos) e o sinal released separado do pressed.
Quando precisar disparar logica direto, sem passar pelo InputMap, conecte os sinais:
extends TouchScreenButton
signal tiro_solicitado
func _ready() -> void:
pressed.connect(_ao_pressionar)
func _ao_pressionar() -> void:
tiro_solicitado.emit()
Uma observacao de ergonomia: o shape do TouchScreenButton nao precisa ter o tamanho exato da arte. Deixe a area de toque maior que o desenho visivel. O dedo erra mais que o olho, e uma margem invisivel de alguns pixels ao redor do botao reduz muito o toque que falha. Para escolher resolucao base e ancoras que mantenham esses botoes no lugar em qualquer aparelho, o post sobre criar jogo direto pelo celular ajuda a fechar o ciclo de UI mobile.
Construindo o joystick virtual analogico
Aqui esta o coracao do controle mobile. Um joystick virtual tem duas partes visuais: a base, um circulo fixo, e o knob (o "polegar"), um circulo menor que segue o dedo dentro da base. A direcao que o jogo usa nasce da posicao do knob em relacao ao centro da base, convertida num Vector2 de comprimento 0 a 1.
A matematica e direta. Pegue o vetor do centro da base ate o dedo. Se o comprimento desse vetor passar do raio maximo, limite ele ao raio (o knob nao sai da base). Para a direcao normalizada que o personagem usa, divida o vetor pelo raio: no centro da base da Vector2.ZERO, na borda da o vetor de comprimento 1.
Monte a cena assim: um Control ou Node2D chamado JoystickVirtual, com dois filhos TextureRect ou Sprite2D, Base e Knob. O script abaixo e completo e tipado:
extends Control
@export var raio_maximo: float = 80.0
@export var deadzone: float = 0.15
@onready var base: TextureRect = $Base
@onready var knob: TextureRect = $Knob
var direcao: Vector2 = Vector2.ZERO
var dedo_ativo: int = -1
var centro_base: Vector2 = Vector2.ZERO
func _ready() -> void:
centro_base = base.global_position + base.size * 0.5
_resetar_knob()
func _input(event: InputEvent) -> void:
if event is InputEventScreenTouch:
_processar_toque(event)
elif event is InputEventScreenDrag:
_processar_arraste(event)
func _processar_toque(toque: InputEventScreenTouch) -> void:
if toque.pressed and dedo_ativo == -1:
# So aceita o toque que cair dentro da area do joystick.
if toque.position.distance_to(centro_base) <= raio_maximo:
dedo_ativo = toque.index
_atualizar_knob(toque.position)
elif not toque.pressed and toque.index == dedo_ativo:
dedo_ativo = -1
_resetar_knob()
func _processar_arraste(arraste: InputEventScreenDrag) -> void:
if arraste.index == dedo_ativo:
_atualizar_knob(arraste.position)
func _atualizar_knob(posicao_dedo: Vector2) -> void:
var offset: Vector2 = posicao_dedo - centro_base
offset = offset.limit_length(raio_maximo)
knob.global_position = centro_base + offset - knob.size * 0.5
var bruta: Vector2 = offset / raio_maximo
if bruta.length() < deadzone:
direcao = Vector2.ZERO
else:
direcao = bruta
func _resetar_knob() -> void:
direcao = Vector2.ZERO
knob.global_position = centro_base - knob.size * 0.5
O ponto importante e o dedo_ativo. Ele guarda o index do dedo que controla esse joystick e ignora qualquer outro toque. Sem isso, um segundo dedo na tela bagunçaria o analogico. Com isso, o joystick so responde ao dedo certo e o resto da tela fica livre para outros controles.
O limit_length(raio_maximo) faz dois trabalhos de uma vez: prende o knob na borda quando o dedo passa do raio e garante que a direcao bruta nunca passe de 1 depois da divisao. O deadzone zera tremidas minimas do dedo perto do centro, igual a deadzone de um analogico fisico.
Usando a direcao no personagem
Com o joystick devolvendo direcao, o personagem so consome esse Vector2. Para um jogo top-down:
extends CharacterBody2D
@export var velocidade: float = 220.0
@export var joystick_path: NodePath
@onready var joystick: Control = get_node(joystick_path)
func _physics_process(delta: float) -> void:
var direcao: Vector2 = joystick.direcao
velocity = direcao * velocidade
move_and_slide()
Como direcao ja vem com comprimento entre 0 e 1, o personagem anda mais devagar quando o dedo esta perto do centro e em velocidade cheia na borda, exatamente como um analogico. Se voce quisesse so as oito direcoes de um d-pad, bastaria normalizar e arredondar, mas o charme do joystick virtual e justamente o controle analogico fino.
Multitouch: mover e atirar ao mesmo tempo
A regra que torna o multitouch confiavel ja esta no codigo acima: cada controle reivindica um index e ignora os demais. Coloque o joystick na metade esquerda da tela e o botao de tiro na metade direita, e os dois funcionam ao mesmo tempo porque cada um cuida do seu dedo.
Se voce monta o botao de tiro tambem por _input em vez de TouchScreenButton, aplique a mesma logica de zona. Aceite o toque so quando ele cair na area do botao e guarde o index dele:
extends Node2D
@export var area_tiro: Rect2 = Rect2(540, 360, 200, 200)
var dedo_tiro: int = -1
func _input(event: InputEvent) -> void:
if event is InputEventScreenTouch:
var toque: InputEventScreenTouch = event
if toque.pressed and dedo_tiro == -1:
if area_tiro.has_point(toque.position):
dedo_tiro = toque.index
_atirar()
elif not toque.pressed and toque.index == dedo_tiro:
dedo_tiro = -1
func _atirar() -> void:
print("tiro disparado")
Repare que o joystick reivindica dedos na area dele e o botao de tiro reivindica dedos na area dele. Como as duas areas nao se sobrepoem, um dedo arrastando o joystick e outro tocando o botao convivem sem conflito. Esse e o padrao de zonas de toque: dividir a tela em regioes e cada controle so escuta a sua. Numa tela pequena, zonas bem separadas valem mais que controles bonitos amontoados.
Ergonomia: o detalhe que separa controle bom de ruim
Codigo correto nao garante controle confortavel. Tres cuidados fazem a maior diferenca no mobile.
Primeiro, tamanho da area de toque. Mire em pelo menos 48 dp de lado por elemento, perto de 9 milimetros, a largura media da ponta do dedo. Botao menor que isso vira loteria. Lembre que a area que responde ao toque pode ser maior que a arte desenhada, e geralmente deve ser.
Segundo, nao cubra o personagem com o dedo. O erro classico e por o joystick no centro de baixo: o polegar do jogador tapa justamente a acao. Empurre o joystick para o canto inferior, longe do centro vertical onde a camera costuma manter o personagem. Deixe a parte de cima da tela limpa de controles, porque e ali que o olho fica.
Terceiro, considere o joystick dinamico. Em vez de uma base fixa, muitos jogos mobile fazem a base nascer onde o dedo encostou na metade esquerda da tela. O ajuste no script e pequeno: no toque dentro da zona, reposicione centro_base para toque.position, mostre a base ali e siga a mesma matematica. O jogador nunca precisa cacar o joystick com o olhar, porque ele aparece debaixo do dedo. Esse padrao some o atrito do controle mobile e quase nao tem custo.
Por fim, cuide do desempenho. Controles touch desenham nos da UI por cima do jogo todo frame, e em aparelho fraco isso soma. Vale ler o material sobre otimizacao de jogos mobile no Godot para garantir que a interface nao roube quadros do gameplay.
Proximo passo pratico
Monte uma cena de teste enxuta: um CharacterBody2D com sprite no centro, o no JoystickVirtual ancorado no canto inferior esquerdo e um TouchScreenButton no canto inferior direito mapeado a uma acao de tiro. Ligue Emulate Touch From Mouse e rode no editor para validar a logica. Depois suba a build no celular e teste com os dois polegares: ande com a esquerda enquanto atira com a direita, e ajuste raio_maximo, deadzone e o tamanho das areas ate o controle parecer natural na sua mao.
Quando o controle estiver afinado, o passo seguinte e levar o jogo para o aparelho de forma definitiva. O caminho completo de build e assinatura esta no guia de exportar o jogo Godot para Android, e com os controles touch resolvidos voce ja tem a metade mais dificil do mobile pronta.
Perguntas frequentes
Preciso ativar alguma coisa para detectar toque no Godot 4?
O toque ja chega como InputEventScreenTouch e InputEventScreenDrag sem configuracao. So ative "Emulate Touch From Mouse" em Project Settings se quiser testar com o mouse no PC, e "Emulate Mouse From Touch" se algum sistema antigo seu so escuta clique de mouse.
Como faco o joystick virtual devolver uma direcao normalizada?
Pegue o vetor do dedo menos o centro da base, divida pelo raio maximo e use limit_length(1.0). Assim a direcao fica em Vector2 com comprimento de 0 a 1, igual a um analogico de controle.
Como movo e atiro ao mesmo tempo com touch?
Use o index de cada toque (event.index). Cada dedo tem um index proprio, entao o joystick guarda o index que o controla e ignora os outros, deixando o segundo dedo livre para o botao de tiro. Isso e multitouch.
TouchScreenButton ou Control/Button para os botoes na tela?
TouchScreenButton e o no feito para isso: tem action_released, passband e shape de toque proprios. Use Button dentro de Containers quando quiser layout responsivo e estados visuais de UI. Para gameplay rapido, TouchScreenButton mapeado a uma acao do InputMap costuma bastar.
Qual o tamanho minimo de uma area de toque confortavel?
Mire em pelo menos 48 dp de lado, o equivalente a uns 9 milimetros, que e a largura media da ponta do dedo. Botoes menores que isso geram toque errado e frustracao, principalmente em telas pequenas.


