Patrolování entit (NPC). Log #8 (Unity, C#)
14.03.2017

                Možná je na tohle v téhle fázi brzo, ale trochu se odreaguji od hráče a kamery a začnu dělat na další důležité věci. A tou je patrolování. Každé NPC (non player character) je entita, která bude žít svůj zatím jednoduchý život. Nepřátelé, jako různé kreatury a podobně, se musejí nějak samostatně hýbat. Mít své vlastní patrolovací trasy (smyčky) a vůbec nějaký kontroler který se bude starat o jejich pohyb. Proto jsem vymyslel způsob jak takový systém vytvořit. Jeho princip je snadný. Každá entita bude mít určitý rádius v kterém se bude pohybovat. V tomhle rádiusu vygeneruju určitý počet náhodných bodů s pořadím. Protože se musí entita hýbat, bude mít vlastního navmesh agenta. Tomuto agentovi jen budu postupně nastavovat tyhle waypointy patroly.

                K tomu je zapotřebí vytvořit skript "EntityController" v "Scripts">"Entites". První si definuji velikost radiusu ve kterém se bude NPC pohybovat. Nemůžu ale generovat jen v určitém plochém rozsahu okolo NPC, respektive potřebuji taky brát v potaz výškové body terénu. Souřadnice waypoint bodů i s výškou získám z raycastu, který bude směřovat paprsky rovnou pod sebe. Musím ale měnit náhodně pozici raycastu. O to se postará funkce Random.Range(), ta mi vrátí náhodný bod v rozsahu který jí předám. Tohle provedu pro X a Z. Souřadnice stačí dvě, takže je uložím do offsetu typu Vector2, výšku Y raycastu nastavuji staticky, například na 10f nad výškou NPC. Takže mám offset který je náhodně generovaný v rádiusu (v kladných i záporných hodnotách, jinak se budou body generovat jen v jednom kvadrantu os) a pak Vector3 origin, což je pozice NPC, ale s navýšenou hodnotou Y. Vložím vše do for a raycast se v každém kroku pohybuje náhodně, tolikrát kolikrát potřebuji. Když budu vrhat z těchto náhodných pozic paprsek přímo dolů, budu ukládat RaycastHitpoint jako waypoint do pole waypointů.

 

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

 

                Body tedy mám a zbývá jen aby po nich NPC chodilo. Budu to nazývat patrolou. Pro začátek patrolování spustím hned jak se spustí hra. Vytvořím funkci StartPatrol() s parametrem destinace. Uložím stav patrolování do proměnné patrolPending nastavím agentovi cíl a spustím agenta. Před spuštěním musím ovšem nastavit který waypoint je cílový, tím je na začátku vždy první vygenerovaný waypoint. Abych mohl říct že NPC dorazil k waypointu, musím počítat vzdálenost mezi entitou a waypointem. Výpočet provedu stejně jako u postavy hráče a udělám funkci EntityToWaypointDistance(). Jakmile dorazí entita do cíle, nastaví se nový náhodný waypoint a celý cyklus se neustále opakuje. Taky jsem zabezpečil aby se waypoint nevybral stejný jako byl aktuální, o tohle se stará funkce SetRandomWaypointIndex(). Takže patrol system vypadá následovně

 

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

 

                Patrolování jde vyzkoušet jednoduše na několika krychlích. Vložím do scény tři krychle a přiřadím jim skript EntityController jako komponentu. Nastavím jim počet waypoint bodů a radius. Ještě malý detail, abych nemusel každému objektu přidávat komponentu NavMesh Agenta, tak před začátek těla třídy přidám řádek [RequireComponent(typeof(NavMeshAgent))], to zajistí, že pokaždé když tento komponent přiřadím k instanci nějakého objektu, automaticky přidá požadovanou komponentu. Možná je potřeba upravit navmesh agent radius, aby se krychle nestřetávaly.