C# Enemy Stuck In Attack Mode

I’m so sorry I’m back here again. I always feel bad if I need to come here for help and I don’t like pestering people too much. In my enemy state system, everything seems to be mostly working, and some of the problems I can sort out myself, but this one is tricky. When an enemy in my scene goes into its attack state (when the player is nearby) it shoots projectiles which harms the player. However, once it goes into this particular state, it won’t leave. I’ve gone through my scripts to figure out why it’s stuck there but as far as I can see (as an intermediate programmer) there’s no reason why it shouldn’t exit that state. What should happen is that my player and enemy character is surrounded by a sphere collider, where if the player is in range of the enemy, the enemy attacks accordingly, and then when the player exits the trigger and gets away, the enemy should go back to alert state when they search for the player. It’s very odd. If you could look at my code and see what the issue is, I’d be very grateful. It’s important that I get this working and obviously I don’t want it to look broken, which at the moment it does. It’s frustrating for me because my code should be really easy to manipulate but it isn’t. I’ll post the main enemy state script, the attack state script, the chase state script, and also the alert state script.

using UnityEngine;
using UnityEngine.UI;
using System.Collections;

public class StatePatternEnemy : MonoBehaviour {

    //Sets speed for enemy turning while searching for the player
    public float searchingTurnSpeed = 120f;
    //The amount of time the enemy spends looking for the player
    public float searchingDuration = 4f;
    //The range in which the enemy can detect or see the player
    public float sightRange = 20f;
    //This sets up waypoints for the enemy to follow
    public Transform[] wayPoints;
    public Transform eyes;
    public Vector3 offset = new Vector3 (0f,0.5f,0f);
    //Indicates what state the enemy is in
    public MeshRenderer meshRendererFlag;
    //Stores reference to bullet prefab
    public GameObject bulletPrefab;
    //Stores reference to text from message box UI component
    public Text messageBoxText;
    //Set location for bullet spawn location
    public Transform bulletSpawn;
    //Reference to the audio source.
    public AudioSource enemyAudio;
    //The sound to play when the enemy dies.
    public AudioClip deathClip;
    //The sound to play when the enemy is wounded.
    public AudioClip damagedClip;
    //Gets a reference to each of the three enemies
    public GameObject enemyType1;
    public GameObject enemyType2;
    public GameObject enemyType3;
    public GameObject enemyCharacter;
    //Range between player and enemy to see if player is close or not to attack
    public float range;
    //Player's location
    public Transform player;
    bool playerInRange;


    //These variables are hidden from the Inspector as they don't need to be edited
    //Locates the enemy's current chase target (the player)
    [HideInInspector] public Transform chaseTarget;
    //Variable for enemy's current state
    [HideInInspector] public IEnemyState currentState;
    //Variable for the chase state
    [HideInInspector] public ChaseState chaseState;
    //Variable for the alert state
    [HideInInspector] public AlertState alertState;
    //Variable for the patrol state
    [HideInInspector] public PatrolState patrolState;
    //Variable for the dead state
    [HideInInspector] public DeadState deadState;
    //Variable for the attack state
    [HideInInspector] public AttackState attackState;
    //Variable for enemy health
    [HideInInspector] public EnemyHealth enemyHealth;
    //Reference to NavMeshAgent component that allows AI to move around scene automatically
    [HideInInspector] public NavMeshAgent navMeshAgent;

    private void Awake()
    {
        chaseState = new ChaseState (this);
        alertState = new AlertState (this);
        patrolState = new PatrolState (this);
        deadState = new DeadState (this);
        attackState = new AttackState (this);
        //Gets the NavMeshAgent component before the game starts to run
        navMeshAgent = GetComponent<NavMeshAgent> ();
        enemyAudio = GetComponent<AudioSource> ();
        //Get Shoot and Lunge functions from AttackState class so the text can be changed depending on which of these are active
        //enemyCharacter.GetComponent<AttackState>().Lunge();
        //enemyCharacter.GetComponent<AttackState>().Shoot();
    }

    void Start()
    {
        //When the game starts to run, the default state for the enemy will be patrol
        currentState = patrolState;
        //Get message box text component when scene runs
        //messageBoxText = GetComponent<Text> ();
        //Get Animator component
        //anim = GetComponent<Animator> ();
    }

    public string GetCurrentState()
    {
        return currentState.GetType ().ToString ();
    }

