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.