How would I go about introducing some noise into my NavMesh agents paths?
E.G
If I have 100 characters and I tell them to go to point A they will all follow the same shortest path to this position, I would like the paths to be less optimal and more random to create a crowd movement effect, at the moment it looks like one large queue.
Is there a way to tell the NavMesh agents path calculator to introduce some noise into the calculations so they are less optimal?
I now have a working path generator that introduces noise however I am unable to assign the new path to my agent, The only thing I can think of is to set my agents destination to each point in the path and assign the next point on arrival.
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
public class PathFollower : MonoBehaviour
{
public Transform destination;
private Vector3 currentDest;
public NavMeshAgent agent;
public float noise = 10.0f;
private float oldNoise;
public NavMeshPath before;
public NavMeshPath after;
public List<Vector3> afterPoints;
/// <summary>
/// Generate initial path
/// </summary>
void Start()
{
GeneratePath();
}
/// <summary>
/// Generate a new noisy path
/// </summary>
void GeneratePath()
{
NavMeshPath path = new NavMeshPath();
if( agent.CalculatePath( destination.position, path ) )
{
agent.path = AddNoise( -noise, noise, path );
oldNoise = noise;
currentDest = transform.position;
}
else
{
Debug.Log( "FAILED to generate path" );
}
}
/// <summary>
/// Introduce noise into the path
/// </summary>
/// <param name="minNoise"></param>
/// <param name="MaxNoise"></param>
/// <param name="path"></param>
/// <returns></returns>
NavMeshPath AddNoise( float minNoise, float MaxNoise, NavMeshPath path )
{
NavMeshPath noisePath = new NavMeshPath();
List<Vector3> newPathCorners = new List<Vector3>();
Vector3[] corners = path.corners;
newPathCorners.Add( corners[0] );
for( int i = 1; i < corners.Length; ++i )
{
Vector3 cornerWithNoise = corners[i] + ( Random.insideUnitSphere * Random.Range( minNoise, MaxNoise ) );
NavMeshPath tempPath = new NavMeshPath();
NavMesh.CalculatePath( newPathCorners[newPathCorners.Count - 1], cornerWithNoise, int.MaxValue, tempPath );
// Append onto current path
newPathCorners.AddRange( tempPath.corners );
}
afterPoints = newPathCorners;
// Add the corners to the noisePath
//noisePath.corners = newPathCorners.ToArray();
before = path;
after = noisePath;
return noisePath;
}
/// <summary>
/// Check for changes, if any occur update the path
/// </summary>
void Update()
{
if( oldNoise != noise || currentDest != transform.position )
{
GeneratePath();
}
}
/// <summary>
/// Draw both paths
/// </summary>
void OnDrawGizmos()
{
if( before != null after != null )
{
Gizmos.color = Color.green;
for( int i = 1; i < before.corners.Length; ++i )
{
Gizmos.DrawLine( before.corners[i - 1], before.corners[i] );
}
Gizmos.color = Color.blue;
for( int i = 1; i < after.corners.Length; ++i )
{
Gizmos.DrawLine( after.corners[i - 1], after.corners[i] );
}
for( int i = 1; i < afterPoints.Count; ++i )
{
Gizmos.DrawLine( afterPoints[i - 1], afterPoints[i] );
}
}
}
}
i am sorry maybe my question is a little bit out of topic
how can you draw that original path (green ones) without calculate the noised ones?
i tried to understanding your code, but still, i didnt have any idea
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
/// <summary>
/// Generates a randomised path to a destination.
/// This prevents the agent from travelling in an optimised path to a point which
/// does not always look realistic, especially when a large number of agents are
/// all heading to the same location.
/// </summary>
public class NavAgentEx : MonoBehaviour
{
#region Properties
// Our navmesh agent
public NavMeshAgent agent;
// Maximum distance from each path point.
public float maxDistanceFromPathNode = 15;
// Maximum number of points to generate between the NavMeshPath corners.
public int maxIntermediatePoints = 3;
// The generated path that contains noise
public Queue<Vector3> generatedPath;
private Vector3 lastDestination;
#endregion Properties
//////////////////////////////////////////////////////////////////////
/// <summary>
/// Moves to a location using a noisy un-optimised path
/// </summary>
/// <param name="target"></param>
/// <returns>True if a path was found</returns>
//////////////////////////////////////////////////////////////////////
public bool WanderMoveTo( Vector3 target )
{
NavMeshPath path = new NavMeshPath();
if( agent.CalculatePath( target, path ) )
{
lastDestination = target;
// Now add noise
GeneratePath( path );
GoToNextCorner();
return true;
}
return false;
}
//////////////////////////////////////////////////////////////////////
/// <summary>
/// Move to a location using an optimised path.
/// </summary>
/// <param name="target"></param>
/// <returns>True if a path was found</returns>
//////////////////////////////////////////////////////////////////////
public bool MoveTo( Vector3 target )
{
NavMeshPath path = new NavMeshPath();
if( agent.CalculatePath( target, path ) )
{
lastDestination = target;
return true;
}
return false;
}
//////////////////////////////////////////////////////////////////////
/// <summary>
/// Checks if the agent is at or close to its destination
/// </summary>
/// <param name="threshold"></param>
/// <returns></returns>
//////////////////////////////////////////////////////////////////////
public bool HasArrived( float threshold )
{
return agent.remainingDistance < threshold ? true : false;
}
//////////////////////////////////////////////////////////////////////
/// <summary>
/// Checks if the agent is at its destination
/// </summary>
/// <returns></returns>
//////////////////////////////////////////////////////////////////////
public bool HasArrived()
{
return agent.remainingDistance == 0 ? true : false;
}
//////////////////////////////////////////////////////////////////////
/// <summary>
/// Apply noise to a path
/// </summary>
/// <param name="path"></param>
//////////////////////////////////////////////////////////////////////
public void GeneratePath( NavMeshPath path )
{
Vector3[] corners = path.corners;
// First generate additional corners along the original path.
List<Vector3> extendedPath = new List<Vector3>();
for( int i = 1; i < corners.Length; ++i )
{
Vector3 a = corners[i - 1];
Vector3 b = corners[i];
// Add original corner first
extendedPath.Add( a );
// Generate some intermediate points between a and b
int intermediatePoints = Random.Range( 0, maxIntermediatePoints );
for( int j = 0; j < intermediatePoints; ++j )
{
// Get the points location.
Vector3 intermediate = Vector3.Lerp( a, b, ( float )i / ( float )intermediatePoints );
extendedPath.Add( intermediate );
}
}
extendedPath.Add( corners[corners.Length - 1] );
// Now apply some noise to each point
generatedPath = new Queue<Vector3>();
for( int i = 0; i < extendedPath.Count; ++i )
{
Vector3 noisyPoint = ( Random.insideUnitSphere * maxDistanceFromPathNode ) + extendedPath[i];
// Now get the nearest place on the nav mesh
NavMeshHit hit = new NavMeshHit();
if( NavMesh.SamplePosition( noisyPoint, out hit, maxDistanceFromPathNode, agent.walkableMask ) )
{
// Add the found point
generatedPath.Enqueue( hit.position );
}
}
}
//////////////////////////////////////////////////////////////////////
/// <summary>
/// When we use a noisy path we are unable to apply it make to the NavMeshAgent
/// so we must travel to each point as a separate path.
/// This checks if we have reached our destination and moves us to the next.
/// </summary>
/// <returns></returns>
//////////////////////////////////////////////////////////////////////
IEnumerator CheckPath()
{
while( Application.isPlaying )
{
yield return new WaitForSeconds( 1 );
if( HasArrived( 1 ) agent.pathStatus != NavMeshPathStatus.PathPartial )
{
GoToNextCorner();
break;
}
}
}
//////////////////////////////////////////////////////////////////////
/// <summary>
/// Move to the next position in our generated path
/// </summary>
//////////////////////////////////////////////////////////////////////
void GoToNextCorner()
{
if( generatedPath != null generatedPath.Count > 0 )
{
if( agent.SetDestination( generatedPath.Dequeue() ) )
{
if( generatedPath.Count > 0 )
{
StartCoroutine( "CheckPath" );
}
}
}
}
//////////////////////////////////////////////////////////////////////
/// <summary>
/// Draw path in scene view when object is selected.
/// </summary>
//////////////////////////////////////////////////////////////////////
void OnDrawGizmosSelected()
{
if( generatedPath != null )
{
Gizmos.color = Color.green;
Vector3[] points = generatedPath.ToArray();
for( int i = 1; i < points.Length; ++i )
{
Gizmos.DrawLine( points[i - 1], points[i] );
}
Gizmos.color = Color.blue;
Gizmos.DrawLine( transform.position, lastDestination );
}
}
}
thats very nice, and i have some question in gizmo drawing (which I care most, since that is very cool and the gizmo drawing for debugging is very helpful)
i made some plane, so there are plenty navmesh layer and i gave different cost for it, so the agent not walk in best shortest path, but based in cost, and how come the gizmo line is still the shortest path (not the path that agent followed) ? is it any function to calculate ‘true’ path that followed by the agent? instead of calculatepath (or maybe changing the variables? i really stuck in making ‘right’ gizmo line that followed by the agent)