    void Update()
    {
        //This is used to send the enemy into attack mode if the player is in range,
        //but currently it doesn't work efficiently.
        /*if (playerInRange) {
            currentState.ToAttackState ();
        } else {
            currentState.ToAlertState ();
        }*/
        Debug.DrawLine (enemyCharacter.transform.position, transform.position, Color.red);
        //Update the current state of the enemy
        currentState.UpdateState ();
        //Updates message box text depending on enemy state
        //UPDATE 31/03/17. This if statement is what I tried to do to get the text to update but it didn't work. I was trying to get the current state.
        if (currentState == patrolState)
            messageBoxText.text = "Enemy is patrolling";
         else if (currentState == chaseState)
            messageBoxText.text = "Enemy is chasing";
         else if (currentState == alertState)
            messageBoxText.text = "Enemy is searching";
         else if (currentState == attackState)
            messageBoxText.text = "Enemy is attacking";
         else if (currentState == deadState)
            messageBoxText.text = "Enemy is dead";
        /*else if (currentState == AttackState.Instance.Shoot())
            messageBoxText.text = "Enemy is shooting";
        else if (currentState == AttackState.Instance.Lunge())
            messageBoxText.text = "Enemy is attacking at close range";*/
         else
            messageBoxText.text = "Unknown state";

    }

    void OnCollisionEnter(Collision col)
    {
        //If a bullet collides with an enemy and their health is more than 0...
        if (col.gameObject.tag == "Bullet" && enemyHealth.currentHealth > 0)
        {
            //Play the audio clip of the enemy being hurt
            enemyAudio.clip = damagedClip;
            enemyAudio.Play ();
        }
    }

    private void OnTriggerEnter (Collider other)
    {
        currentState.OnTriggerEnter (other);
        if (other.tag == "Player") {
            playerInRange = true;
        }
    }

    private void OnTriggerExit (Collider other)
    {
        if (other.tag == "Player") {
            playerInRange = false;
        }
    }
}
using UnityEngine;
using UnityEngine.UI;
using System.Collections;

public class ChaseState : IEnemyState {

    //Gets a reference to the StatePatternEnemy script
    private readonly StatePatternEnemy enemy;

    //This checks whether or not the player is in range of the enemy and determines what action the enemy will take
    bool playerInRange;

    public ChaseState (StatePatternEnemy statePatternEnemy)
    {
        enemy = statePatternEnemy;
    }

    //Updates the Look and Chase functions during the chase state's operation
    public void UpdateState()
    {
        Look ();
        Chase ();
    }

    public void OnTriggerEnter (Collider other)
    {
        //If the entering collider is the player...
        if (other.CompareTag ("Player"))
        {
            //...the player is in range.
            playerInRange = true;
        }
       
    }
   
    public void OnTriggerExit (Collider other)
    {
        //If the exiting collider is the player...
        if (other.CompareTag ("Player"))
        {
            //...the player is no longer in range.
            playerInRange = false;
        }
    }

    //This is left empty because the enemy doesn't go from chase state to patrol state
    public void ToPatrolState()
    {

    }

    //Logic for alert state. Simply sets enemy into alert state.
    public void ToAlertState()
    {
        enemy.currentState = enemy.alertState;
    }

    //This state is also empty because it's already in chase state
    public void ToChaseState()
    {

    }

    public void ToDeadState()
    {
        //Sets enemy into dead state from alert state
        enemy.currentState = enemy.deadState;
    }

    public void ToAttackState()
    {
        enemy.currentState = enemy.attackState;
    }

    void Update()
    {
        //enemy.messageBoxText.text = "Enemy is chasing the player";
    }

    //This function sets up the logic so that if the player is detected by the enemy, the enemy will chase the player
    private void Look()
    {
        Debug.Log ("Enemy chasing");
        //The raycast is used to detect the player
        RaycastHit hit;
        //Enemy sets target on player
        Vector3 enemyToTarget = (enemy.chaseTarget.position + enemy.offset) - enemy.eyes.transform.position;
        //If the enemy GameObject detects the player when they are within range and can see the player...
        if (Physics.Raycast (enemy.eyes.transform.position, enemyToTarget, out hit, enemy.sightRange) && hit.collider.CompareTag ("Player")) {
            //...the enemy will start to chase the player depending on what the raycast hit
            enemy.chaseTarget = hit.transform;


        }
        //If the player is in range of the enemy...
        /*else if (Vector3.Distance (enemy.player.transform.position, enemy.transform.position) < enemy.range)
        {
            //Go into the attack state
            ToAttackState();
        }*/
        if (playerInRange) {
            ToAttackState ();
        }

        //If the scene doesn't meet the above requirements, instead the enemy will return to alert state
        else if (!playerInRange) {
            //...the enemy will return to alert state if the player is out of range/sight
            ToAlertState ();
           
        }


       
    }

