Voltar para o Blog
Quest Log

Touch na Unity: Toque, Arrastar e Multitoque no Celular

Tela de celular com dois pontos de toque brilhando afastando-se para dar zoom num mapa de jogo 2D

Unity touch input com o Input System: toque simples pela action, EnhancedTouch para arrastar e multitoque, pinça para zoom e como testar no editor sem celular.

Ferramenta:UnityC#

Touch na Unity: Toque, Arrastar e Multitoque no Celular

Para ler Unity touch input no Input System você tem dois caminhos: actions com bindings de <Touchscreen> para um toque simples, ou a API EnhancedTouch para multitoque, arrastar e gestos. A action é o jeito certo quando o toque substitui um clique ou um botão; EnhancedTouch é o jeito certo quando você precisa saber de cada dedo separado.

Aqui você monta os dois com código para Unity 6.3 LTS: toque simples pela action, leitura de todos os dedos com EnhancedTouch, arrastar um objeto com o dedo, pinça para zoom de câmera e como testar tudo no editor sem plugar o celular a cada mudança.

Unity touch input: duas formas

A diferença prática entre os dois caminhos:

Action com <Touchscreen>EnhancedTouch
Quantos dedosUm (o primário) por actionTodos
Funciona com mouse sem código extraSim, se a action tiver binding de <Pointer>Só com Touch Simulation
RemapeávelSimNão se aplica
Melhor paraTocar para pular, tocar para atirar, tocar num botão do mundoArrastar, pinça, rotação, joystick virtual próprio

A regra que funciona bem: se o mesmo gesto existe no PC como clique, use action. Se o gesto só existe no celular, use EnhancedTouch.

Toque simples com Input Actions

O asset padrão da Unity 6 já tem, no map UI, a action Point (posição) e a Click, ligadas a mouse, caneta e toque. Para gameplay, prefira criar as suas no map Player, para poder desligar UI e gameplay separadamente:

  1. Abra InputSystem_Actions, map Player, + em Actions e crie TouchPress: Action Type Button, binding Touchscreen > Primary Touch > Press.
  2. Crie TouchPosition: Action Type Value, Control Type Vector2, binding Touchscreen > Primary Touch > Position.
  3. Se quiser testar com mouse, adicione em cada uma o binding equivalente de Pointer (<Pointer>/press e <Pointer>/position).

Arquivo TapToJump.cs:

using UnityEngine;
using UnityEngine.InputSystem;

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

    private InputAction pressAction;
    private InputAction positionAction;
    private bool jumpRequested;

    private void Awake()
    {
        pressAction = InputSystem.actions.FindAction("Player/TouchPress", true);
        positionAction = InputSystem.actions.FindAction("Player/TouchPosition", true);
    }

    private void Update()
    {
        if (pressAction.WasPressedThisFrame())
        {
            Vector2 screen = positionAction.ReadValue<Vector2>();
            bool rightHalf = screen.x > Screen.width * 0.5f;
            if (rightHalf)
            {
                jumpRequested = true;
            }
        }
    }

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

Esse é o controle de muito jogo mobile casual: metade direita da tela pula, a esquerda fica livre para outra coisa. O primary touch é, pela documentação, normalmente o primeiro dedo que encostou, e é ele que dirige o ponteiro e a UI.

EnhancedTouch: ativar e ler toques

EnhancedTouch precisa ser ligado explicitamente. Depois disso, Touch.activeTouches traz todos os dedos que estão na tela neste frame, cada um com fase, posição e um Finger estável.

Arquivo TouchReader.cs:

using UnityEngine;
using UnityEngine.InputSystem.EnhancedTouch;
using Touch = UnityEngine.InputSystem.EnhancedTouch.Touch;
using TouchPhase = UnityEngine.InputSystem.TouchPhase;

public class TouchReader : MonoBehaviour
{
    private void OnEnable()
    {
        EnhancedTouchSupport.Enable();
    }

