Voltar para o Blog
Quest Log

Ler Botão no Input System: GetKeyDown, Pressed e Hold

Botão de controle brilhando ao ser pressionado, com uma barra de carga enchendo ao lado de um canhão 2D

Como checar se um botão foi pressionado no New Input System da Unity: equivalentes de GetKeyDown, GetKey e GetKeyUp, Hold para carregar tiro e callbacks.

Ferramenta:UnityC#

Ler Botão no Input System: GetKeyDown, Pressed e Hold

Para checar se um botão foi pressionado no Unity New Input System, use WasPressedThisFrame() na action (ou wasPressedThisFrame direto no controle do teclado). Ele é o equivalente do Input.GetKeyDown: devolve true só no frame em que o botão desceu. Para "está segurando", use IsPressed(), e para "soltou agora", WasReleasedThisFrame(). Essa é a resposta curta para "unity new input system check if button is pressed".

A resposta longa importa porque é nela que mora o bug. Aqui você vai ver a tabela de equivalência completa, quando ler direto do teclado e quando ler pela action, como fazer um tiro que carrega enquanto o botão é segurado, como usar callbacks sem disparar duas vezes e por que o botão some quando você lê no lugar errado. O código é para Unity 6.3 LTS com o Input System já ativo, que é o padrão de projeto novo na Unity 6. Se você ainda não configurou as actions, comece pelo tutorial do New Input System do zero.

Unity new input system: check if button is pressed

O Input System tem dois níveis em que você pode perguntar por um botão:

  1. No controle físico: Keyboard.current.spaceKey, Mouse.current.leftButton, Gamepad.current.buttonSouth. Cada um é um ButtonControl com as propriedades wasPressedThisFrame, isPressed e wasReleasedThisFrame.
  2. Na action: InputAction com os métodos WasPressedThisFrame(), IsPressed(), WasReleasedThisFrame() e WasPerformedThisFrame().

Os nomes são quase iguais de propósito. A diferença é que a action responde a qualquer binding ligado a ela (espaço, botão A, toque), enquanto o controle físico responde só a ele mesmo.

GetKeyDown, GetKey e GetKeyUp no novo sistema

A tabela que você vai consultar mais:

Input Manager antigoInput System, controle físicoInput System, action
Input.GetKeyDown(KeyCode.Space)Keyboard.current.spaceKey.wasPressedThisFramejump.WasPressedThisFrame()
Input.GetKey(KeyCode.Space)Keyboard.current.spaceKey.isPressedjump.IsPressed()
Input.GetKeyUp(KeyCode.Space)Keyboard.current.spaceKey.wasReleasedThisFramejump.WasReleasedThisFrame()
Input.GetMouseButtonDown(0)Mouse.current.leftButton.wasPressedThisFrameattack.WasPressedThisFrame()
Input.GetButtonDown("Jump")(não existe, é conceito de action)jump.WasPressedThisFrame()

Também dá para indexar o teclado pela enum Key: Keyboard.current[Key.Space] devolve o mesmo KeyControl de Keyboard.current.spaceKey. Isso é útil quando a tecla vem de uma variável, por exemplo um campo no Inspector.

Arquivo KeyboardDebug.cs:

using UnityEngine;
using UnityEngine.InputSystem;

public class KeyboardDebug : MonoBehaviour
{
    [SerializeField] private Key debugKey = Key.F1;

    private void Update()
    {
        Keyboard keyboard = Keyboard.current;
        if (keyboard == null)
        {
            return;
        }

        if (keyboard[debugKey].wasPressedThisFrame)
        {
            Debug.Log("Tecla de debug apertada: " + debugKey);
        }

        if (keyboard.leftShiftKey.isPressed)
        {
            Debug.Log("Shift segurado");
        }

        if (keyboard[debugKey].wasReleasedThisFrame)
        {
            Debug.Log("Tecla de debug solta");
        }
    }
}

Repare no if (keyboard == null). Keyboard.current é o último teclado usado ou conectado; num celular sem teclado ele é null, e o script quebra com NullReferenceException sem essa checagem. O mesmo vale para Gamepad.current quando não há controle ligado.

