Voltar para o Blog
Quest Log

Input Map no Godot: Configurar Controles, Teclas e Rebinding

Teclado e controle de videogame conectados a um painel de mapeamento de ações no Godot

Aprenda a usar o Input Map do Godot: configure teclado e joystick, leia ações no código, ajuste deadzone e implemente rebinding de teclas em runtime.

Input Map no Godot: Configurar Controles, Teclas e Rebinding

Tem dois jeitos de ler input num jogo: perguntar "a tecla W está pressionada?" ou perguntar "o jogador quer andar pra frente?". O Input Map do Godot existe pra você fazer a segunda pergunta. Em vez de chumbar teclas no código, você define ações como jump e attack, mapeia teclado, mouse e joystick pra elas, e o script só conhece a ação.

A diferença parece pequena até você precisar adicionar suporte a controle, ou deixar o jogador trocar as teclas. Com tecla chumbada, isso vira caça ao KEY_SPACE espalhado por vinte scripts. Com o Input Map, é mexer num lugar só, ou em lugar nenhum, porque o rebinding em runtime mexe no mapa, não no código.

Esse tutorial cobre o ciclo completo no Godot 4: criar ações no editor, ler elas em GDScript, mapear teclado e joystick na mesma ação, ajustar deadzone e construir um sistema de rebinding que salva em disco. Todo código roda como está.

O que é o Input Map no Godot

O Input Map é uma tabela de ações que vive em Project Settings > Input Map. Cada ação tem um nome (jump, move_left, pause) e uma lista de eventos que disparam ela: uma tecla, um botão de mouse, um botão de joystick, um eixo de analógico. Vários eventos podem apontar pra mesma ação, e é assim que teclado e controle funcionam juntos sem nenhum if extra no código.

O Godot já vem com ações embutidas com prefixo ui_ (ui_accept, ui_left, etc.). Elas existem pro sistema de interface: navegar menu, confirmar botão. Dá pra usar em protótipo, mas pra gameplay crie as suas. Dois motivos práticos: se você mudar ui_left pra acomodar o movimento do personagem, quebra a navegação de todos os menus; e o nome ui_accept num script de pulo não diz nada, jump diz tudo.

Criando ações no editor

O caminho é curto:

  1. Abra Project > Project Settings > Input Map
  2. Digite o nome da ação no campo de cima (use snake_case: move_left, não Move Left) e clique em Add
  3. Clique no + ao lado da ação e pressione a tecla, botão de mouse ou botão do controle que quer mapear
  4. Repita o + pra adicionar mais eventos na mesma ação

Pra um plataforma 2D, o conjunto típico fica assim:

move_left   →  A, seta esquerda, analógico esquerdo (eixo X negativo), d-pad esquerda
move_right  →  D, seta direita, analógico esquerdo (eixo X positivo), d-pad direita
jump        →  Espaço, botão A do controle
attack      →  J, botão de mouse esquerdo, botão X do controle
pause       →  Esc, botão Start

Keycode vs physical keycode

Quando você adiciona uma tecla, o Godot pergunta se quer keycode ou physical keycode. A distinção importa pra quem joga em teclado que não é o seu.

O keycode é o caractere impresso na tecla. O physical keycode é a posição física dela no teclado. No layout francês AZERTY, a tecla na posição do nosso W imprime Z. Se você mapeou movimento por keycode W, o jogador francês vai andar com a mão torta. Mapeado por physical keycode, o WASD continua no mesmo lugar físico em qualquer layout.

A regra que eu sigo: movimento e ações de gameplay usam physical keycode (posição da mão importa, letra não). Atalhos ligados a uma letra específica, tipo I de inventário ou M de mapa, usam keycode (a letra é o mnemônico, então ela tem que seguir a letra). O editor já sugere physical por padrão no Godot 4, e na dúvida fique com ele.

Lendo as ações no código

Com as ações criadas, o script consulta a classe Input por nome. Os três métodos básicos:

func _physics_process(delta):
    # Verdadeiro enquanto segura. Movimento contínuo.
    if Input.is_action_pressed("move_left"):
        pass

    # Verdadeiro só no frame em que apertou. Pulo, tiro, interação.
    if Input.is_action_just_pressed("jump"):
        pass

    # Verdadeiro só no frame em que soltou. Cortar pulo, carregar e soltar.
    if Input.is_action_just_released("jump"):
        pass

