Part of these game systems:
intermediate AI & Pathfinding

NavMesh Click-to-Move

Click-to-move character controller using NavMeshAgent. Click anywhere to navigate, with destination marker and path line.

Unity 2022.3+ · 2.1 KB · NavMeshClickToMove.cs

How to Use

1

Add a NavMeshAgent component to your character

2

Attach NavMeshClickToMove

3

Bake a NavMesh on your ground/terrain

4

Right-click (configurable) to move to a destination

5

Optionally assign a destination marker prefab (particle, ring, etc.)

6

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

NavMeshClickToMove.cs
C#
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;
        }
    }
}