NavMesh Click-to-Move
Click-to-move character controller using NavMeshAgent. Click anywhere to navigate, with destination marker and path line.
How to Use
Add a NavMeshAgent component to your character
Attach NavMeshClickToMove
Bake a NavMesh on your ground/terrain
Right-click (configurable) to move to a destination
Optionally assign a destination marker prefab (particle, ring, etc.)
Path line shows the navigation route in cyan
Features
- Click-to-move navigation using Unity NavMeshAgent
- Visual path line rendered with LineRenderer component
- Destination marker prefab spawned at click position
- NavMesh.SamplePosition for snapping clicks to valid walkable areas
- Configurable mouse button, ground layer, and stopping distance
- Public MoveTo and Stop methods for scripted navigation
When to Use This
Perfect for RTS games, isometric RPGs (Diablo-style), point-and-click adventures, and MOBA-style games. Use this when the player navigates by clicking on the ground rather than using WASD. Works well with a top-down or isometric camera setup and NavMesh-baked environments.
Common Mistakes
You must bake a NavMesh on your ground geometry (Window > AI > Navigation > Bake) or the agent will not move at all. The groundLayer mask must match the layer of your terrain or floor objects, otherwise clicks pass through. If the destinationMarkerPrefab is not assigned, no visual marker appears but movement still works — this is optional, not an error.
Source Code
using UnityEngine;
using UnityEngine.AI;
/// <summary>
/// Click-to-move controller using NavMeshAgent.
/// Click on the ground to navigate. Optionally shows path and destination marker.
/// </summary>
[RequireComponent(typeof(NavMeshAgent))]
public class NavMeshClickToMove : MonoBehaviour
{
[Header("Input")]
[SerializeField] private int mouseButton = 1; // Right-click
[SerializeField] private LayerMask groundLayer = ~0;
[Header("Visual Feedback")]
[SerializeField] private GameObject destinationMarkerPrefab;
[SerializeField] private bool showPathLine = true;
[SerializeField] private Color pathColor = Color.cyan;
[Header("Movement")]
[SerializeField] private float stoppingDistance = 0.5f;
[SerializeField] private float moveSpeed = 5f;
private NavMeshAgent agent;
private LineRenderer pathLine;
private GameObject activeMarker;
private Camera mainCamera;
private void Awake()
{
agent = GetComponent<NavMeshAgent>();
agent.speed = moveSpeed;
agent.stoppingDistance = stoppingDistance;
if (showPathLine)
{
pathLine = gameObject.AddComponent<LineRenderer>();
pathLine.startWidth = 0.05f;
pathLine.endWidth = 0.05f;
pathLine.material = new Material(Shader.Find("Sprites/Default"));
pathLine.startColor = pathColor;
pathLine.endColor = pathColor;
pathLine.positionCount = 0;
}
}
private void Start()
{
mainCamera = Camera.main;
}
private void Update()
{
if (Input.GetMouseButtonDown(mouseButton))
{
Ray ray = mainCamera.ScreenPointToRay(Input.mousePosition);
if (Physics.Raycast(ray, out RaycastHit hit, 100f, groundLayer))
{
MoveTo(hit.point);
}
}
UpdatePathLine();
UpdateMarker();
}
/// <summary>
/// Move to a world position via NavMesh.
/// </summary>
public void MoveTo(Vector3 destination)
{
if (NavMesh.SamplePosition(destination, out NavMeshHit navHit, 2f, NavMesh.AllAreas))
{
agent.SetDestination(navHit.position);
ShowMarker(navHit.position);
}
}
/// <summary>
/// Stop moving and clear the path.
/// </summary>
public void Stop()
{
agent.ResetPath();
HideMarker();
}
private void ShowMarker(Vector3 position)
{
if (destinationMarkerPrefab == null) return;
if (activeMarker == null)
activeMarker = Instantiate(destinationMarkerPrefab);
activeMarker.transform.position = position + Vector3.up * 0.05f;
activeMarker.SetActive(true);
}
private void HideMarker()
{
if (activeMarker != null)
activeMarker.SetActive(false);
}
private void UpdateMarker()
{
if (activeMarker != null && activeMarker.activeSelf)
{
if (!agent.hasPath || agent.remainingDistance <= stoppingDistance)
HideMarker();
}
}
private void UpdatePathLine()
{
if (pathLine == null) return;
if (agent.hasPath && agent.path.corners.Length > 1)
{
pathLine.positionCount = agent.path.corners.Length;
pathLine.SetPositions(agent.path.corners);
}
else
{
pathLine.positionCount = 0;
}
}
}