Posição do Mouse na Unity: Input System e ScreenToWorldPoint
No Unity Input System, a posição do mouse vem de Mouse.current.position.ReadValue(): um Vector2 em pixels, com (0, 0) no canto inferior esquerdo da tela. Para usar esse ponto no jogo, você converte para o mundo com Camera.ScreenToWorldPoint (em 2D) ou com um raio da câmera (em 3D). A conversão é onde quase todo mundo erra, porque o Z que você passa muda o resultado.
Aqui você vai ter o código de cada caso pronto para Unity 6.3 LTS: posição em tela, posição no mundo 2D, o bug do Z que faz o ScreenToWorldPoint devolver a posição da câmera, mouse no mundo 3D com raycast, leitura pela action Point (que funciona com toque também) e como ignorar o clique quando o cursor está em cima de um botão.
Unity input system mouse position: em tela
A leitura mais direta usa o dispositivo Mouse:
Arquivo MouseScreenPosition.cs:
using UnityEngine;
using UnityEngine.InputSystem;
public class MouseScreenPosition : MonoBehaviour
{
private void Update()
{
Mouse mouse = Mouse.current;
if (mouse == null)
{
return;
}
Vector2 screenPos = mouse.position.ReadValue();
Vector2 delta = mouse.delta.ReadValue();
if (mouse.leftButton.wasPressedThisFrame)
{
Debug.Log("Clique em " + screenPos + " (tela " + Screen.width + "x" + Screen.height + ")");
}
if (delta.sqrMagnitude > 0f)
{
Debug.Log("Mouse moveu " + delta + " pixels neste frame");
}
}
}
O que esse valor significa:
- Unidade é pixel da janela do jogo, não do monitor. No editor, é o tamanho da Game view.
- Origem é inferior esquerda. O canto superior direito é
(Screen.width, Screen.height). mouse.deltaé quanto o mouse andou desde o último frame, útil para câmera em primeira pessoa.
A checagem Mouse.current == null parece exagero no PC, mas o mesmo script rodando num build de celular sem mouse quebraria com NullReferenceException logo no primeiro frame.
Converter para o mundo em 2D
Num jogo 2D com câmera ortográfica, você quer saber em que ponto do mundo o cursor está para mirar, posicionar uma construção ou mover o personagem até lá.
Arquivo MouseWorld2D.cs:
using UnityEngine;
using UnityEngine.InputSystem;
public class MouseWorld2D : MonoBehaviour
{
[SerializeField] private Camera cam;
[SerializeField] private Transform cursorMarker;
private void Awake()
{
if (cam == null)
{
cam = Camera.main;
}
}
private void Update()
{
if (cursorMarker == null)
{
return;
}
cursorMarker.position = GetMouseWorldPosition();
}
public Vector3 GetMouseWorldPosition()
{
if (Mouse.current == null)
{
return Vector3.zero;
}
Vector2 screen = Mouse.current.position.ReadValue();
float distance = -cam.transform.position.z;
Vector3 world = cam.ScreenToWorldPoint(new Vector3(screen.x, screen.y, distance));
world.z = 0f;
return world;
}
}
Duas linhas carregam o método todo. A primeira calcula distance = -cam.transform.position.z: a câmera padrão 2D fica em z = -10 e o jogo acontece em z = 0, então a distância até o plano do jogo é 10. A segunda zera o z do resultado, para o objeto ficar no mesmo plano dos sprites e não atrás da câmera.
Guarde a câmera num campo em vez de chamar Camera.main em todo lugar. Ela é encontrada pela tag MainCamera; se você trocar a câmera ou esquecer a tag, o campo no Inspector deixa claro qual está sendo usada.
O bug do Z no ScreenToWorldPoint
A versão que aparece em todo fórum é esta:
Vector3 world = Camera.main.ScreenToWorldPoint(Mouse.current.position.ReadValue());
O Vector2 vira Vector3 com z = 0, e a documentação do Camera.ScreenToWorldPoint diz que o z do ponto é a distância em unidades de mundo à frente da câmera. Distância zero é o próprio plano da câmera. O que acontece em cada tipo de câmera:
- Perspectiva: todos os raios saem de um ponto só. Na distância zero, qualquer pixel da tela vira a posição da câmera. É o sintoma "o ScreenToWorldPoint sempre devolve a posição da câmera, não importa onde eu clico".
- Ortográfica: x e y saem certos, porque os raios são paralelos. Mas o z do resultado é o z da câmera (-10). Se você põe um objeto nesse ponto, ele fica no plano da câmera e some da tela, ou é cortado pelo Near Clip Plane.
A correção é a do script anterior: passe a distância certa no z e, em 2D, zere o z do resultado. Em câmera perspectiva olhando para um jogo 2.5D, a distância certa depende de onde está o plano do jogo, e aí o raio da próxima seção é mais seguro.
Mouse no mundo 3D com raycast
Em 3D não existe "o ponto" do mouse no mundo: existe um raio que sai da câmera passando pelo pixel. Você decide onde ele para.
Arquivo MouseWorld3D.cs:
using UnityEngine;
using UnityEngine.InputSystem;
public class MouseWorld3D : MonoBehaviour
{
[SerializeField] private Camera cam;
[SerializeField] private LayerMask groundMask;
[SerializeField] private float maxDistance = 200f;
public bool TryGetMouseHit(out RaycastHit hit)
{
hit = default;
if (Mouse.current == null)
{
return false;
}
Vector2 screen = Mouse.current.position.ReadValue();
Ray ray = cam.ScreenPointToRay(screen);
return Physics.Raycast(ray, out hit, maxDistance, groundMask);
}
public bool TryGetPointOnFloor(out Vector3 point)
{
point = Vector3.zero;
if (Mouse.current == null)
{
return false;
}
Ray ray = cam.ScreenPointToRay(Mouse.current.position.ReadValue());
Plane floor = new Plane(Vector3.up, Vector3.zero);
if (floor.Raycast(ray, out float enter))
{
point = ray.GetPoint(enter);
return true;
}
return false;
}
private void Update()
{
if (Mouse.current != null && Mouse.current.leftButton.wasPressedThisFrame)
{
if (TryGetMouseHit(out RaycastHit hit))
{
Debug.Log("Clicou em " + hit.collider.name + " no ponto " + hit.point);
}
}
}
}
São dois métodos para dois usos. TryGetMouseHit usa Physics.Raycast e precisa de collider: serve para clicar em unidade, em inimigo, em terreno com MeshCollider. O LayerMask evita que o raio pare no próprio jogador ou num gatilho invisível. TryGetPointOnFloor usa um Plane matemático em y = 0, sem collider nenhum: é o jeito barato de fazer um top-down 3D mirar no chão, e funciona mesmo onde não há geometria.
Pela action Point
Ler Mouse.current prende o código ao mouse. Se o jogo também roda em celular ou tablet com caneta, a action Point do asset padrão já resolve: no map UI do InputSystem_Actions da Unity 6 ela está ligada à posição do mouse, da caneta e do toque.
Arquivo PointerWorld2D.cs:
using UnityEngine;
using UnityEngine.InputSystem;
public class PointerWorld2D : MonoBehaviour
{
[SerializeField] private Camera cam;
private InputAction pointAction;
private void Awake()
{
if (cam == null)
{
cam = Camera.main;
}
pointAction = InputSystem.actions.FindAction("UI/Point", true);
}
public Vector3 GetPointerWorldPosition()
{
Vector2 screen = pointAction.ReadValue<Vector2>();
Vector3 world = cam.ScreenToWorldPoint(new Vector3(screen.x, screen.y, -cam.transform.position.z));
world.z = 0f;
return world;
}
}
O código de conversão é o mesmo; só a fonte mudou. Se preferir manter as actions de gameplay todas no map Player, crie uma action Point lá (Action Type Value, Control Type Vector2) e adicione os bindings <Pointer>/position. O path Pointer cobre mouse, caneta e toque de uma vez.
Ler a posição é o fácil. O que dá trabalho num jogo inteiro é tudo que depende dela ao mesmo tempo: mira, câmera que acompanha o cursor, construção em grade, UI que bloqueia o clique, controle que substitui o mouse. Juntar isso num projeto que fecha é o que o curso de Unity ensina construindo jogos completos, em vez de cada peça num tutorial separado.
Mouse sobre a UI: ignorar cliques
Clássico: o jogador clica no botão "Inventário" e o personagem também atira, porque o script de tiro leu o mesmo clique. A correção é perguntar ao EventSystem se o ponteiro está sobre a UI.
Arquivo ClickToShoot.cs:
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.InputSystem;
public class ClickToShoot : MonoBehaviour
{
[SerializeField] private MouseWorld2D mouseWorld;
private void Update()
{
if (Mouse.current == null || !Mouse.current.leftButton.wasPressedThisFrame)
{
return;
}
if (EventSystem.current != null && EventSystem.current.IsPointerOverGameObject())
{
return;
}
Vector3 target = mouseWorld.GetMouseWorldPosition();
Debug.Log("Atirando em " + target);
}
}
Três condições para isso funcionar:
- O
EventSystemda cena usa o InputSystemUIInputModule. Se o Inspector mostrar o StandaloneInputModule antigo, clique no botão de substituir pelo módulo do Input System. - A chamada acontece no
Update. Dentro de um callback de action (performed +=), oIsPointerOverGameObjectresponde com o estado da UI do frame anterior, e o pacote avisa isso no Console. - O elemento de UI tem Raycast Target ligado. Uma imagem decorativa com Raycast Target ligado bloqueia cliques que você queria no mundo; um painel com ele desligado deixa passar.
Encaixar o mouse na grade e trocar o cursor
Dois pedidos que aparecem logo depois de "pegar a posição do mouse": construir em grade e trocar a setinha do sistema por um cursor do jogo.
Para grade, arredonde o ponto do mundo. Com tiles de 1 unidade (o padrão de um sprite de 16 px com Pixels Per Unit 16), Mathf.Floor(world.x) + 0.5f coloca o fantasma da construção no centro da célula. Se a cena usa um componente Grid com Tilemap, use grid.WorldToCell(world) para obter a célula como Vector3Int e grid.GetCellCenterWorld(cell) para voltar ao centro dela. Assim a conta continua certa se alguém mudar o Cell Size do Grid.
Para o cursor, você tem duas opções. Cursor.SetCursor(textura, hotspot, CursorMode.Auto) troca o cursor de hardware, que não tem atraso e respeita a sensibilidade do sistema; a textura precisa estar importada como Cursor no Texture Type, e o hotspot é o pixel que conta como "ponta" (o centro, numa mira). A outra opção é esconder o cursor com Cursor.visible = false e desenhar um sprite na posição do mundo, como o cursorMarker do exemplo 2D. O sprite permite animação e brilho, mas segue o mouse com o atraso de um frame de renderização, e em jogo de mira rápida isso se sente.
Para jogo em primeira pessoa, Cursor.lockState = CursorLockMode.Locked prende o cursor no centro da janela. A partir daí position fica parado e quem interessa é Mouse.current.delta, que continua informando o movimento.
Erros comuns
O objeto segue o mouse com atraso de um frame. Você move no Update e a câmera se move no LateUpdate (Cinemachine, por exemplo). Converta e posicione no LateUpdate, depois da câmera.
Posição certa no editor e errada no build. A Game view do editor tem uma resolução e o build outra; se você usou números fixos de pixel em algum lugar, eles quebram. Trabalhe sempre com o valor convertido para o mundo ou relativo a Screen.width e Screen.height.
Cursor sobre um Canvas no modo Screen Space Camera. A posição do mouse está em pixels de tela e o Canvas tem a própria escala. Para posicionar algo dentro do Canvas, use RectTransformUtility.ScreenPointToLocalPointInRectangle em vez de ScreenToWorldPoint.
Input.mousePosition lançando exceção. O projeto está com o Input System ativo e esse código é do sistema antigo. Troque por Mouse.current.position.ReadValue().
Mira tremendo quando o personagem anda. A câmera segue o personagem e o ponto no mundo muda mesmo com o mouse parado. Isso é correto; se incomoda, suavize a rotação da arma, não a leitura do mouse.
Próximo passo
Com a posição no mundo em mãos, o uso mais comum é girar a arma para o cursor. O post de mirar no mouse em 2D usa a mesma conversão daqui (ScreenToWorldPoint com o Z certo) e calcula o ângulo com Mathf.Atan2. Se a parte de vetor e distância ainda está nebulosa, a matemática de vetores para jogos explica direção, magnitude e normalização com exemplos de jogo. E se você também usa Godot, o Input Map da Godot mostra como a outra engine trata ações e ponteiro.
Perguntas frequentes
Como pegar a posição do mouse no New Input System?
Use Mouse.current.position.ReadValue(). Ele devolve um Vector2 em pixels, com a origem no canto inferior esquerdo da tela. Confira antes se Mouse.current não é null, porque em celular ou console pode não existir mouse.
Por que o ScreenToWorldPoint sempre devolve a posição da câmera?
Porque você passou um ponto com Z igual a zero. O Z do ScreenToWorldPoint é a distância à frente da câmera. Em câmera perspectiva, distância zero é a própria posição da câmera. Passe a distância até o plano do jogo, normalmente o valor positivo do Z da câmera, ou use um raio.
Como pegar a posição do mouse no mundo 2D na Unity?
Leia Mouse.current.position, monte um Vector3 com x e y da tela e z igual à distância entre a câmera e o plano do jogo, chame Camera.ScreenToWorldPoint e zere o z do resultado. Em câmera ortográfica isso devolve o ponto exato do cursor no plano do jogo.
Como saber se o mouse está em cima de um botão da UI?
Chame EventSystem.current.IsPointerOverGameObject() no Update, com o InputSystemUIInputModule no EventSystem. Se devolver true, o cursor está sobre um elemento de UI e você ignora o clique no mundo. Evite chamar dentro de callback de action, porque ali ele lê o estado do frame anterior.