    //Logic for the chase function
    private void Chase()
    {
        Debug.Log ("Enemy chasing");
        enemy.meshRendererFlag.material.color = Color.red;
        //Sets the enemy's destination target on the player
        enemy.navMeshAgent.destination = enemy.chaseTarget.position;
        //Resumes movement along current path after pause.
        enemy.navMeshAgent.Resume ();
        enemy.messageBoxText.text = "Enemy is chasing";
    }
}
using UnityEngine;
using UnityEngine.UI;
using System.Collections;

public class AttackState : IEnemyState {

    //Reference to StatePatternEnemy script
    private readonly StatePatternEnemy enemy;

    //Whether player is within the trigger collider
    bool playerInRange;

    public static AttackState Instance;
   
    public AttackState(StatePatternEnemy statePatternEnemy)
    {
        enemy = statePatternEnemy;
    }

    void Awake()
    {
        Instance = this;
    }
   
    //Regularly updates Look and Search functions
    public void UpdateState()
    {
        Shoot ();
        //Lunge ();
    }
   
    public void OnTriggerEnter (Collider other)
    {
        //If the entering collider is the player...
        if (other.CompareTag ("Player"))
        {
            //...the player is in range.
            playerInRange = true;
            //Lunge();
            //enemy.navMeshAgent.Stop();
        }
       
    }

    public void OnTriggerExit (Collider other)
    {
        //If the exiting collider is the player...
        if (other.CompareTag ("Player"))
        {
            //...the player is no longer in range.
            playerInRange = false;
        }
    }
   
    //Logic for patrol state when it transitions from alert state
    public void ToPatrolState()
    {
        Debug.Log ("Cannot transition from attack state to patrol state");
    }
   
    //Logic for alert state. This just generates a warning if it attempts to go into alert state when it's already in this state
    public void ToAttackState()
    {
        Debug.Log ("Can't transition to same state");
    }
   
    //Logic for chase state
    public void ToChaseState()
    {
        //Sets enemy into chase state from alert state
        enemy.currentState = enemy.chaseState;
        //Sets search timer to 0 as this isn't needed
        //searchTimer = 0f;
    }
   
    public void ToDeadState()
    {
        //Sets enemy into dead state from alert state
        enemy.currentState = enemy.deadState;
    }

    public void ToAlertState()
    {
        //Sets enemy into alert state when triggered
        enemy.currentState = enemy.alertState;
    }

    void Update()
    {
        //enemy.messageBoxText.text = "Enemy is attacking the player";
    }

    //This function sets up the logic so that if the player is detected by the enemy, the enemy will chase the player
    public void Shoot()
    {
        Debug.Log ("Enemy attacking at long distance range");
        //Set enemy state alerter to blue to show the enemy is attacking
        enemy.meshRendererFlag.material.color = Color.blue;
        //The raycast is used to detect the player
        RaycastHit hit;
        //If the enemy GameObject detects the player when they are within range and can see the player...
        if (Physics.Raycast (enemy.eyes.transform.position, enemy.eyes.transform.forward,
                             out hit, enemy.sightRange) && playerInRange) {
            //...the enemy will start to shoot at the player depending on what the raycast hit
            //enemy.chaseTarget = hit.transform;
            /*if (bullet != null)*/
            //{
            //Create bullet from bullet prefab
            var bullet = GameObject.Instantiate (enemy.bulletPrefab, enemy.bulletSpawn.position, enemy.bulletSpawn.rotation) as GameObject;
            //Add velocity to the bullet
            bullet.GetComponent<Rigidbody> ().velocity = bullet.transform.forward * 6;
            //Destroy the bullet after 2 seconds
            //enemy.Destroy (bullet, 2.0f);
            //}
            //enemy.navMeshAgent.Stop ();
            //Debug.Log ("Attack");

        } else if (playerInRange == false) {
            ToAlertState();
        }

       
    }
   
    //This function is triggered when the enemy is in the chase state and is very close to the player.
    /*public void Lunge()
    {
        Debug.Log ("Enemy attacking at close range");
        //Set enemy state alerter to blue to show the enemy is attacking
        enemy.meshRendererFlag.material.color = Color.blue;
        //Stops enemy's movement
        //enemy.navMeshAgent.Stop ();
        if (playerInRange == true) {
            Debug.Log ("Lunge");
        } else if (playerInRange == false) {
            ToAlertState ();
        }
        //Debug.Log ("Lunge");
        //If the player is no longer in range, the enemy will switch to the alert state
        if (playerInRange == false)
        {
            ToAlertState();
        }
   


    }*/
   
}

