I’ve been searching for a solution to my problem for a few days now and I don’t really feel like I’m getting anywhere.
Basically, I’m making a scene which has enemy AI that reacts to the player character and changes states depending on certain conditions (like a state machine). It’s going okay (for example, the enemy searches is the player is close then chases when they see them, etc.) but one thing I’d like to do is display in a message box using UI text what state the enemy is in (and which enemy it is as well; there’s three in my scene). I’m not sure how to do this. It should be easy and I know I need the Update function but there’s a problem. Each enemy state (Patrol, Chase, etc.) has it’s own script where the appropriate action takes place depending on the state. There’s a central class the different states inherit from. Without inheriting Monobehaviour, I can’t use the Update() function to update my text to change depending on the current state.
I’ve been searching for a solution for days and I’m at my wit’s end. I don’t normally like to come here to ask for help; not that I don’t appreciate it but I don’t like people thinking I rush here for every little problem because honestly I don’t. Usually I find a solution online, but I feel like I’m going round in circles with this and not getting closer to the solution, and no-one else has this problem. My state machine doesn’t use enums, which now I realise is a bit silly, but I got the state script from a tutorial on the Unity tutorials (found here: https://unity3d.com/learn/tutorials/topics/scripting/using-interfaces-make-state-machine-ai).
I have to finish my project by this coming Friday because of a deadline and it’s actually the key thing I need to show users how my enemy state system works. I’ll post the main enemy scripts and one of the state scripts (I have about five of those in total). I’m not a programming pro but I’m better than I was about a year ago before I started learning C# properly. Thanks for any help or suggestions.
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;
//UPDATE: 31/03/17: Enemy model cannot currently be animated due to errors from imported model
//Get reference for Animator to control animations
//Animator anim;
//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;
//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> ();
}
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()
{
//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";
}
messageBoxText.text = "Patrolling";
}
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);
}
}
using UnityEngine;
using System.Collections;
public interface IEnemyState
{
void UpdateState();
void OnTriggerEnter (Collider other);
void ToPatrolState();
void ToAlertState();
void ToChaseState();
void ToDeadState();
void ToAttackState();
}
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 (playerInRange)
{
//Go into the attack state
ToAttackState();
}
//If the scene doesn't meet the above requirements, instead the enemy will return to alert state
else
{
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";
}
}