DEV Community

vaibhavdevstuff
vaibhavdevstuff

Posted on

Find Random point on Navmesh Surface in Unity3D

Useful For: Random Patrolling Enemy or NPC

In order to find a random point on Navmesh Surface on unity terrain or mesh first we have to bake a Navmesh Surface with a Navmesh-Agent on it, then we find Navmesh point with C#.

1. Bake Navmesh Surface

Bake a navmesh surface using unity's built-in navmesh panel which you can find under Windows >> AI >> Navigation
Or [Recommended] you can use Navmesh Surface Component available in new Navmesh Component Package which you can add using package manager add by name and just write com.unity.ai.navigation and click add.

2. Setup Navmesh Agent

After you done baking place a navmesh agent on the surface and attack a C# script which will find a random position for agent to move.

3. Finding Navmesh Point

In order to find a random navmesh point on surface We are going to use the method provided by unity which finds the nearest point based on the NavMesh within a specified range.

Navemsh.SamplePosition

public static bool SamplePosition(Vector3 sourcePosition, out AI.NavMeshHit hit, float maxDistance, int areaMask);
Enter fullscreen mode Exit fullscreen mode

Firstly Declare the necessary data to pass as parameter to get the position

NavMeshHit hit;
Vector3 origin = transform.position;
float radius = 4f;
Enter fullscreen mode Exit fullscreen mode

Now we find the navmesh point by passing the data

NavMesh.SamplePosition(origin, out hit, radius, NavMesh.AllAreas);
Enter fullscreen mode Exit fullscreen mode

Finally, In return this method will provide us the nearest Navmesh point in hit

Vector3 navmeshPoint = hit.point;
Enter fullscreen mode Exit fullscreen mode

4. Adding Randomness in point Detection

The method "SamplePosition" will only return us the nearest navmesh point, so it doesn't matter if you provide larger radius.
So, in order to find a random navmesh point we will provide it a random origin point within a range. It will goes like:

//Find Random Position within a sphere
Vector3 randomPosition = UnityEngine.Random.insideUnitSphere * roamingPosMaxDistance;
//reset y-axis because we don't need to find it in air
randomPosition.y = 0;
//Add this with the agent position to get random position with agent position as origin
vector3 RandomPos = transform.position + randomPosition;
Enter fullscreen mode Exit fullscreen mode

Now we pass this RandomPos as origin

NavMesh.SamplePosition(RandomPos, out hit, radius, NavMesh.AllAreas);
Enter fullscreen mode Exit fullscreen mode

So now each time it calculate a random point from agent and then find the nearest Navmesh point from there...

5. What if there is no navmesh around Random Point..?

Well in that condition we need to repeat the process untill we find a valid point on surface

randomPosition = UnityEngine.Random.insideUnitSphere * roamingPosMaxDistance;
randomPosition.y = 0;
RandomPos = transform.position + randomPosition;

//Loop untill find proper point
while(!NavMesh.SamplePosition(RandomPos, out hit, radius, NavMesh.AllAreas)
{
  randomPosition = UnityEngine.Random.insideUnitSphere * roamingPosMaxDistance;
  randomPosition.y = 0;
  RandomPos = transform.position + randomPosition;
}

finalPoint = hit.point;
Enter fullscreen mode Exit fullscreen mode

Final Tip

Based on Personal Experience
Run whole process inside a Coroutine or Async[basically on separate thread]. So that if no navmesh surface is available your agent will not just loop the process infinitely and freeze the whole game. For coroutine wait for next frame every time loop execute. So finally it will be like

IEnumerator FindRandomPoint()
{
randomPosition = UnityEngine.Random.insideUnitSphere * roamingPosMaxDistance;
randomPosition.y = 0;
RandomPos = transform.position + randomPosition;

//Loop untill find proper point
while(!NavMesh.SamplePosition(RandomPos, out hit, radius, NavMesh.AllAreas)
{
  randomPosition = UnityEngine.Random.insideUnitSphere * roamingPosMaxDistance;
  randomPosition.y = 0;
  RandomPos = transform.position + randomPosition;

  yield return new WaitForEndOfFrame();
}

finalPoint = hit.point;
}
Enter fullscreen mode Exit fullscreen mode

Top comments (0)