For the Lunge() method I commented it out partially because I decided not to use it anymore and I thought it would fix the problem, but it didn’t.

using UnityEngine;
using UnityEngine.UI;
using System.Collections;

public class AlertState : IEnemyState {

    //Reference to StatePatternEnemy script
    private readonly StatePatternEnemy enemy;
    //A timer that runs while the enemy is in alert state
    private float searchTimer;

    public AlertState(StatePatternEnemy statePatternEnemy)
    {
        enemy = statePatternEnemy;
    }

    //Regularly updates Look and Search functions
    public void UpdateState()
    {
        Look ();
        Search ();
    }

    public void OnTriggerEnter (Collider other)
    {

    }

    //Logic for patrol state when it transitions from alert state
    public void ToPatrolState()
    {
        //Set enemy into patrol state
        enemy.currentState = enemy.patrolState;
        //Sets the search timer to 0 as the enemy is no longer searching for the player
        searchTimer = 0f;
    }

    //Logic for alert state. This just generates a warning if it attempts to go into alert state when it's already in this state
    public void ToAlertState()
    {
        Debug.Log ("Can't transition to same state");
    }

    //Logic for chase state
    public void ToChaseState()
    {
        //Sets enemy into chase state from alert state
        enemy.currentState = enemy.chaseState;
        //Sets search timer to 0 as this isn't needed
        searchTimer = 0f;
    }

    public void ToDeadState()
    {
        //Sets enemy into dead state from alert state
        enemy.currentState = enemy.deadState;
    }

    public void ToAttackState()
    {
        Debug.Log ("Must be in chase state first");
    }

    void Update()
    {
        //enemy.messageBoxText.text = "Enemy is searching";
    }

    //This function sets up the logic so that if the player is detected by the enemy, the enemy will chase the player
    private void Look()
    {
        Debug.Log ("Enemy searching");
        //The raycast is used to detect the player
        RaycastHit hit;
        //If the enemy GameObject detects the player when they are within range and can see the player...
        if (Physics.Raycast (enemy.eyes.transform.position, enemy.eyes.transform.forward,
                             out hit, enemy.sightRange) && hit.collider.CompareTag ("Player"))
        {
            //...the enemy will start to chase the player depending on what the raycast hit
            enemy.chaseTarget = hit.transform;
            //The enemy will enter the chase state
            ToChaseState();
        }
       
    }

    //This function operates while the enemy is in alert mode. They are searching for the player.
    private void Search()
    {
        Debug.Log ("Enemy searching");
        enemy.meshRendererFlag.material.color = Color.yellow;
        //Stops enemy's movement
        enemy.navMeshAgent.Stop ();
        //Sets speed of enemy searching
        enemy.transform.Rotate (0, enemy.searchingTurnSpeed * Time.deltaTime, 0);
        //The search timer will run until time has run out or state changes
        searchTimer += Time.deltaTime;
        //If the search timer runs out, return the enemy to it's default patrol state
        if (searchTimer >= enemy.searchingDuration)
        {
            ToPatrolState ();
        }
    }

}
using UnityEngine;
using System.Collections;

public interface IEnemyState
{

    void UpdateState();

    void OnTriggerEnter (Collider other);

    void ToPatrolState();

    void ToAlertState();

    void ToChaseState();

    void ToDeadState();

    void ToAttackState();
}

Again, apologies for returning here. I’m just panicking a bit because my deadline is only days away and I don’t want to hand in unfinished or broken work. Thanks for any suggestions.

Trigger exit is never called in your attack script?

I’m not 100% sure because I just followed a video tutorial from the Unity site to do the main structure of this state machine, but if you mean the “out hit” from the if statement I think it means the raycast “hitting” the player to locate them.
When I try to get away from the enemy when it’s stuck in attack state, I try getting out of its line of sight and getting as far away as possible, but nothing works. Sometimes it does work but it depends what mood its in (joke). It must be such a simple fix I’ll be kicking myself but then again I’m not completely thinking straight with this looming deadline so there you go.

I was just wondering if you are far away, if you added a debug print statement there, does it say you actually are playerInRange = false ?. because I read over your code and it looks pretty good. So I’m just taking a stab at walking through the problem :slight_smile:
The reason I asked about the hit was my bad when I first wrote that, I edited it out. I had forgotten about the else if statement that follow. Physics.Raycast can return true if it hits anything; so it might have hit itself , I was thinking. However, your also had “&& playerInRange” so that negated my point there, somewhat.