Confundir is_action_pressed com is_action_just_pressed é o bug de input mais comum que existe. Pulo com is_action_pressed dispara todo frame enquanto o botão está apertado, e o personagem vira um foguete. Ação de momento usa just_pressed, estado contínuo usa pressed.

Pra movimento, em vez de testar ação por ação, use os métodos que devolvem o valor combinado:

func _physics_process(delta):
    # Um eixo: devolve float de -1 a 1. Perfeito pra plataforma.
    var direcao = Input.get_axis("move_left", "move_right")
    velocity.x = direcao * SPEED

    # Dois eixos: devolve Vector2 normalizado. Perfeito pra top-down.
    var input_dir = Input.get_vector("move_left", "move_right", "move_up", "move_down")
    velocity = input_dir * SPEED

    move_and_slide()

O get_vector resolve de graça um problema clássico: andar na diagonal com teclado. Somando os eixos na mão, a diagonal fica com comprimento 1.41 e o personagem anda 41% mais rápido em diagonal. O get_vector já limita o vetor a comprimento 1, e ainda aplica a deadzone do analógico corretamente.

Pra ações de evento (pausar, abrir menu), prefira o callback _input em vez de checar todo frame:

func _input(event):
    if event.is_action_pressed("pause"):
        get_tree().paused = not get_tree().paused

A divisão que funciona: movimento e estado contínuo em _physics_process com Input.get_axis ou get_vector; ações pontuais de UI e sistema em _input. E se uma tecla segurada estiver disparando repetido no _input, é o auto-repeat do teclado: filtre com if event.is_echo(): return.

Próximo nível
Quer aprender isso na prática?

No CursoGame.Dev você sai dos tutoriais soltos e constrói jogos publicáveis, com trilha progressiva, quests práticas e feedback real.

Conhecer a plataforma
+500 alunos4.9/5Garantia 7 dias

Mapeando joystick e gamepad

A parte boa: se você adicionou os eventos de joystick nas mesmas ações, o suporte a controle já está pronto. O mesmo Input.get_vector lê WASD e analógico esquerdo sem mudar uma linha. O Godot usa a base de dados de controles da SDL por baixo, então Xbox, PlayStation, Switch Pro e genéricos decentes funcionam com o mesmo mapeamento.

No Input Map, eventos de controle vêm em dois sabores:

  • Joypad Button: botões digitais (A/B/X/Y, bumpers, Start, clique do analógico). Ligado ou desligado.
  • Joypad Axis: eixos analógicos (direcionais e gatilhos). Valor contínuo de -1 a 1, e cada direção do eixo é um evento separado. "Left Stick X negativo" é uma entrada, "Left Stick X positivo" é outra.

Detectar conexão e desconexão do controle é um sinal:

func _ready():
    Input.joy_connection_changed.connect(_on_joy_connection_changed)

func _on_joy_connection_changed(device: int, connected: bool):
    if connected:
        print("Controle conectado: ", Input.get_joy_name(device))
    else:
        print("Controle desconectado no slot ", device)

Isso serve pra pausar o jogo quando o controle cai, ou pra trocar os ícones da UI entre teclado e gamepad.

Deadzone

Analógico em repouso nunca marca zero exato. Desgaste e folga mecânica deixam o stick reportando valores pequenos o tempo todo, e sem tratamento o personagem fica andando sozinho (o famoso stick drift aparecendo no seu jogo). A deadzone é o raio abaixo do qual o input é ignorado.

No Godot ela é configurada por ação, direto no Input Map (campo Deadzone ao lado do nome da ação, padrão 0.5). Pra movimento, 0.2 a 0.3 costuma ser um bom ponto de partida: o 0.5 padrão é conservador demais e come metade do curso do analógico, deixando o movimento com cara de liga/desliga. Pra ações que disparam com gatilho analógico, deadzone maior evita disparo acidental com um toque de leve.

Como get_axis e get_vector aplicam a deadzone das ações por você, não escreva tratamento manual. Ajuste o número no Input Map e teste com controle na mão.

Criando e modificando ações por código

Tudo que o editor faz no Input Map, a classe InputMap faz em runtime. É útil pra ação criada dinamicamente, e é a fundação do rebinding:

func _ready():
    if not InputMap.has_action("dash"):
        InputMap.add_action("dash")

        var tecla = InputEventKey.new()
        tecla.physical_keycode = KEY_SHIFT
        InputMap.action_add_event("dash", tecla)

        var botao = InputEventJoypadButton.new()
        botao.button_index = JOY_BUTTON_B
        InputMap.action_add_event("dash", botao)