Ler direto do teclado vs pela action

Ler Keyboard.current direto é ótimo para ferramenta de desenvolvedor: tecla de debug, atalho de screenshot, cheat de teste. Ninguém vai remapear o F1 do seu console de debug, e você não quer poluir o asset de actions com isso.

Para tudo que o jogador faz (pular, atacar, interagir, abrir inventário), leia pela action. Três razões práticas:

  • O mesmo código funciona no controle e no touch sem mudar uma linha.
  • A tela de remapear controles mexe nos bindings da action; se o seu código lê spaceKey, o remapeamento não tem efeito.
  • Desligar o map Player num menu de pausa corta todas as actions de uma vez.

Arquivo PlayerJump.cs, lendo pela action padrão do asset da Unity 6:

using UnityEngine;
using UnityEngine.InputSystem;

public class PlayerJump : MonoBehaviour
{
    [SerializeField] private Rigidbody2D rb;
    [SerializeField] private float jumpForce = 8f;
    [SerializeField] private float lowJumpMultiplier = 0.5f;

    private InputAction jumpAction;
    private bool jumpRequested;
    private bool jumpReleased;

    private void Awake()
    {
        jumpAction = InputSystem.actions.FindAction("Player/Jump", true);
    }

    private void Update()
    {
        if (jumpAction.WasPressedThisFrame())
        {
            jumpRequested = true;
        }

        if (jumpAction.WasReleasedThisFrame())
        {
            jumpReleased = true;
        }
    }

    private void FixedUpdate()
    {
        if (jumpRequested)
        {
            rb.linearVelocity = new Vector2(rb.linearVelocity.x, jumpForce);
            jumpRequested = false;
        }

        if (jumpReleased)
        {
            if (rb.linearVelocity.y > 0f)
            {
                rb.linearVelocity = new Vector2(rb.linearVelocity.x, rb.linearVelocity.y * lowJumpMultiplier);
            }
            jumpReleased = false;
        }
    }
}

Esse script já faz pulo de altura variável: soltar o botão cedo corta a velocidade de subida pela metade. É o WasReleasedThisFrame trabalhando. Se quiser deixar o pulo ainda mais justo, com tolerância na borda da plataforma e buffer de pulo, o post sobre coyote time e jump buffer mostra a lógica, que vale igual em qualquer engine.

Hold: segurar para carregar

Existem dois jeitos de fazer "segura para carregar", e eles servem para coisas diferentes.

Interação Hold no asset. Abra o asset de actions, selecione a action (ou um binding dela), clique no + de Interactions e escolha Hold. Com isso a action só entra em performed depois que o botão fica pressionado pelo tempo definido. Se o campo Hold Time ficar no padrão, a interação usa InputSettings.defaultHoldTime, que é 0,4 segundo. Você muda o valor global em Edit > Project Settings > Input System Package ou desmarca Default e digita um tempo só para aquele binding.

O ciclo da Hold é: started quando o botão desce, performed quando o tempo é atingido, canceled quando o botão sobe (antes ou depois do tempo). Serve muito bem para "segure E para abrir a porta" ou "segure para pular a cutscene", em que o resultado é binário: completou ou não.

Medir o tempo você mesmo. Para tiro carregado, em que a força depende de quanto tempo o jogador segurou, a Hold atrapalha: você quer o valor contínuo, não só "passou de 0,4 s". Deixe a action como Button simples e meça.

Arquivo ChargeShot.cs:

using UnityEngine;
using UnityEngine.InputSystem;

public class ChargeShot : MonoBehaviour
{
    [SerializeField] private Rigidbody2D projectilePrefab;
    [SerializeField] private Transform firePoint;
    [SerializeField] private float minSpeed = 6f;
    [SerializeField] private float maxSpeed = 20f;
    [SerializeField] private float maxChargeTime = 1.2f;

    private InputAction attackAction;
    private float chargeStartTime;
    private bool isCharging;

    public float ChargePercent { get; private set; }