have you verified you’r OnTriggerExit is being called?.. If so, maybe invert the If-Else and ask for playerinrange==flase before raycasting?? If(!playerInRange){//changeState}else… //theraycasting part … I’ve been exploring your code back and forth and can’t find anything strange…

like everyone else has been stating I don’t see OnTriggerExit being called, I would call it if it collides with the players collision box. Keep it simply my friend :slight_smile:

I’ll have a look through my code again, but I think keeping it simple is important because when it gets complicated it’s hard to fix any problems like this. I’ll go through my code again. I’ll add a Debug.Log statement to my OnTriggerEnter and Exit functions to see if they’re actually working. Hopefully it should be a simple fix. :slight_smile:

Yep, write back with your results because your code looks otherwise good :slight_smile:

Might it have something to do with either my enemies or player rigidbody or colliders. I’m sure they have both of these. The thing is that they have both a sphere collider for range detection and also the enemies have two capsule colliders for detecting bullets, although this may be unnecessary. Do you think having too many colliders might be messing up my enemy’s behaviour? The reason I said this is because you’ve said my code looks fine so I’m wondering if it’s a component issue causing all this.
I found this online which is similar to mine so I’ll look through that as well. c# - Fixing enemy behavior state machine in Unity - Game Development Stack Exchange

Well, you didn’t say whether your OnTriggerExit was being called? :slight_smile:

Which script are you referring to?

Okay, I’ve done some Debug.Log tests on my states to see what’s been happening. I found that when it when into AttackState and I walked away, the Debug.Log would always say Player in range, which is wrong. In the ChaseState, it would detect the player being out of range, but not the AttackState.
I’ve also noticed that none of my enemy character’s have a rigidbody. They have a sphere collider which is set to IsTrigger. They have two capsule colliders. One is currently turned off without IsTrigger checked, and the other is turned on and has IsTrigger checked.
Update #1: Now the enemy will go into a weird limbo if approached by the player. It will search and when in line of sight of the player, will spasm between alert and attack, but I think I know why. In my ChaseState, I put in the Raycast, but I also added the if player in range statement below that, which I think is conflicting, so I’m going to try and put that inside the first if statement with the Raycast.
Update #2: That’s interesting. In the ChaseState in the Look() function, I took out the whole if player in range section and it transitioned into the ChaseState no problem. No stuttering or anything. I’m not saying I’m going to remove the AttackState because I need that, but why would that happen? Also, OnTriggerEnter and Exit were working fine, in other words working as they were supposed to work. Hmm.
Update #3: I’m going to work on getting the enemy into AttackState using the link I mentioned in an earlier post.
Update #4: So I used a different method to get from Chase to Attack. The good news is that it goes from Chase to Attack when the player is in range, which is what I want, the bad news is, once the enemy is in Attack, they still don’t leave, and the player is still in range according to the AttackState script, so my next step is to look into that.
Update #5: I feel like I’m getting there now, which is good. The enemy is still stuck in AttackState though but once I get this fixed, I should be good, for now.
Update #6: YES! I got it to work! Now when I get away from the enemy when in either Attack or Chase State, they go back to Alert. This is very good news. I did this by going into the Shoot() method of the AttackState. I had the normal Physics.Raycast check minus the PlayerInRange Boolean. Here the enemy shoots projectiles at the player while standing still (but turning to face the player). Then in my else statement below, I also removed the PlayerInRange Boolean so it just said to go back to alert state. I’m very pleased at how this has turned out with persistence and patience, and help from the forums of course. I still need to test it to make sure all the enemies in my scene respond the same, but other than that for now it’s working. I’ll probably keep posting here if anything else comes up or I’ve added something, etc. Thanks for your help!

Hey, sounds like you’re narrowing down the problem. That’s good :slight_smile:
It seems sensible to put those checks together, maybe if the raycast is succesful , put it in attack if in range, otherwise chase.
I’ll wait on attempting to offer more feedback until I hear your next update to see where you’re at.

I’m going to keep working on my demo, but I won’t change the state code because I don’t want to mess it up. I’ve already changed the UI text code so that it changes colour to reflect which state the enemy is in, which is working no problem. I’ll also be improving my enemies sight range because at the moment it’s not very realistic. Another thing I want to do is add in the dead state but that’s not too important. Another thing I want to implement that may be complicated is when one enemy goes into attack or chase state, I want a nearby enemy to go into alert state, so this might take some figuring out but shouldn’t be difficult. Like I said, I don’t want to mess up my already working state system, but I want to try and make my enemies seem more intelligent.

I didn’t notice the edits in this thread as updates, so I just saw them now. I’m glad that you got it working pretty well :slight_smile:
Congrats on that.