Um detalhe que pega muita gente: mudanças via InputMap valem só pra sessão atual. Fechou o jogo, voltou tudo pro que está no Project Settings. Persistir é trabalho seu, e é o que o sistema de rebinding abaixo resolve. Pra voltar aos padrões do projeto a qualquer momento, existe InputMap.load_from_project_settings().

Rebinding de teclas em runtime

Deixar o jogador remapear os controles é acessibilidade básica, e no Godot o sistema inteiro cabe em dois passos: capturar o próximo evento de input e trocar os eventos da ação no InputMap.

O botão de remapear

A peça central é um botão que, ao ser clicado, entra em modo de escuta e atribui a próxima tecla pressionada à ação:

extends Button

@export var action_name: String = "jump"

var aguardando := false

func _ready():
    pressed.connect(_iniciar_captura)
    _atualizar_texto()

func _iniciar_captura():
    aguardando = true
    text = "Pressione uma tecla..."

func _input(event):
    if not aguardando:
        return
    if event is InputEventKey and event.pressed:
        InputMap.action_erase_events(action_name)
        InputMap.action_add_event(action_name, event)
        aguardando = false
        _atualizar_texto()
        # Consome o evento pra tecla não vazar pro jogo ou pra UI.
        get_viewport().set_input_as_handled()

func _atualizar_texto():
    var eventos = InputMap.action_get_events(action_name)
    if not eventos.is_empty() and eventos[0] is InputEventKey:
        text = eventos[0].as_text_physical_keycode()
    else:
        text = "Não mapeado"

Instancie um desses por ação na tela de opções, setando action_name no Inspector, e o menu de controles está de pé. Pra aceitar botões de controle também, acrescente or event is InputEventJoypadButton na checagem do _input.

Duas melhorias que valem num jogo de verdade: tratar Esc como cancelar (testar event.physical_keycode == KEY_ESCAPE antes de atribuir) e avisar quando a tecla escolhida já está em uso por outra ação, percorrendo as ações com InputMap.action_has_event(). Note também que o action_erase_events apaga todos os eventos da ação, incluindo os de joystick. Se o seu menu remapeia teclado e controle separados, apague só o evento antigo do tipo certo com InputMap.action_erase_event(action, evento_antigo) em vez de limpar tudo.

Salvando e carregando o mapeamento

Como o InputMap esquece tudo ao fechar, salve os eventos num ConfigFile dentro de user://. O ConfigFile serializa os objetos InputEvent direto, sem você converter nada na mão:

const CAMINHO_CONTROLES = "user://controles.cfg"

func salvar_controles():
    var config = ConfigFile.new()
    for action in InputMap.get_actions():
        if action.begins_with("ui_"):
            continue  # as ações de UI ficam fora do save
        config.set_value("controles", action, InputMap.action_get_events(action))
    config.save(CAMINHO_CONTROLES)

func carregar_controles():
    var config = ConfigFile.new()
    if config.load(CAMINHO_CONTROLES) != OK:
        return  # sem arquivo salvo, fica nos padrões do projeto
    for action in config.get_section_keys("controles"):
        if not InputMap.has_action(action):
            continue
        InputMap.action_erase_events(action)
        for evento in config.get_value("controles", action):
            InputMap.action_add_event(action, evento)

Chame salvar_controles() ao sair do menu de opções e carregar_controles() num autoload no início do jogo, e o mapeamento do jogador sobrevive entre sessões. O botão "restaurar padrões" da tela de opções é uma linha: InputMap.load_from_project_settings() seguido de um save.

Fechando

O Input Map é daqueles sistemas que custam dez minutos pra adotar e pagam o projeto inteiro. O resumo do que importa: ações com nome de intenção em vez de tecla, physical keycode pra movimento, teclado e joystick nos mesmos nomes, get_vector pra movimento e _input pra eventos, deadzone ajustada na ação, e rebinding com InputMap mais ConfigFile.

Se você ainda lê tecla direto no código, a migração é mecânica e rápida: crie as ações, troque os KEY_* por nomes, rode o jogo. A partir daí, adicionar suporte a controle ou uma tela de remapear deixa de ser refatoração e vira tarefa de uma tarde. Vai por etapas, testa com controle na mão, e o input do seu jogo para de ser fonte de bug pra virar coisa resolvida.