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

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;
        }
    }
}