- Home /
Navmesh AI - Huge performance impact
So I've been working on a stealth game for a while. Until now, I've been working on an enemy AI, that can hear and see the player. The only problem I'm facing, is that one enemy with a Navmesh Agent and my AI script has almost a 60FPS performance impact. I know my script needs to be optimized, I just have no idea what to actually optimize. If anyone could give me some pointers, that'd be appreciated.
(P.S. I'm using a quite a powerful PC, so it shouldn't have anything to do with it.)
using UnityEngine;
using System.Collections;
public class EnemyCode : MonoBehaviour {
private GameObject player; //The player. Automatically assigned, you just need to have an object with the tag "Player" in your scene. To tag an object, 1.) select an object 2.) Go to "tag" 3.) Assign the player tag on the player.
public GameObject Enemy; //The enemy. DON'T CHANGE THIS.
public GameObject HearingArea; //A sphere collider that represents the area where the enemy can hear the player. Not necessary, but useful for debugging.
private NavMeshAgent enemyAI; //The AI/'brain' of the enemy. DON'T CHANGE THIS.
private Vector3 enemy_StartPoint; //The point where the enemy was standing before moving to the player. Not necessary, but it makes the enemy a bit more smart, since it won't just start sitting where it last saw the player.
public static bool enemy_IsAggressive = false; //Is the enemy aggressive? Used for feeding information to the player, aswell as alarming other enemies etc.
public bool DoesReturnToOriginalPoint = true; //Does the enemy return to it's original spawn point after losing the player?
int layerMask = 1 << 9; //Used for the enemy's sight. SET THE LATTER VALUE TO THE SAME AS THE LAYER THE PLAYER IS ON.
public Transform[] Waypoints;
int currentWaypoint = 0;
int maxWaypoint;
public float minWaypointDistance = 1f;
[SerializeField] private Floats floats = new Floats ();
[System.Serializable]
public class Floats
{
public float MaxDetectRange; //The maximum range of detection. If the player is visible but is outside of this radius, the enemy can't really 'see' the player. CAN BE CHANGED FROM THE INSPECTOR.
public float RangeOfHearing; //The radius of the enemy's hearing. CAN BE CHANGED FROM THE INSPECTOR.
public float SenseOfPrecenseRange; //Use an approximate value of 1.25 to detect the player when they're touching the enemy. CAN BE CHANGED FROM THE INSPECTOR.
}
[SerializeField] private Senses senses = new Senses ();
[System.Serializable]
public class Senses
{
public bool HasSight = true; //Does the enemy have a sight? Used for humans, cameras, robots etc. CAN BE TOGGLED FROM THE INSPECTOR.
public bool HasHearing = true; //Can the enemy hear the player? Used for humans, sensors etc. CAN BE TOGGLED FROM THE INSPECTOR.
public bool SensesPresence = true; //Does the enemy sense if the player is right behind them/around? Used for humans. CAN BE TOGGLED FROM THE INSPECTOR.
}
[SerializeField] private Debugging debugging = new Debugging ();
[System.Serializable]
public class Debugging
{
public bool ShowHearingArea = true; //Is the collider that represents the enemy's hearing radius displayed in the scene view while playing? Useful for debugging. CAN BE TOGGLED FROM THE INSPECTOR.
public bool DrawLineFromEnemyToPlayer = true; //Useful for debugging, displays a line between the player and the enemy. Can be used to detect object rotation/transform issues. CAN BE TOGGLED FROM THE INSPECTOR.
}
void Start()
{
layerMask = ~layerMask; //Used for the enemy's sight. Inverts the layer mask.
if (debugging.ShowHearingArea) //REMOVE FROM FINAL BUILD
{
SphereCollider hearing = HearingArea.GetComponent<SphereCollider> (); //Determines the collider representing the hearing radius.
hearing.radius = floats.RangeOfHearing / 2; //Scale the sphere collider representing the hearing radius to the correct size.
}
else
{
HearingArea.SetActive(false);
}
player = GameObject.FindGameObjectWithTag ("Player"); //Determines the player - If there isn't an object with a 'Player' tag in the scene, this will throw an error.
if(player == null)
{
Debug.LogWarning("There isn't an object with the tag 'Player' in the scene!");
}
enemy_StartPoint = Enemy.transform.position; //Determines the starting position of the enemy - This is used to make the enemy return back to its original 'patrol area'.
enemyAI = Enemy.GetComponent<NavMeshAgent> (); //Determines the NavMesh Agent attached to the enemy.
if (enemyAI == null) //If there isn't a NavMesh Agent attached to the enemy...
{
Debug.LogWarning("Enemy AI not found!"); //Throw an error...
}
InvokeRepeating ("CheckAlarm", 1, 0.5f); //Checks if an alarm occurs every half a second.
maxWaypoint = Waypoints.Length - 1;
}
void Update()
{
if (!enemy_IsAggressive)
Patrol ();
if (senses.HasSight) //If the enemy has a sight, this part of the code will be accessed.
{
if (player.renderer.IsVisibleFrom (this.camera) && Vector3.Distance (player.transform.position, Enemy.transform.position) < floats.MaxDetectRange) //If the enemy can see the player AND is in range of the player...
{
if(debugging.DrawLineFromEnemyToPlayer) //REMOVE FROM FINAL BUILD
{
Debug.DrawLine (transform.position, player.transform.position, Color.white); //Draws a line from the enemy's position to the player's, useful for debugging.
}
if (!Physics.Linecast (transform.position, player.transform.position, layerMask)) //If the line doesn't hit anything but the player (nothing is in the way)...
{
PlayerDetected(); //Let all hell break loose (in other words, detect the player).
}
}
else //If the player isn't visible to the enemy...
if (enemy_IsAggressive && Vector3.Distance (player.transform.position, Enemy.transform.position) < floats.MaxDetectRange) //...but the enemy is still in a hostile state from chasing the player...
{
PlayerDetected(); //...the enemy will keep making its way to the player.
}
else
if(PlayerFunctionality.Alarm)
{
PlayerDetected();
}
else //If the player gets away and isn't inside the enemy's detection radius...
{
enemy_IsAggressive = false; //... the enemy will become not hostile.
//enemyAI.SetDestination (enemy_StartPoint); //...the enemy will keep making its way to the player.
}
}
if (senses.HasHearing) //If the enemy can hear, this area of the code will be accessed.
{
if(Vector3.Distance(transform.position, player.transform.position) <= floats.RangeOfHearing) //If the player is within the range of the enemy's hearing...
{
CalculateNoise();
}
}
if (senses.SensesPresence) //If the enemy senses that the player is nearby, this area of code will be accessed. THIS DOES NOT MEAN THAT THE ENEMY IS ALERTED, THIS SIMPLY MEANS THAT THE ENEMY WILL SENSE THE PLAYER, IF IT'S TOUCHING THE ENEMY.
{
if(Vector3.Distance(transform.position, player.transform.position) <= floats.SenseOfPrecenseRange) //Check to see if the player is within the radius where the enemy can detect them.
{
PlayerDetected(); //If they are, run the PlayerDetected() function.
}
}
}
void CalculateNoise()
{
if (Vector3.Distance (player.transform.position, transform.position) > floats.RangeOfHearing)
{
return;
}
else
if(PlayerFunctionality.NoiseLevel >= 9) //Start detecting if the player is making enough noise. If they are...
{
PlayerDetected(); //...Again, let all hell break loose.
return;
}
else
if(PlayerFunctionality.NoiseLevel >= 7 && Vector3.Distance(transform.position, player.transform.position) <= 8) //If the player makes noise of level 7, and is under 8 units away from the enemy...
{
PlayerDetected(); //Detect the player.
return;
}
else
if(PlayerFunctionality.NoiseLevel >= 5 && Vector3.Distance(transform.position, player.transform.position) <= 6) //Same thing goes for the last lines here.
{
PlayerDetected();
return;
}
else
if(PlayerFunctionality.NoiseLevel >= 4 && Vector3.Distance(transform.position, player.transform.position) <= 5) //This same process is repeated with different values, because we want the enemy to hear different levels of noise from different distances.
{
PlayerDetected();
return;
}
else
if(PlayerFunctionality.NoiseLevel >= 3 && Vector3.Distance(transform.position, player.transform.position) <= 4)
{
PlayerDetected();
return;
}
else
if(PlayerFunctionality.NoiseLevel >= 2 && Vector3.Distance(transform.position, player.transform.position) <= 3)
{
PlayerDetected();
return;
}
}
void PlayerDetected() //The function to run when the player is seen by the enemy.
{
enemyAI.SetDestination (player.transform.position); //Set the enemy's destination to where the player is.
enemy_IsAggressive = true; //Set the enemy into an aggressive state.
//Simple as that.
}
void CheckAlarm()
{
if(PlayerFunctionality.Alarm)
{
PlayerDetected();
}
}
void Patrol()
{
enemyAI.SetDestination (Waypoints [currentWaypoint].position);
if (Vector3.Distance (transform.position, Waypoints [currentWaypoint].position) <= minWaypointDistance) {
// Have we reached the last waypoint?
if (currentWaypoint == maxWaypoint)
// If so, go back to the first waypoint and start over again
currentWaypoint = 0;
else
// If we haven't reached the Last waypoint, just move on to the next one
currentWaypoint++;
}
}
}
Answer by tanoshimi · Dec 21, 2014 at 08:07 PM
There's lots of scope for optimisation in your code (like you appear to be calculating Vector3.Distance(transform.position, player.transform.position)
in at least four separate places of every frame...).
However, the thing that's killing it is that, in Patrol(), you're calling SetDestination, forcing your navagent to recalculate a new route across the navmesh every frame. Only call SetDestination when the destination actually changes.
Thanks for the quick answer. Should I try storing the distance between the enemy and the player in a variable? Or would there be a wiser way to approach this?
Definitely. Calculate and store it once at the beginning of Update() and then re-use that same variable. In fact, do you really need to calculate it every frame? You could move it into a separate function that updated, say, twice a second, which might be plenty.
I tried storing the distance and the noise values to two variables, with the distance being updated 2x a second, and the noise being updated 4x a second. Still, there wasn't a significant change in performance ($$anonymous$$y FPS rose by 2).
Okay, so I noticed that using cameras as eyes for the enemies seems to be the thing affecting the performance. When I disable the cameras, my FPS increases by 200.
Answer by VertexSoup · Jan 21, 2015 at 11:27 PM
You can swap cameras you are using for detection to invisible cone mesh and do detection based on collision and some verification afterward...
Your answer
Follow this Question
Related Questions
raycast hit not detecting && Nav mesh agent ai stealth 1 Answer
How to get a velocity unit vector from a NavMeshAgent? 1 Answer
Enemy's NavMesh not working 2 Answers
(2 hidden skinned meshes) or (1 extra draw call) for character props? 2 Answers
Simple Animation showing as ~6.3 in Xcode console profiler 0 Answers