    private void OnDisable()
    {
        EnhancedTouchSupport.Disable();
    }

    private void Update()
    {
        foreach (Touch touch in Touch.activeTouches)
        {
            if (touch.phase == TouchPhase.Began)
            {
                Debug.Log("Dedo " + touch.finger.index + " encostou em " + touch.screenPosition);
            }
            else if (touch.phase == TouchPhase.Ended || touch.phase == TouchPhase.Canceled)
            {
                Debug.Log("Dedo " + touch.finger.index + " saiu depois de " + (touch.time - touch.startTime) + " s");
            }
        }
    }
}

As duas linhas de using com alias resolvem o erro mais comum de quem começa com EnhancedTouch: Touch e TouchPhase existem também no namespace UnityEngine (sistema antigo), e sem o alias o compilador acusa referência ambígua.

O finger.index é o que você usa para seguir um dedo específico ao longo de vários frames. Um Touch é o estado de um dedo num frame; o Finger é o dedo em si e continua o mesmo do Began até o Ended. A documentação do pacote destaca que essas APIs não geram lixo para o garbage collector, então ler em todo Update é seguro em celular.

Se você prefere eventos a percorrer a lista todo frame, EnhancedTouch também tem: Touch.onFingerDown, Touch.onFingerMove e Touch.onFingerUp, todos estáticos e recebendo um Finger. Assine no OnEnable e desassine no OnDisable, igual a qualquer evento de action. Trocando os métodos do TouchReader.cs por estes:

private void OnEnable()
{
    EnhancedTouchSupport.Enable();
    Touch.onFingerDown += HandleFingerDown;
}

private void OnDisable()
{
    Touch.onFingerDown -= HandleFingerDown;
    EnhancedTouchSupport.Disable();
}

private void HandleFingerDown(Finger finger)
{
    Debug.Log("Dedo " + finger.index + " em " + finger.screenPosition);
}

Evento é melhor para coisas pontuais (tocou, soltou). Para algo que muda continuamente, como arrastar, o laço no Update deixa o fluxo mais fácil de ler e depurar, e é o que uso no próximo exemplo. Um detalhe sobre EnhancedTouchSupport.Enable() e Disable(): os dois funcionam em contagem, então vários scripts podem ligar e desligar sem um derrubar o outro, desde que cada Enable tenha o seu Disable.

Arrastar objeto com o dedo

Arrastar é o primeiro gesto de verdade: pegar a peça do puzzle, a carta, a unidade. O fluxo é: no Began, ver se o dedo encostou em algo arrastável; no Moved, mover; no Ended, soltar.

Arquivo TouchDrag2D.cs:

using UnityEngine;
using UnityEngine.InputSystem.EnhancedTouch;
using Touch = UnityEngine.InputSystem.EnhancedTouch.Touch;
using TouchPhase = UnityEngine.InputSystem.TouchPhase;

public class TouchDrag2D : MonoBehaviour
{
    [SerializeField] private Camera cam;
    [SerializeField] private LayerMask draggableMask;

    private Transform dragged;
    private int dragFingerIndex = -1;
    private Vector3 grabOffset;

    private void OnEnable()
    {
        EnhancedTouchSupport.Enable();
    }

    private void OnDisable()
    {
        EnhancedTouchSupport.Disable();
    }

    private void Update()
    {
        foreach (Touch touch in Touch.activeTouches)
        {
            Vector3 world = ScreenToWorld(touch.screenPosition);

            if (touch.phase == TouchPhase.Began && dragged == null)
            {
                Collider2D hit = Physics2D.OverlapPoint(world, draggableMask);
                if (hit != null)
                {
                    dragged = hit.transform;
                    dragFingerIndex = touch.finger.index;
                    grabOffset = dragged.position - world;
                }
            }
            else if (touch.finger.index == dragFingerIndex)
            {
                if (touch.phase == TouchPhase.Moved || touch.phase == TouchPhase.Stationary)
                {
                    dragged.position = world + grabOffset;
                }
                else if (touch.phase == TouchPhase.Ended || touch.phase == TouchPhase.Canceled)
                {
                    dragged = null;
                    dragFingerIndex = -1;
                }
            }
        }
    }

