- Home /
Why does this Enemy Behaviour script cause frame rate issues?
I've been working on this script for a while now and it does as it is suppose to. It does use the A*Pathfinding system to help me out with the movement, but I've narrowed down the script that causes the most trouble through process of elimination. This script here seems to be the cause. Tell me if there is any optimizations that can be done to prevent this from lowering the frame rate by 20 fps.
using UnityEngine;
using System.Collections;
//Things to fix:
//1. Find out reason for AI to have low frame rate for every additional enemy that executes behavior as soon as the target is spotted.
//It isn't being caused by multiple redefinitions of the player's AI.
//It isn't being caused by not being able to completely execute the swarmer AI's lastLocation to none if there are multiple enemies in the way of the path.
[RequireComponent(typeof(EnemyDetection))]
public class EnemyBehavior : MonoBehaviour {
//This is the transform of the AI.
private Transform tr;
//This will be how long the searching has gone for since it started.
private float searchingTime;
//This is the time till the enemy will stop searching for the player.
public float timeTillStopSearching = 5f;
//This is the minimum distance required for the AI to begin attacking the player.
public float minAttackDistance = 3f;
//Is the enemy currently attacking
public bool isAttacking = false;
private float attackTimer = 0;
private float lookTimer = 0;
[System.Serializable]
public class AttackProperties
{
public float damage = 5f;//This is the amount of damage that will be subtracted.
public float coolDown = 2f; //This is the time it will take for every attack action to occur.
}
//This will setup the class so that we can refrence the class with writing attack.
public AttackProperties attack = new AttackProperties();
public enum AbyssEnemyType
{
none = 0,
swarmer = 1,
sleeper = 2
}
public AbyssEnemyType abyssEnemy = AbyssEnemyType.none;
public enum EnemyState
{
none = 0,
chase = 1,
searchArea = 2,
attack = 3,
lastLocationChase = 4
}
public EnemyState state = EnemyState.none;
[System.Serializable]
public class SwarmerComponents
{
//We save the original speed so we can change it back when the player turns off it's light.
public float originalSpeed;
//This is the rate that the AI will increase it's speed per second when exposed to light.
public float speedRate = 0.5f;
[HideInInspector]
public float currentVel;
[HideInInspector]
public float timeSpotted;
[HideInInspector]
public float newSpeed;
[HideInInspector]
public Vector3 lastTargetPos;
public bool moveToLastTargetPos = false;
}
public SwarmerComponents swarmerComp = new SwarmerComponents();
[System.Serializable]
public class SleeperComponents
{
/// <summary>
/// This will be the stage that the sleeper is up to if this is a sleeper.
/// </summary>
public int stage = 1;
public GameObject eyes;
public GameObject head;
public GameObject mouth;
public bool shouldTurnEyesOn;
public bool shouldOpenMouth;
public Material eyeColorOpen;
public Material eyeColorClose;
public float originalSpeed; //We save the original speed so we can change it back when the stage has been cycled all the way.
public float sprintSpeed = 5f; //This is the sprint speed that the AI will have in the third stage.
[HideInInspector]
public int currentStage = 0;
}
public SleeperComponents sleeperComp = new SleeperComponents();
private GameObject player;
private GameObject lighter;
private Light pLight;
private Vector3 lastKnownPos;
private bool lastKnownPosRecorded; //This will ensure tht the lastKnownPosition only gets retrieved once.
private float distanceToTarget; //This is the distance from the AI's position to the player's position.
public bool lookLeft;
public bool lookRight;
//This will be used to indicate if this AI should try and hunt the target.
public bool shouldHuntTarget = false;
//This will be a shortcut to refer to the attached script.
public EnemyDetection enemyDetection;
public AIFollow aiFollow;
// Use this for initialization
void Start ()
{
tr = transform;
if(enemyDetection == null)
enemyDetection = GetComponent<EnemyDetection>();
if(aiFollow == null)
aiFollow = GetComponent<AIFollow>();
if(abyssEnemy == AbyssEnemyType.swarmer)
{
swarmerComp.originalSpeed = aiFollow.speed;
}
//We will save the original speed of the sleeper AI so that
//we can reset it when the stages have cycled back to 1.
if(abyssEnemy == AbyssEnemyType.sleeper)
{
sleeperComp.originalSpeed = aiFollow.speed;
}
}
// Update is called once per frame
void Update ()
{
if(aiFollow.target != null)
{
Searching();
distanceToTarget = Vector3.Distance(tr.position, aiFollow.target.position);
if(player == null)
{
//This will only activate once because giving player a definintion will deactivate this if condition.
player = aiFollow.target.gameObject;
pLight = player.GetComponent<Light>();
}
}
if(aiFollow.target != null)
{
if(abyssEnemy == AbyssEnemyType.swarmer)
{
SwarmerBehavior();
}
if(abyssEnemy == AbyssEnemyType.sleeper)
{
SleeperBehavior();
}
if(state == EnemyState.chase)
{
Chase();
}
if(state == EnemyState.lastLocationChase)
{
LastLocationChase();
}
if(state == EnemyState.searchArea)
{
SearchArea();
}
if(state == EnemyState.attack)
{
Attack();
}
else
{
attackTimer = attack.coolDown;
}
if(state == EnemyState.none)
{
aiFollow.Stop();
}
}
}
//This is the behavior that the swarmer enemy type will have.
void SwarmerBehavior()
{
aiFollow.speed = swarmerComp.newSpeed + swarmerComp.originalSpeed;
if(shouldHuntTarget == true)
{
if(distanceToTarget <= minAttackDistance)
{
state = EnemyState.attack;
}
else
{
state = EnemyState.chase;
//The longer the AI has the target detected, the faster it will become.
swarmerComp.newSpeed += swarmerComp.speedRate * Time.deltaTime;
}
swarmerComp.lastTargetPos = aiFollow.target.position;
swarmerComp.timeSpotted = swarmerComp.newSpeed / swarmerComp.speedRate;
}
else
{
if(Vector3.Distance(tr.position, swarmerComp.lastTargetPos) > 1.5f)
{
state = EnemyState.lastLocationChase;
//If the AI doesn't have the exact position of the target, then slow it down little by little.
swarmerComp.newSpeed = Mathf.SmoothDamp(swarmerComp.newSpeed, 0, ref swarmerComp.currentVel, swarmerComp.timeSpotted * Time.deltaTime);
}
else
{
state = EnemyState.none;
swarmerComp.newSpeed = 0;
swarmerComp.timeSpotted = 0;
}
}
}
//This is the behavior that the sleeper enemy type will have.
void SleeperBehavior()
{
//With each time the sleeper is exposed to light, it will move on to its next stage.
//Here are the stages:
//1. Eyes open and look at the player's position with their head and it can rotate it's head 360 degrees.
//2. Open it's mouth and make a constant noise.
//3. Move fast to the light and make loud scream. When light is off, they will search around the area
//where the last location of the light being on was at.
//Then reset countdown.
if(state != EnemyState.searchArea) //We first make sure that the enemy isn't in the middle of searching so that we can prevent stage changes.
{
lastKnownPosRecorded = false;
lookLeft = false;
lookRight = false;
lookTimer = 0;
}
if(shouldHuntTarget == true)
{
//This variable will help stop continous changes of the stage and only activate once.
sleeperComp.currentStage = sleeperComp.stage;
//Turn head to follow where the player is located
sleeperComp.head.transform.LookAt(aiFollow.target);
if(sleeperComp.stage == 1)
{
//Make sure that the AI speed is the original speed it had.
aiFollow.speed = sleeperComp.originalSpeed;
//turn on sleeper's eyes
sleeperComp.shouldTurnEyesOn = true;
Debug.Log ("Look at the player");
}
if(sleeperComp.stage == 2)
{
sleeperComp.shouldOpenMouth = true;
Debug.Log ("Open mouth and make constant noise");
}
if(sleeperComp.stage == 3)
{
aiFollow.speed = sleeperComp.sprintSpeed;
Debug.Log ("Move fast to location of light");
}
}
else if(shouldHuntTarget == false && sleeperComp.currentStage == sleeperComp.stage)
{
if(sleeperComp.stage != 3)
{
sleeperComp.head.transform.rotation = Quaternion.Slerp(tr.rotation, Quaternion.LookRotation(tr.forward), 20 * Time.deltaTime);
Debug.Log(tr.forward);
sleeperComp.stage += 1;
}
}
if(sleeperComp.shouldTurnEyesOn == true)
{
Renderer[] eyes = sleeperComp.eyes.GetComponentsInChildren<Renderer>();
//Change eye color to red
foreach(Renderer eye in eyes)
{
eye.material = sleeperComp.eyeColorOpen;
}
}
else
{
Renderer[] eyes = sleeperComp.eyes.GetComponentsInChildren<Renderer>();
//Change eye color to original color.
foreach(Renderer eye in eyes)
{
eye.material = sleeperComp.eyeColorClose;
}
}
if(sleeperComp.shouldOpenMouth == true)
{
sleeperComp.mouth.renderer.enabled = true;
}
else
{
sleeperComp.mouth.renderer.enabled = false;
}
//If we are allowed to hunt the target.
if(shouldHuntTarget == true)
{
//We will change the enemy state to attack when in range.
if(distanceToTarget <= minAttackDistance)
{
state = EnemyState.attack;
}
else if(distanceToTarget > minAttackDistance && sleeperComp.currentStage == sleeperComp.stage && sleeperComp.stage == 3)
{
state = EnemyState.chase;
}
}
else
{
if(sleeperComp.currentStage == sleeperComp.stage && sleeperComp.stage == 3)
{
state = EnemyState.searchArea;
}
else
{
state = EnemyState.none;
}
}
}
void Searching()
{
if(enemyDetection.targetSeen == true && lightDetected() == true)
{
shouldHuntTarget = true;
}
if(beginSearchStop() == true)
{
Debug.Log("BeginCountDown");
searchingTime += Time.deltaTime;
if(timeTillStopSearching <= searchingTime)
{
shouldHuntTarget = false;
enemyDetection.targetSeen = false;
searchingTime = 0;
enemyDetection.startPlayerUnseenCountDown = false;
}
}
else
{
searchingTime = 0;
}
}
void Chase()
{
//Make the ai follow the player.
aiFollow.Resume();
}
//This function will have the enemy search the last know location of the AI.
void SearchArea()
{
Debug.Log("SearchTheArea");
//Since this condition will always be true for the first time it performs
//the search function, it will always activate. Also it will only activate
//once because aiFollow.Stop(); sets the canMove and canSearch bools to false.
if(lastKnownPosRecorded == false)
{
//This will store the last known position of the target.
lastKnownPos = new Vector3(aiFollow.target.position.x, tr.position.y, aiFollow.target.position.z);
aiFollow.Stop();
lookLeft = true;
//We recorded the position so we don't want to reactivate this statement again.
lastKnownPosRecorded = true;
}
if(lastKnownPosRecorded == true)
{
lookTimer += Time.deltaTime;
while(lookTimer > 4)
{
lookTimer = 0;
if(lookLeft == true)
{
lookLeft = false;
lookRight = true;
break;
}
if(lookRight == true)
{
lookRight = false;
break;
}
}
if(lookLeft == true && lookTimer <= 4)
{
tr.rotation = Quaternion.Slerp(tr.rotation, Quaternion.LookRotation(lastKnownPos + new Vector3(12,0,0) - tr.position), 0.4f * Time.deltaTime);
}
else if(lookRight == true && lookTimer <= 4)
{
tr.rotation = Quaternion.Slerp(tr.rotation, Quaternion.LookRotation(lastKnownPos + new Vector3(-12,0,0) - tr.position), 0.4f * Time.deltaTime);
}
else if(lookLeft == false && lookRight == false)
{
//After we have finshed looking both left and right...
//We want to reset the stage if it is up to its final stage
sleeperComp.stage = 1;
//Turn off sleeper's eyes and mouth.
sleeperComp.shouldTurnEyesOn = false;
sleeperComp.shouldOpenMouth = false;
lastKnownPosRecorded = false;
}
}
}
void Attack()
{
aiFollow.Stop();
if(attackTimer > 0)
{
attackTimer -= Time.deltaTime;
}
if(attackTimer < 0)
{
attackTimer = 0;
}
if(attackTimer == 0)
{
Health th = (Health)aiFollow.target.gameObject.GetComponent("Health");
th.CurrentHealthModification(-attack.damage, true);
attackTimer = attack.coolDown;
}
}
//This is more specified to the swarmer enemy type
void LastLocationChase()
{
aiFollow.canSearch = false;
aiFollow.canMove = true;
}
//This will determine when the AI should start a timer for how
//long until the AI should stop following the player.
public bool beginSearchStop()
{
//If either the enemy can't have a line of sight on the player or the light they have on is no longer present.
if(enemyDetection.targetSeen == false || lightDetected() == false)
return true;
else
return false;
}
//This will be used to detect the condition of the player's light.
public bool lightDetected()
{
if(pLight != null && aiFollow.target != null)
{
if(pLight.intensity == 1f && distanceToTarget <= enemyDetection.visualDistance)
{
return true;
}
else if(pLight.intensity == 0.25f && distanceToTarget <= enemyDetection.visualDistance * 0.4f)
{
return true;
}
else
{
return false;
}
}
else
return false;
}
}
It is especially prominent if there are multiple AIs. Usually when there are multiple AI's, it will actually cause the frame rate to permanently lose frames even if the monster has lost visibility of the player. If there is any confusion, please let me know.
Didn't really read the code, but try to remove your debug calls. They tend to be very framerate reducing. Remove all the following:
Debug.Log("I drop frames");
And see if that does not help the case.
At line 51 you get into this hell of conditionals. Each frame you're going to check all of those cases for every script you have running. You should use a switch statement ins$$anonymous$$d:
switch(abyssEnemy)
{
case AbyssEnemyType.swarmer:
SwarmerBehavior();
break;
case AbyssEnemyType.swarmer:
SwarmerBehavior();
break;
case AbyssEnemyType.sleeper:
SleeperBehavior();
break;
}
switch (state)
{
case EnemyState.chase:
Chase();
break;
case EnemyState.lastLocationChase:
LastLocationChase();
break;
case EnemyState.searchArea:
SearchArea();
break;
case EnemyState.attack:
Attack();
break;
case EnemyState.none:
aiFollow.Stop();
break;
}
The only thing out of that block I didn't place was the following line, which you may have to put into each case other than EnemyState.attack. I didn't place it because I wasn't totally sure how your scripts work with each other.
attackTimer = attack.coolDown;
Also, you should cache any results of GetComponent, as it takes a bit of time to execute. If you are constantly checking against different objects, then use a dictionary to store a reference for that gameObject's component in question. Ex:
private Dictionary<GameObject, Health> healthCache;
Health GetHeatlthForGameObject(GameObject go)
{
if (!healthCache.Contains$$anonymous$$ey(go))
healthCache.Add(go, go.GetComponent< Health >());
return healthCache[go];
}
The above approach works, but it would be even more efficient to have a central place where these values are cached so that interested objects can lookup the value from a singleton.
Finally, you've offered no visibility of the AIFollow script, so I don't know what all is happening in Stop(). Perhaps the reason for the permanent frame loss stems from the fact that something isn't actually fully stopping when that method is called?
Answer by PaceKeeper · Dec 01, 2013 at 02:31 AM
Thank you for your suggestions. The Debugs are what were causing the problem in the first place. I had no idea that just around 4 debugs on the same script could cause such impact on performance. I thank you OrangeLightning for proposing the correct answer and you as well iwaldrop for giving me helpful advice on performance optimization.
Your answer
Follow this Question
Related Questions
Low FPS when against a wall? 1 Answer
low FPS when battery is low ( unity android ) 0 Answers
Making a Terrain a child of something dramatically reduces framrate 1 Answer
Extremely poor Android performance, even in simple scenes 0 Answers
Framerate drops from 60 to 20 because of meshrenderer? 0 Answers