Click to Move na Unity: Mover com o Mouse e NavMesh
Para fazer unity click to move você precisa de quatro peças: um NavMesh assado no cenário com o pacote AI Navigation, um NavMeshAgent no personagem, um raycast da câmera até o chão na posição do mouse e uma chamada a NavMeshAgent.SetDestination com o ponto atingido. O agente calcula o caminho e desvia de paredes e obstáculos sozinho. Este tutorial monta tudo em C# para o Unity 6.3 LTS, com Input System, marcador de destino no chão e animação de andar pela velocidade.
É o controle de jogos de ação vista de cima, RPGs isométricos, estratégia e point and click em 3D. No fim, você terá clique simples, segurar para seguir o mouse e o personagem parando direito no destino.
Unity click to move: visão geral
O fluxo de um clique:
- O jogador clica. O script lê a posição do mouse na tela.
Camera.ScreenPointToRaytransforma esse ponto 2D num raio 3D que sai da câmera.Physics.Raycastacha onde o raio bate no chão.NavMesh.SamplePositionajusta o ponto para o NavMesh mais próximo (caso o clique tenha caído num lugar onde não dá para andar).agent.SetDestination(ponto)manda o agente ir.- Um marcador aparece no ponto e some quando o agente chega.
O personagem não tem Rigidbody nem CharacterController: quem move é o NavMeshAgent.
Instalar AI Navigation e fazer o bake
No Unity 6, as ferramentas de NavMesh ficam no pacote AI Navigation (com.unity.ai.navigation). O manual do Unity 6.3 LTS lista a versão 2.0.16 do pacote para esse editor. Se ele não estiver no projeto: Window > Package Management > Package Manager, Unity Registry, procure "AI Navigation" e instale.
O bake, resumido (o post dedicado de NavMesh com AI Navigation cobre áreas, links e obstáculos dinâmicos):
- Selecione o objeto que agrupa o cenário (ou crie um vazio
Navigationna raiz). Add Component > Navigation > NavMesh Surface.- Agent Type:
Humanoid(padrão). Collect Objects:All Game Objectspara começar. Use Geometry:Render MeshesouPhysics Colliders; colliders costumam gerar um NavMesh mais limpo. - Include Layers: deixe só as layers de chão e obstáculos. Excluir a layer do personagem e de efeitos evita buracos estranhos no NavMesh.
- Clique em Bake. A área azul que aparece na Scene view é onde o agente pode andar.
Toda vez que você mudar o cenário estático, clique em Bake de novo. O resultado é salvo num asset referenciado pelo componente.
NavMeshAgent no personagem
No objeto do personagem, Add Component > NavMeshAgent. Os valores padrão são pensados para uma IA tranquila; para personagem controlado pelo jogador, ajuste:
- Speed:
5. Velocidade máxima em metros por segundo. - Angular Speed:
720. O padrão é baixo para um personagem de jogador, que faz curvas abertas e lentas. - Acceleration:
40. Com o padrão, o personagem demora a arrancar e passa do ponto ao frear. - Stopping Distance:
0.1. A que distância do destino ele considera ter chegado. - Auto Braking: ligado. Desacelera ao se aproximar do destino.
- Radius e Height: do tamanho do personagem.
O pivô do personagem deve estar nos pés: o agente posiciona o objeto com a base sobre o NavMesh.
Raycast do mouse até o chão
O Input System lê a posição do mouse com Mouse.current.position.ReadValue(), em pixels, com origem no canto inferior esquerdo da tela. É exatamente o que Camera.ScreenPointToRay espera. Crie uma layer Ground para o chão, para o raycast ignorar paredes, inimigos e o próprio personagem.
Crie ClickToMove.cs:
using UnityEngine;
using UnityEngine.AI;
using UnityEngine.EventSystems;
using UnityEngine.InputSystem;
[RequireComponent(typeof(NavMeshAgent))]
public class ClickToMove : MonoBehaviour
{
private static readonly int SpeedHash = Animator.StringToHash("Speed");
[Header("Referências")]
[SerializeField] private NavMeshAgent agent;
[SerializeField] private Camera mainCamera;
[SerializeField] private Animator animator;
[SerializeField] private Transform destinationMarker;
[Header("Clique")]
[SerializeField] private LayerMask groundMask;
[SerializeField] private float maxRayDistance = 200f;
[SerializeField] private float sampleRadius = 1f;
[SerializeField] private float holdRepathInterval = 0.1f;
private float nextRepathTime;
private void Awake()
{
if (agent == null)
{
agent = GetComponent<NavMeshAgent>();
}
if (mainCamera == null)
{
mainCamera = Camera.main;
}
if (destinationMarker != null)
{
destinationMarker.gameObject.SetActive(false);
}
}
private void Update()
{
HandleClick();
UpdateArrival();
UpdateAnimator();
}
private void HandleClick()
{
Mouse mouse = Mouse.current;
if (mouse == null)
{
return;
}
bool clicked = mouse.leftButton.wasPressedThisFrame;
bool holding = mouse.leftButton.isPressed && Time.time >= nextRepathTime;
if (!clicked && !holding)
{
return;
}
if (EventSystem.current != null && EventSystem.current.IsPointerOverGameObject())
{
return;
}
if (TryGetClickPoint(mouse.position.ReadValue(), out Vector3 point))
{
agent.SetDestination(point);
nextRepathTime = Time.time + holdRepathInterval;
if (clicked)
{
ShowMarker(point);
}
}
}
private bool TryGetClickPoint(Vector2 screenPosition, out Vector3 point)
{
point = Vector3.zero;
Ray ray = mainCamera.ScreenPointToRay(screenPosition);
if (!Physics.Raycast(ray, out RaycastHit hit, maxRayDistance, groundMask, QueryTriggerInteraction.Ignore))
{
return false;
}
if (!NavMesh.SamplePosition(hit.point, out NavMeshHit navHit, sampleRadius, NavMesh.AllAreas))
{
return false;
}
point = navHit.position;
return true;
}
private void UpdateArrival()
{
if (agent.pathPending)
{
return;
}
if (agent.remainingDistance <= agent.stoppingDistance)
{
if (destinationMarker != null && destinationMarker.gameObject.activeSelf)
{
destinationMarker.gameObject.SetActive(false);
}
}
}
private void ShowMarker(Vector3 point)
{
if (destinationMarker == null)
{
return;
}
destinationMarker.position = point + Vector3.up * 0.02f;
destinationMarker.gameObject.SetActive(true);
}
private void UpdateAnimator()
{
if (animator == null)
{
return;
}
float normalizedSpeed = agent.velocity.magnitude / agent.speed;
animator.SetFloat(SpeedHash, normalizedSpeed, 0.1f, Time.deltaTime);
}
}
No Inspector, marque Ground no Ground Mask e arraste um marcador (pode ser um Quad deitado com uma textura de anel, sem collider).
O IsPointerOverGameObject impede que um clique num botão da interface também mande o personagem andar. Sem isso, clicar no inventário faz o herói correr para baixo do inventário.
SetDestination e parar no destino
SetDestination não calcula o caminho na hora. O pedido entra numa fila e o caminho fica pronto em seguida; enquanto isso, agent.pathPending é verdadeiro. Esse é o detalhe que derruba a checagem de chegada mais comum: logo depois do clique, remainingDistance ainda é o valor antigo (zero, se ele estava parado), e o teste remainingDistance <= stoppingDistance dá verdadeiro no mesmo frame, escondendo o marcador antes de o personagem sair do lugar. Por isso o UpdateArrival sai cedo quando pathPending é verdadeiro.
O NavMesh.SamplePosition resolve outro caso: o jogador clica num ponto do chão que está fora do NavMesh (rente a uma parede, ou numa área baixa demais). Sem o ajuste, o agente vai até o ponto mais próximo que conseguir, mas o marcador fica num lugar onde o personagem nunca chega. Com sampleRadius = 1, o ponto é puxado para o NavMesh num raio de um metro, e o marcador mostra o destino real.
Segurar o botão reenvia o destino a cada holdRepathInterval (0,1 segundo), que é o controle de "seguir o mouse" de RPGs de ação. Reenviar todo frame funciona, mas pede um caminho novo 60 vezes por segundo sem ganho visível.
Um personagem que anda até o clique é o começo de um RPG ou de um jogo de estratégia. Depois vêm clicar num inimigo para atacar, clicar num baú para abrir, selecionar várias unidades, fila de ordens. Cada clique passa a significar uma coisa diferente dependendo do que está embaixo do mouse, e organizar isso sem virar um if gigante é a parte que mais trava. É o tipo de sistema que o curso de Unity monta dentro de um jogo completo.
Animação de andar pela velocidade
O NavMeshAgent expõe velocity, a velocidade real do agente. Dividir pela speed máxima dá um valor de 0 a 1, que o script manda para o parâmetro Speed do Animator. O SetFloat com dampTime de 0,1 segundo suaviza a transição para a animação não piscar nas curvas.
No Animator Controller:
- Crie o parâmetro Float
Speed. - Crie um Blend Tree 1D com
Speedcomo parâmetro:Idleem 0,Walkem 0,5,Runem 1. - Se a animação de andar tem deslocamento próprio (root motion), desmarque
Apply Root Motionno Animator. Quem move o personagem é o agente; root motion junto faz ele andar o dobro ou deslizar.
Se os pés deslizam no chão, ajuste a velocidade do clip de andar (campo Speed do estado) até o passo da animação casar com a velocidade do agente.
Point and click: clicar em objetos
Em point and click e RPG, clicar num baú ou num NPC não é "andar até o ponto", é "andar até perto e interagir". A extensão natural do script é fazer o raycast acertar qualquer layer clicável e decidir pelo que foi atingido:
- Troque o
groundMaskpor uma máscara comGroundeInteractable. - Se o
hit.collidertiver um componente de interação (comTryGetComponent), use como destino um ponto na frente do objeto, por exemplohit.collider.transform.position + hit.collider.transform.forward * 1f, e guarde o objeto como alvo pendente. - No
UpdateArrival, quando o agente chegar e houver alvo pendente, chame a interação e limpe o alvo. - Se o jogador clicar no chão no meio do caminho, limpe o alvo pendente: ele mudou de ideia.
Esse padrão de "alvo pendente" é o mesmo que um sistema de interação usa com tecla, só que disparado pela chegada do agente em vez do botão.
Erros comuns
"SetDestination can only be called on an active agent that has been placed on a NavMesh". O personagem começou fora do NavMesh, ou o bake não foi feito. Confira a área azul na Scene view e se os pés do personagem estão sobre ela.
O clique não faz nada. O Ground Mask está vazio, o chão não tem collider ou não está na layer Ground. Desenhe o raio com Debug.DrawRay(ray.origin, ray.direction * 100f, Color.red, 1f) para ver onde ele vai.
O personagem para longe do ponto. Stopping Distance alto, ou o ponto clicado está fora do NavMesh e o SamplePosition está desligado.
O personagem dá voltas antes de virar. Angular Speed baixo. Suba para 720 ou mais.
Passa do ponto e volta. Acceleration baixa ou Auto Braking desligado.
O NavMesh tem buracos onde o personagem estava. O personagem estava na cena durante o bake e entrou como obstáculo. Tire a layer dele do Include Layers do NavMesh Surface.
Clicar na UI move o personagem. Falta o IsPointerOverGameObject, ou a cena não tem EventSystem.
Próximo passo
Com o click to move pronto, o mesmo NavMeshAgent serve para os inimigos. O post sobre IA de inimigo que persegue e patrulha mostra a lógica de estados por trás de patrulha e perseguição, que se encaixa direto num agente como este. E se o seu objetivo é um jogo de estratégia com várias unidades, o guia de como fazer jogo de estratégia RTS mostra o que vem depois: seleção em caixa, ordens em grupo e formação.
Exercício: troque o clique esquerdo pelo direito, faça o clique esquerdo selecionar objetos com uma interface ISelectable e veja como o mesmo raycast passa a servir a dois propósitos.
Perguntas frequentes
Como mover o personagem até onde clico com o mouse na Unity?
Transforme a posição do mouse num raio com Camera.ScreenPointToRay, use Physics.Raycast para achar o ponto no chão e passe esse ponto para NavMeshAgent.SetDestination. O agente calcula o caminho e desvia dos obstáculos sozinho.
Onde fica a janela de bake do NavMesh no Unity 6?
No Unity 6 o bake é feito pelo componente NavMesh Surface, do pacote AI Navigation (com.unity.ai.navigation). Adicione o componente num objeto do cenário em Add Component > Navigation > NavMesh Surface e clique em Bake no Inspector.
Como saber se o NavMeshAgent chegou no destino?
Confira se o caminho já foi calculado e se a distância restante é menor ou igual à distância de parada: !agent.pathPending && agent.remainingDistance <= agent.stoppingDistance. Sem checar pathPending, o teste dá verdadeiro no frame do clique, antes do caminho existir.
Click to move funciona em jogo 2D na Unity?
O NavMesh do pacote AI Navigation coleta malhas e colliders 3D e não lê Tilemap nem Collider2D. Para 2D top-down, as saídas são o NavMeshPlus (projeto aberto da comunidade, não oficial) ou um A* próprio sobre o Tilemap. Para point and click 2D simples, sem obstáculos complexos, mover em linha reta até o ponto clicado costuma bastar.



