Entities patrolling (NPC). Log #8 (Unity, C#)
01.05.2017

          Maybe it's too soon for this, but I will focus on other subject than camera and character, and start working on another importatnt thing. Which is patrol system. Each NPC is an entity living its own simple life. Enemies like any creatures or anything, must move somehow. They will have its own patrol loops and controllers dealing with their movement. So I made up simple patrolling system, where each entity will have its own radius determinig how far they can go and in this radius I will generate few random points. Because entity needs to move it will have its own NavMeshAgent, whom I am going to set these points as target waypoints.

          This will require to create script EntityController in folder Scripts>Entities. At first I will define the size of NPC's radius. But I can't just generate points in flat area, respectively I have to consider a height of terrain at the point position. I will use raycast for this. So I get raycast point with height coord, but I need to change X a Z position randomly of this raycast inside the radius. Function Random.Range() will help with this. This function returns random number in range I refer as two parameters. For X and Z coords I use Vector2 because Y is static, for instance 10f above NPC's Y position. So now I have Vector2 offset, randomly generated in radius (in positive and negative axis, otherwise points will be only in one quadrant of radius circle), and Vector3 origin, which is NPC's position but with added Y position. I put all of it into for and by waypointCount I tell exactly how many times I need to generate new position in which I cast ray downward. Then, out of RayCast hit-point I get new waypoint that I store in the array of waypoints. It seems to be confusing, but the code is actually simple.

 

void Start()
{
	waypoints = new Vector3[waypointCount];
	origin = transform.position;
	origin.y += 10;
	
	GenerateWaypoints();
}

void GenerateWaypoints()
{
	for (int c = 0; c < waypointCount; c++)
	{
		offset = new Vector2(Random.Range(-waypointRadius, waypointRadius) + origin.x, Random.Range(-waypointRadius, waypointRadius) + origin.z);
		
		if (Physics.Raycast(new Vector3(offset.x, origin.y, offset.y), - Vector3.up, out rHit))
		{
			waypoints[c] = rHit.point;
		}
	}
}

 

          I have random waypoints and all I need to do now, is to set the agent. I will call it patrol. I will start patrol in default Start() function then create function StartPatrol() with Vector3 destination as parameter. Current state of patrol I will store to variable patrolPending, then set the target and start the agent. First generated waypoint will be srating waypoint. To tell if the agent reached its destination I need to calculate distance between NPC and waypoint. I will do it the same way as I did it for character. So I will create function EntityToWaypointDistance(). As the entity reaches its destination, new waypoint will be set and this cycle will be repeated. I would like to avoid choosing the same waypoint as the current one, so I will create function SetRandomWaypointIndex()

 

void Start()
{
	//Waypoint system
	waypoints = new Vector3[waypointCount];
	nma = GetComponent<NavMeshAgent>();
	nma.stoppingDistance = 0.1f;
	nma.angularSpeed = 500;

	origin = transform.position;
	origin.y += 10;

	GenerateWaypoints();

	waypointIndex = 0;
	playerInteraction = false;
	StartPatrol(waypoints[waypointIndex]);
}

void StartPatrol(Vector3 destination)
{
	patrolPending = true;
	nma.SetDestination(destination);
	nma.Resume();
}

void SetRandomWaypointIndex()
{
	int oldIndex = waypointIndex;
	waypointIndex = Random.Range(0, waypointCount);        
	if (waypointIndex == oldIndex)
	{
		SetRandomWaypointIndex();
	}
}

void EntityToWaypointDistance()
{
	distance = Vector3.Distance(transform.position, waypoints[waypointIndex]) - (nma.baseOffset * 1.85f);
}

void Update()
{
	EntityToWaypointDistance();
	if (distance <= 0.1f && !movingTowardsPlayer)
	{
		nma.Stop();
		SetRandomWaypointIndex();
		EntityToWaypointDistance();
		StartPatrol(waypoints[waypointIndex]);
	}
}

 

          So we can test our patrolling system on few cubes. I will add few cubes to the scene and attach EntityController scrip to them as component. Set amount of waypoints and radius and it is all. There is small detail. Adding NavMeshAgent component to all objects is quite annoying, so by adding line [RequireComponent(typeof(NavMeshAgent))] right above the class name is caused that inspector will automatically add the required component to the object when script is attached.