    private Vector3 ScreenToWorld(Vector2 screen)
    {
        Vector3 world = cam.ScreenToWorldPoint(new Vector3(screen.x, screen.y, -cam.transform.position.z));
        world.z = 0f;
        return world;
    }
}

O grabOffset é o detalhe que faz o arrasto parecer certo: sem ele, a peça pula para ficar centralizada debaixo do dedo. Guardar o dragFingerIndex garante que um segundo dedo encostando na tela não "rouba" a peça. E tratar Canceled junto com Ended evita a peça grudada quando o sistema interrompe o toque (uma notificação, uma ligação).

As peças precisam de um Collider2D e estar na layer do draggableMask. A conversão de tela para mundo é a mesma do mouse; o Z certo é a distância até o plano do jogo, não zero.

Toque, arrasto e pinça são a parte fácil do mobile. O que trava projeto é o resto: interface que se adapta à tela de cada aparelho, desempenho em celular fraco, build para Android, ícone, publicação na loja. Se a sua meta é ter um jogo mobile publicado e não só uma cena que responde ao dedo, o curso de Unity cobre esse caminho completo com projetos que vão até o build.

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

Multitoque e pinça para zoom

Pinça é a conta mais simples de multitoque: compare a distância entre dois dedos neste frame com a do frame anterior.

Arquivo PinchZoom.cs:

using UnityEngine;
using UnityEngine.InputSystem.EnhancedTouch;
using Touch = UnityEngine.InputSystem.EnhancedTouch.Touch;

public class PinchZoom : MonoBehaviour
{
    [SerializeField] private Camera cam;
    [SerializeField] private float minSize = 3f;
    [SerializeField] private float maxSize = 12f;

    private void OnEnable()
    {
        EnhancedTouchSupport.Enable();
    }

    private void OnDisable()
    {
        EnhancedTouchSupport.Disable();
    }

    private void Update()
    {
        if (Touch.activeTouches.Count < 2)
        {
            return;
        }

        Touch a = Touch.activeTouches[0];
        Touch b = Touch.activeTouches[1];

        Vector2 aPrev = a.screenPosition - a.delta;
        Vector2 bPrev = b.screenPosition - b.delta;

        float prevDistance = Vector2.Distance(aPrev, bPrev);
        float currentDistance = Vector2.Distance(a.screenPosition, b.screenPosition);

        if (prevDistance <= 0f)
        {
            return;
        }

        float ratio = prevDistance / currentDistance;
        cam.orthographicSize = Mathf.Clamp(cam.orthographicSize * ratio, minSize, maxSize);
    }
}

Usar a razão entre as distâncias (em vez da diferença em pixels) deixa o zoom igual em celular pequeno e em tablet: afastar os dedos ao dobro da distância divide o tamanho da câmera por dois, em qualquer tela. Para câmera perspectiva, troque orthographicSize por fieldOfView com limites de ângulo, ou mova a câmera no eixo Z.

Se o seu jogo usa pinça e arrasto ao mesmo tempo (mapa de estratégia), decida pela contagem de dedos: um dedo arrasta, dois dedos fazem zoom, e ao passar de dois para um, ignore o primeiro frame para a câmera não dar um salto.

Rotação com dois dedos sai da mesma ideia. Em vez da distância, compare o ângulo do vetor entre os dedos: Vector2.SignedAngle(bPrev - aPrev, b.screenPosition - a.screenPosition) devolve quantos graus o par girou desde o frame anterior, com sinal. Some esse valor à rotação Z do objeto ou da câmera e você tem o gesto de girar mapa dos jogos de cidade. Aplique um limiar pequeno (meio grau, por exemplo) para a câmera não tremer quando o jogador só quer dar zoom e os dedos giram um pouco sem querer.