    private void Awake()
    {
        attackAction = InputSystem.actions.FindAction("Player/Attack", true);
    }

    private void Update()
    {
        if (attackAction.WasPressedThisFrame())
        {
            isCharging = true;
            chargeStartTime = Time.time;
        }

        if (isCharging)
        {
            float held = Time.time - chargeStartTime;
            ChargePercent = Mathf.Clamp01(held / maxChargeTime);
        }

        if (isCharging && attackAction.WasReleasedThisFrame())
        {
            Fire(ChargePercent);
            isCharging = false;
            ChargePercent = 0f;
        }
    }

    private void Fire(float charge)
    {
        float speed = Mathf.Lerp(minSpeed, maxSpeed, charge);
        Rigidbody2D projectile = Instantiate(projectilePrefab, firePoint.position, firePoint.rotation);
        projectile.linearVelocity = firePoint.right * speed;
    }
}

O ChargePercent público (só leitura) é o que a barra de carga da UI lê para encher. Repare que o tiro sai no soltar, não no apertar, e que a velocidade é interpolada entre mínimo e máximo. Um toque rápido dispara o tiro fraco; segurar 1,2 segundo dispara o forte. Esse é o comportamento que o jogador espera em Mega Man e similares.

Até aqui são peças soltas: um pulo, um tiro, uma barra. O que separa protótipo de jogo é juntar isso com animação de carga, som que sobe de tom, câmera que treme no disparo e um inimigo que reage, sem virar um script de 600 linhas. Essa organização de projeto é o que o curso de Unity trabalha em cima de jogos completos, e é a parte que nenhum tutorial de função isolada cobre.

Curso de Unity
Quer fazer seu primeiro jogo na Unity?

No curso de Unity 6 da CursoGame.Dev você vai do zero ao primeiro jogo, com um desafio prático em cada level e suporte individual no WhatsApp quando travar.

Conhecer o curso de Unity
Suporte no WhatsAppAcesso vitalícioGarantia de 7 dias

Callbacks performed e canceled

Polling (perguntar todo frame) é o mais simples. A alternativa é assinar eventos da action: started, performed e canceled. O evento roda quando a coisa acontece, e o seu Update fica limpo.

Arquivo InteractHold.cs, usando uma action Interact com a interação Hold configurada no asset:

using UnityEngine;
using UnityEngine.InputSystem;
using UnityEngine.InputSystem.Interactions;

public class InteractHold : MonoBehaviour
{
    private InputAction interactAction;

    private void Awake()
    {
        interactAction = InputSystem.actions.FindAction("Player/Interact", true);
    }

    private void OnEnable()
    {
        interactAction.started += OnInteractStarted;
        interactAction.performed += OnInteractPerformed;
        interactAction.canceled += OnInteractCanceled;
    }

    private void OnDisable()
    {
        interactAction.started -= OnInteractStarted;
        interactAction.performed -= OnInteractPerformed;
        interactAction.canceled -= OnInteractCanceled;
    }

    private void OnInteractStarted(InputAction.CallbackContext context)
    {
        if (context.interaction is HoldInteraction)
        {
            Debug.Log("Começou a segurar");
        }
    }

    private void OnInteractPerformed(InputAction.CallbackContext context)
    {
        Debug.Log("Segurou o tempo todo: porta aberta. Duração: " + context.duration);
    }

    private void OnInteractCanceled(InputAction.CallbackContext context)
    {
        Debug.Log("Soltou o botão");
    }
}

O par OnEnable / OnDisable não é enfeite. Assinar sem desassinar significa que o evento continua chamando um objeto destruído (MissingReferenceException) ou, se o objeto for desativado e reativado, chama o método duas vezes.

Um detalhe que confunde: numa action com Hold, WasPressedThisFrame() continua devolvendo true no frame em que o botão desce, porque ele olha o ponto de pressão e ignora as interações. Quem respeita a Hold é WasPerformedThisFrame(), que só fica true no frame em que o tempo foi atingido. Se você colocou Hold e o código continua disparando na hora, é porque está lendo o método errado.

