Joystick Virtual na Unity para Jogo Mobile
O jeito mais limpo de fazer um Unity joystick virtual é usar o On-Screen Stick que já vem no pacote Input System. Ele transforma uma imagem da UI num analógico de gamepad virtual, então o movimento chega na mesma action Move que o teclado e o controle usam. Nenhum asset pago, nenhum if (mobile) no script do personagem.
Aqui você monta o joystick fixo com o On-Screen Stick, um botão de pulo com o On-Screen Button, posiciona os dois para funcionar em qualquer tela (Canvas Scaler e safe area), cria a versão flutuante que aparece onde o dedo toca e esconde tudo quando o jogo roda no PC. Código para Unity 6.3 LTS com o Input System ativo.
Unity joystick virtual: opções
Existem três caminhos, e cada um tem um custo:
- On-Screen Stick do Input System. Grátis, vem no pacote, e alimenta as actions existentes. O visual é seu (duas imagens). Limitação: os modos de comportamento são fixos, e a versão "aparece onde toca" tem pouca personalização.
- Joystick Pack (Asset Store). Gratuito, com vários estilos prontos (fixo, flutuante, dinâmico) e sprites, mas sem atualização há anos: confira a data da última versão na página do asset antes de importar. Ele lê os eventos da UI e expõe
Horizontal/Verticalnum componente, então você escreve a ligação com o personagem e mantém dois caminhos de input no código. - Componente próprio herdando de
OnScreenControl. Umas 50 linhas de C#, comportamento exatamente como você quer, e continua alimentando as actions. É o que faço quando o jogo precisa de joystick flutuante.
Para a maioria dos jogos, comece com o 1. Se o design pedir flutuante, vá para o 3. As duas seções a seguir cobrem os dois.
On-Screen Stick e On-Screen Button
Monte a hierarquia:
- GameObject > UI > Canvas (se a cena não tem). Confirme que existe um EventSystem com o InputSystemUIInputModule.
- Dentro do Canvas, crie uma UI > Image chamada
StickBackground: sprite de círculo, 300 x 300 px, alfa uns 40%. - Dentro dela, outra UI > Image chamada
StickHandle: círculo menor, 140 x 140 px, centralizado. - No
StickHandle, Add Component > On-Screen Stick. - No campo Control Path, clique e escolha Gamepad > Left Stick.
- Movement Range: 80. É o raio em pixels (da referência do Canvas) que o handle pode andar a partir do centro.
Para o botão de pulo: UI > Image chamada JumpButton, 180 x 180 px, Add Component > On-Screen Button, Control Path = Gamepad > Button South.
O que está acontecendo por baixo: o pacote cria um gamepad virtual. Mexer o handle escreve no leftStick dele; apertar o botão escreve no buttonSouth. Para o resto do jogo, existe um controle conectado.
O On-Screen Stick tem três modos no campo Behaviour, com os nomes exatos da API:
- Relative Position With Static Origin: centro fixo, o handle começa em repouso e anda conforme o dedo se move. É o padrão e o mais previsível.
- Exact Position With Static Origin: centro fixo, mas o handle pula para a posição exata do dedo.
- Exact Position With Dynamic Origin: o centro vai para onde o dedo encostou, dentro de um raio definido por Dynamic Origin Range.
E a opção Use Isolated Input Actions existe para um problema real: quando o jogo detecta troca de dispositivo (o gamepad virtual contra o touch), a interação com o stick pode ser cancelada. Ligar essa opção faz o stick usar actions locais e evita o cancelamento.
Ligar à mesma action do teclado
Esse passo é quase nada, e é a maior vantagem do método. O asset padrão InputSystem_Actions da Unity 6 já tem Move com binding em <Gamepad>/leftStick e Jump em <Gamepad>/buttonSouth. Então o script de movimento que você já tem continua o mesmo:
Arquivo PlayerMove2D.cs:
using UnityEngine;
using UnityEngine.InputSystem;
public class PlayerMove2D : MonoBehaviour
{
[SerializeField] private Rigidbody2D rb;
[SerializeField] private float speed = 5f;
[SerializeField] private float jumpForce = 8f;
private InputAction moveAction;
private InputAction jumpAction;
private Vector2 moveInput;
private bool jumpRequested;
private void Awake()
{
moveAction = InputSystem.actions.FindAction("Player/Move", true);
jumpAction = InputSystem.actions.FindAction("Player/Jump", true);
}
private void Update()
{
moveInput = moveAction.ReadValue<Vector2>();
if (jumpAction.WasPressedThisFrame())
{
jumpRequested = true;
}
}
private void FixedUpdate()
{
rb.linearVelocity = new Vector2(moveInput.x * speed, rb.linearVelocity.y);
if (jumpRequested)
{
rb.linearVelocity = new Vector2(rb.linearVelocity.x, jumpForce);
jumpRequested = false;
}
}
}
Teclado, controle físico e joystick virtual chegam pela mesma moveAction. Se o seu asset é personalizado e não tem binding de gamepad, adicione Gamepad > Left Stick em Move e Gamepad > Button South em Jump. Se quiser que a deadzone do analógico físico também valha para o virtual, o processor Stick Deadzone no binding cobre os dois.
Posicionar com Canvas Scaler e safe area
Joystick com tamanho fixo em pixels fica minúsculo num tablet e enorme num celular pequeno. Selecione o Canvas e, no Canvas Scaler:
- UI Scale Mode: Scale With Screen Size.
- Reference Resolution: 1920 x 1080 (jogo em paisagem).
- Screen Match Mode: Match Width Or Height, Match 0,5.
Agora os 300 px do fundo do stick são 300 px de uma tela de referência, e escalam proporcionalmente. Ancore o StickBackground no canto inferior esquerdo (no Anchor Presets, segure Shift e Alt para levar pivot e posição junto) com margem de uns 120 px, e o JumpButton no inferior direito.
O segundo problema é o notch e as bordas arredondadas. Um joystick encostado no canto some debaixo do recorte da câmera em muitos aparelhos. A Unity informa o retângulo utilizável em Screen.safeArea. Crie um painel SafeArea filho do Canvas, esticado na tela inteira, mova os controles para dentro dele e adicione este script:
Arquivo SafeAreaFitter.cs:
using UnityEngine;
[RequireComponent(typeof(RectTransform))]
public class SafeAreaFitter : MonoBehaviour
{
private RectTransform panel;
private Rect lastSafeArea;
private Vector2Int lastScreenSize;
private void Awake()
{
panel = GetComponent<RectTransform>();
Apply();
}
private void Update()
{
Vector2Int screenSize = new Vector2Int(Screen.width, Screen.height);
if (Screen.safeArea != lastSafeArea || screenSize != lastScreenSize)
{
Apply();
}
}
private void Apply()
{
Rect safe = Screen.safeArea;
lastSafeArea = safe;
lastScreenSize = new Vector2Int(Screen.width, Screen.height);
Vector2 anchorMin = safe.position;
Vector2 anchorMax = safe.position + safe.size;
anchorMin.x /= Screen.width;
anchorMin.y /= Screen.height;
anchorMax.x /= Screen.width;
anchorMax.y /= Screen.height;
panel.anchorMin = anchorMin;
panel.anchorMax = anchorMax;
panel.offsetMin = Vector2.zero;
panel.offsetMax = Vector2.zero;
}
}
A checagem no Update cobre a rotação da tela: ao virar o celular, o notch troca de lado e o painel se reajusta. Teste no Device Simulator escolhendo um aparelho com recorte; ele desenha a safe area por cima da Game view.
Até aqui você tem controles na tela que funcionam. Jogo mobile publicado pede mais: HUD que não briga com os controles, feedback de toque, desempenho em aparelho barato, build assinado para a loja. Se o seu objetivo é sair do protótipo e chegar no jogo na mão do jogador, é exatamente esse caminho que o curso de Unity percorre com projetos completos.
Joystick flutuante
Em jogo de ação mobile, o jogador não olha para o canto da tela: encosta o polegar em qualquer lugar da metade esquerda e espera que o joystick nasça ali. O modo Exact Position With Dynamic Origin faz algo parecido, mas o fundo do joystick não acompanha. Um componente próprio resolve isso e continua alimentando a action Move.
Monte assim: uma UI > Image JoystickArea cobrindo a metade esquerda da tela (anchors de 0 a 0,5 no X), cor com alfa 0 e Raycast Target ligado, pivot no centro. Dentro dela, o StickBackground (anchors no centro) com o StickHandle dentro. Remova o On-Screen Stick do handle e coloque este script na JoystickArea:
Arquivo FloatingJoystick.cs:
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.InputSystem.Layouts;
using UnityEngine.InputSystem.OnScreen;
public class FloatingJoystick : OnScreenControl, IPointerDownHandler, IDragHandler, IPointerUpHandler
{
[InputControl(layout = "Vector2")]
[SerializeField] private string stickControlPath = "<Gamepad>/leftStick";
[SerializeField] private RectTransform background;
[SerializeField] private RectTransform handle;
[SerializeField] private float radius = 100f;
private RectTransform area;
private Vector2 origin;
protected override string controlPathInternal
{
get => stickControlPath;
set => stickControlPath = value;
}
private void Awake()
{
area = (RectTransform)transform;
background.gameObject.SetActive(false);
}
public void OnPointerDown(PointerEventData eventData)
{
RectTransformUtility.ScreenPointToLocalPointInRectangle(area, eventData.position, eventData.pressEventCamera, out origin);
background.anchoredPosition = origin;
handle.anchoredPosition = Vector2.zero;
background.gameObject.SetActive(true);
SendValueToControl(Vector2.zero);
}
public void OnDrag(PointerEventData eventData)
{
RectTransformUtility.ScreenPointToLocalPointInRectangle(area, eventData.position, eventData.pressEventCamera, out Vector2 local);
Vector2 offset = Vector2.ClampMagnitude(local - origin, radius);
handle.anchoredPosition = offset;
SendValueToControl(offset / radius);
}
public void OnPointerUp(PointerEventData eventData)
{
handle.anchoredPosition = Vector2.zero;
background.gameObject.SetActive(false);
SendValueToControl(Vector2.zero);
}
}
Por que isso funciona: OnScreenControl é a classe base do próprio On-Screen Stick. Herdando dela, você ganha o gamepad virtual de graça; só precisa dizer qual controle alimentar (controlPathInternal) e mandar o valor com SendValueToControl. O atributo [InputControl(layout = "Vector2")] faz o Inspector mostrar o seletor de controles filtrado para analógicos.
A conta é a de sempre: a distância do dedo até a origem, limitada pelo raio com Vector2.ClampMagnitude, dividida pelo raio para virar um valor de 0 a 1. Para o anchoredPosition do fundo bater com o ponto local, a JoystickArea precisa de pivot no centro e o StickBackground de anchors no centro.
Esconder no PC
Joystick na tela de quem joga com teclado é poluição visual. A regra mais simples:
Arquivo MobileControlsVisibility.cs:
using UnityEngine;
using UnityEngine.InputSystem;
public class MobileControlsVisibility : MonoBehaviour
{
[SerializeField] private GameObject mobileControlsRoot;
[SerializeField] private bool forceShowInEditor = false;
private void Start()
{
bool hasTouch = Touchscreen.current != null;
bool show = Application.isMobilePlatform || hasTouch || (Application.isEditor && forceShowInEditor);
mobileControlsRoot.SetActive(show);
}
}
O forceShowInEditor deixa você testar no editor sem trocar de plataforma. Para notebook com tela de toque, a checagem de Touchscreen.current mostra os controles, o que costuma ser o certo. Se quiser algo mais fino, esconda os controles quando o jogador tocar no teclado ou no controle físico, e mostre de volta no próximo toque.
Erros comuns
O stick não responde. Falta EventSystem na cena, ou ele usa o StandaloneInputModule antigo. O On-Screen Stick depende dos eventos de ponteiro da UI.
O handle mexe mas o personagem não anda. O asset de actions não tem binding de <Gamepad>/leftStick em Move, ou o Player Input está preso num esquema de controle Keyboard&Mouse e ignora o gamepad virtual.
O stick solta sozinho no meio do movimento. Troca de dispositivo detectada entre o toque e o gamepad virtual. Ligue Use Isolated Input Actions no On-Screen Stick.
O botão de pulo ativa junto com o joystick. As duas imagens se sobrepõem, ou a JoystickArea flutuante cobre o botão. Ajuste a área ou a ordem na hierarquia (o último filho fica por cima e recebe o toque primeiro).
Próximo passo
Teste o controle com o polegar de verdade num aparelho: raio do stick, tamanho do botão e margem mudam muito do monitor para a mão. Para gestos além do joystick, veja o post de touch na Unity com EnhancedTouch. Se você está escolhendo engine para o projeto, o mesmo problema resolvido na Godot está em controles touch e joystick virtual na Godot, e o guia de como criar o primeiro jogo mobile ajuda a dimensionar o escopo.
Perguntas frequentes
Preciso comprar asset para fazer joystick virtual na Unity?
Não. O pacote Input System traz os componentes On-Screen Stick e On-Screen Button, que transformam uma imagem da UI num analógico ou botão de um gamepad virtual. Como eles alimentam as mesmas actions do teclado e do controle, o código do personagem não muda.
Como fazer o joystick aparecer onde o dedo toca?
Use o On-Screen Stick com Behaviour em Exact Position With Dynamic Origin e um Dynamic Origin Range grande, ou escreva um componente próprio herdando de OnScreenControl que move o fundo do joystick para o ponto do toque e envia o valor com SendValueToControl. O segundo jeito dá controle total sobre a área e o visual.
O Joystick Pack da Asset Store ainda funciona?
O Joystick Pack segue gratuito na Asset Store em outubro de 2026, mas a última versão listada é de março de 2019. Ele lê os eventos da UI e entrega valores que você precisa passar ao personagem por código próprio, sem integração com as actions do Input System.
Como deixar o joystick fora do notch do celular?
Coloque os controles dentro de um painel ajustado a Screen.safeArea. Um script converte o retângulo seguro em anchorMin e anchorMax do painel, e tudo que estiver dentro dele respeita o notch e as bordas arredondadas.