Testar no editor sem celular

Ciclo de build para o celular a cada ajuste mata a produtividade. Existem dois atalhos:

  • Device Simulator: na aba da Game view, troque o seletor de Game para Simulator (ou abra por Window > General > Device Simulator). Escolha um aparelho, e o jogo aparece com a resolução, a densidade e o recorte da tela dele. Clique do mouse vira toque. É também onde você confere a safe area de aparelho com notch.
  • Touch Simulation: no Window > Analysis > Input Debugger, menu Options, ative Simulate Touch Input From Mouse or Pen. O mouse passa a gerar um Touchscreen virtual na Game view comum. Por código, TouchSimulation.Enable() faz o mesmo, útil se você quer deixar ligado num build de PC para teste.

Os dois simulam um dedo. Pinça e gestos de dois dedos precisam do aparelho de verdade; para isso, deixe um build de desenvolvimento instalado e itere nos parâmetros pelo Inspector com valores expostos.

Erros comuns

InvalidOperationException ao ler Touch.activeTouches. EnhancedTouch não foi ativado. Chame EnhancedTouchSupport.Enable() no OnEnable ou num Awake.

Toque passa através do botão da UI. O dedo apertou um botão e o jogo também pulou. Cheque EventSystem.current.IsPointerOverGameObject() no Update antes de tratar o toque no mundo, com o InputSystemUIInputModule no EventSystem.

Arrasto funciona no simulador e falha no celular. Quase sempre é Z da conversão ou câmera errada. Use um campo de câmera no Inspector em vez de Camera.main, e confira a distância até o plano do jogo.

Alvo pequeno demais para o dedo. Um botão de 40 pixels parece ótimo no monitor e é impossível de acertar num celular de alta densidade. Pense em tamanho físico: Screen.dpi informa os pontos por polegada do aparelho (e pode devolver 0 quando o sistema não informa, então tenha um valor padrão). Com ele você converte "um centímetro" em pixels e garante área de toque confortável, ou usa o Canvas Scaler em Scale With Screen Size para a UI crescer junto com a tela.

Desempenho caindo com muitos toques. Raycast físico a cada toque e a cada frame em cena cheia pesa. Faça o OverlapPoint só no Began.

Próximo passo

Com toque e arrasto prontos, o controle mais pedido em jogo mobile de ação é o joystick virtual, que o próprio Input System entrega com o On-Screen Stick. Se você ainda está no planejamento, o guia de como criar o primeiro jogo mobile ajuda a escolher o escopo, e a comparação de melhor engine para jogo mobile mostra onde a Unity se encaixa frente às outras opções.

Perguntas frequentes

Como detectar toque na tela na Unity com o Input System?

Para um toque só, crie uma action com binding <Touchscreen>/primaryTouch/press e leia a posição em <Touchscreen>/primaryTouch/position, ou use a action Point do asset padrão. Para vários dedos, ative EnhancedTouchSupport e percorra Touch.activeTouches no Update.

O que é EnhancedTouch na Unity?

É uma API do Input System voltada a touch que você liga com EnhancedTouchSupport.Enable(). Ela entrega cada toque com fase, posição, dedo e histórico, sem gerar lixo para o garbage collector, e é o caminho indicado para multitoque, arrastar e gestos.

Dá para testar touch na Unity sem celular?

Dá. O Device Simulator mostra o jogo dentro da tela de um aparelho e transforma o clique do mouse em toque. No Input Debugger, a opção Simulate Touch Input From Mouse or Pen faz o mesmo na Game view. Multitoque real, como pinça, só testa bem no aparelho.

Por que aparece erro de Touch ambíguo no script?

Porque UnityEngine.Touch (sistema antigo) e UnityEngine.InputSystem.EnhancedTouch.Touch têm o mesmo nome. Crie um alias no topo do arquivo: using Touch = UnityEngine.InputSystem.EnhancedTouch.Touch; e o compilador para de reclamar.