Quando usar evento e quando usar polling? Polling para coisas que alimentam física ou estado contínuo (movimento, carga). Evento para ações pontuais que não dependem do frame (abrir menu, interagir, trocar arma).

Outra interação que resolve caso comum sem código é a Press. Ela tem um campo Trigger Behavior com três opções: Press Only (dispara ao apertar), Release Only (dispara ao soltar) e Press And Release (dispara nos dois). Se o seu ataque deve sair no soltar do botão, como num arco que só atira quando o dedo levanta, troque para Release Only no binding e o código continua lendo performed sem saber da regra. Isso deixa o designer ajustar a sensação no asset, sem abrir o script.

E a Tap faz o contrário da Hold: só entra em performed se o botão for solto antes de InputSettings.defaultTapTime (0,2 segundo por padrão). Dá para colocar Tap e Hold no mesmo binding para ter "toque rápido ataca, segurar defende" numa tecla só. A ordem na lista de interações importa: a Unity testa de cima para baixo, então deixe a Tap antes da Hold.

Erros comuns: input perdido e leitura dupla

Input perdido no FixedUpdate. WasPressedThisFrame() olha o frame de Update. O FixedUpdate roda em passo fixo (0,02 s por padrão), então em jogo rodando a 144 fps existem vários frames sem física; se o aperto cair num deles, o seu if dentro do FixedUpdate nunca vê. A correção é sempre a mesma: lê no Update, guarda em bool, consome no FixedUpdate, como no PlayerJump. Existe também a opção de mudar Update Mode do Input System para Process Events In Fixed Update nas configurações do pacote, mas aí quem perde aperto é o Update, e a UI sente.

Leitura dupla. Acontece quando o mesmo aperto é tratado por dois caminhos: um performed += e um WasPressedThisFrame() no mesmo script, ou um Player Input com Send Messages mais um script lendo InputSystem.actions. O pulo sai duas vezes, ou o menu abre e fecha no mesmo frame. Escolha um caminho por action.

Evento assinado em Awake sem desassinar. O objeto é destruído na troca de cena, a action continua viva (project-wide actions duram o jogo todo) e o callback chama um objeto morto.

Gatilho analógico que nunca "pressiona". Gatilhos de gamepad são analógicos. O ponto em que eles contam como pressionados vem de InputSettings.defaultButtonPressPoint (0,5 por padrão). Se quiser que meio aperto já conte, ajuste o Press Point na interação ou no binding.

Próximo passo

Monte uma cena de teste com três botões: um que pula com altura variável, um que carrega tiro e um que só abre a porta se segurado. Leia os três pelo map Player e confira no Window > Analysis > Input Debugger o que chega de cada dispositivo. Se você usa Godot também, compare com o Input Map da Godot, em que is_action_just_pressed cumpre o papel do WasPressedThisFrame.

Perguntas frequentes

Qual o equivalente do GetKeyDown no New Input System?

Pela action, use action.WasPressedThisFrame(). Direto no teclado, use Keyboard.current.spaceKey.wasPressedThisFrame (ou Keyboard.current[Key.Space].wasPressedThisFrame). Os dois devolvem true só no frame em que o botão desceu, igual ao Input.GetKeyDown.

Como saber se o botão está sendo segurado no Input System?

Use action.IsPressed() na action ou isPressed no controle (Keyboard.current.spaceKey.isPressed). Ele devolve true em todos os frames enquanto o botão estiver abaixo do ponto de pressão, como o antigo Input.GetKey.

Qual a duração padrão da interação Hold?

Se você deixar o campo Hold Time em zero ou com Default marcado, a interação usa InputSettings.defaultHoldTime, que vem em 0,4 segundo. Dá para mudar o valor global em Project Settings, Input System Package, ou definir um tempo por binding.

Por que meu botão às vezes não é detectado?

Quase sempre é leitura dentro do FixedUpdate. WasPressedThisFrame vale para o frame de Update; em frames em que a física não roda, o aperto é perdido. Leia no Update, guarde numa variável e consuma no FixedUpdate.