Fixing enemy behavior state machine in Unity
I'm working on a state machine for my game's NPC.
The only thing I can't to get work properly is the hit sequence. The hits register. What I would like to happen is for the attack sequence (animation) to stop, play the hit animation sequence, & then go back to the chase/attack state using a coroutine.
Currently, the attack sequence seems to just loop through, ignoring the coroutine.
Here's the code attached to the NPC: using UnityEngine; using System.Collections; ///
/// State pattern enemy. /// Handles animation sequences & states /// [RequireComponent(typeof(AudioSource))] public class StatePatternEnemy : MonoBehaviour { public Transform[] wayPoints; public float searchTurnSpeed = 120f; public float searchDuration = 5f; public float sightRange = 15f; public float alertZone = 20f; public float stopAtDistance = 0f; //used to stop chase public float hitSequenceTimer = 1.25f; //use to return to attackState public float animWalkSpeed = 1f; public float animRunSpeed = 1.75f; public float animPunchSpeed = 1f; public float animGotHitSpeed = 1f; public GameObject nmeFistL;
public GameObject nmeFistR;
public Vector3 offset = new Vector3(0,0.5f,0);//offsets raycast so it won't aim for foot
public Transform eyes;
public bool onAlert = false;
public Transform chaseTarget;
[HideInInspector]public float playerDistance; //player's transform info
[HideInInspector]public float alertSearchTimer;//increment timer search value
[HideInInspector]public Vector3 enemyToTarget; //player's Vector info
[HideInInspector]public IEnemyState currentState;
[HideInInspector]public AlertState _alertState;
[HideInInspector]public AttackState _attackState;
[HideInInspector]public ChaseState _chaseState;
[HideInInspector]public HitState _hitState;
[HideInInspector]public PatrolState _patrolState;
[HideInInspector]public NavMeshAgent _navMeshAgent;
public AudioClip sfxGrunterThreat;
public float sfxVolume = 0.125f;
public AnimationClip nmeGotHit;
public AnimationClip nmeIdle;
public AnimationClip nmeJump;
public AnimationClip nmePunch;
public AnimationClip nmeRoar;
public AnimationClip nmeRun;
public AnimationClip nmeTaunt;
public AnimationClip nmeWalk;
AudioSource _sfx;
RaycastHit _hit;
private void Awake()
{
_alertState = new AlertState(this);
_attackState = new AttackState(this);
_chaseState = new ChaseState(this);
_hitState = new HitState(this);
_patrolState = new PatrolState(this);
_navMeshAgent = GetComponent<NavMeshAgent>();
_sfx = GetComponent<AudioSource>();
}
// Use this for initialization
void Start ()
{
currentState = _patrolState;
}
// Update is called once per frame
void Update ()
{
currentState.UpdateState();
Rayline();//remove later
BeAlert();
print(currentState);
}
private void OnTriggerEnter(Collider other)
{
if (other.gameObject.CompareTag ("fireBall"))
{
///create a hit state to move player
///back & play the hit animation after coroutine
currentState = _hitState;
StopAnim();
Debug.Log("fire");
StartCoroutine(EnemyHitSequence());
}
}
public void BeAlert()
{
///ensure the player is within alert zone
float playerDistance = Vector3.Distance(chaseTarget.position, transform.position);
if(playerDistance > alertZone)
{
nmeFistL.SetActive(false);
nmeFistR.SetActive(false);
currentState = _patrolState;
//print("Outside alertzone: " + playerDistance);
}
else
{
//print("Alertzone: " + playerDistance);
//print("AlertZone Timer" + alertZoneTimer);
onAlert = true;
enemyToTarget = ((chaseTarget.position + offset) - eyes.transform.position);
//check to see if player is in visual range & is hit with the rayCast
if(Physics.Raycast(eyes.position, enemyToTarget, out _hit, sightRange) && _hit.collider.CompareTag("Player") && (playerDistance <= sightRange))
{
currentState = _chaseState; //player is found & being chased
StopChase(); //player is being attacked
// _attackState setUp is in stopChase method
}
else// <---player not found but is close, be on alert!
{
//currentState = _patrolState;
if(onAlert == true)
{
currentState = _alertState; //look around for a bit
alertSearchTimer += Time.deltaTime;
///if player isn't found, go patrol
if(alertSearchTimer >= searchDuration)
{
onAlert = false;
currentState = _patrolState;//nothing is found go back & patrol
}
}
}
}
}
public void StopChase()
{
_navMeshAgent.speed = 3.5f;//slow down the navAgent
float playerDistance = Vector3.Distance(chaseTarget.position, transform.position);
//stop navMeshAgent to begin attack sequence
if (playerDistance <= stopAtDistance)
{
///set this up to go on attack
/// ensure enemy is looking at player
enemyToTarget = ((chaseTarget.position + offset) - eyes.transform.position);
if(Physics.Raycast(eyes.position, enemyToTarget, out _hit, sightRange) && _hit.collider.CompareTag("Player"))
{
nmeFistL.SetActive(true);
_navMeshAgent.Stop();
currentState = _attackState;
}
///ensure the enemy isn't hitting air
/// & will always look for the player
else
{
transform.Rotate(chaseTarget.position, searchTurnSpeed * Time.deltaTime, 0);
}
}
print("in stop chase");
}
public void EnemyGotHit()
{
//print("CurrentState in enemyGotHit: " + currentState);
//transform.Translate(0f,0.25f,-0.125f);
AnimGotHit();
//_navMeshAgent.Stop();
//StartCoroutine(EnemyHitSequence());
}
IEnumerator EnemyHitSequence()
{
EnemyGotHit();
print("CurrentState in hit sqnz: " + currentState);
yield return new WaitForSeconds(hitSequenceTimer);
//_navMeshAgent.Resume();
currentState = _attackState;
StopChase();
print("CurrentState after switch in coroutine: " + currentState);
}
public void Rayline()
{
Vector3 forward = transform.TransformDirection(Vector3.forward) * 10;
Debug.DrawRay(eyes.transform.position, forward, Color.green);
}
public void StopAnim()
{
GetComponent<Animation>().Stop("nmeWalk");
GetComponent<Animation>().Stop("nmePunch");
}
public void AnimWalk()
{
GetComponent<Animation>().Play("nmeWalk", PlayMode.StopAll);
GetComponent<Animation>().wrapMode = WrapMode.Once;
GetComponent<Animation>()["nmeWalk"].speed = animWalkSpeed;
}
public void AnimRun()
{
GetComponent<Animation>().Play("nmeRun", PlayMode.StopAll);
GetComponent<Animation>().wrapMode = WrapMode.Once;
GetComponent<Animation>()["nmeWalk"].speed = animRunSpeed;
}
public void AnimPunch()
{
GetComponent<Animation>().Play("nmePunch", PlayMode.StopAll);
GetComponent<Animation>().wrapMode = WrapMode.Once;
GetComponent<Animation>()["nmePunch"].speed = animPunchSpeed;
}
public void AnimRoar()
{
GetComponent<Animation>().Play("nmeRoar", PlayMode.StopAll);
GetComponent<Animation>().wrapMode = WrapMode.Once;
//GetComponent<Animation>()["nmeRoar"].speed = animRunSpeed;
}
public void AnimTaunt()
{
GetComponent<Animation>().Play("nmeTaunt", PlayMode.StopAll);
GetComponent<Animation>().wrapMode = WrapMode.Once;
GetComponent<Animation>()["nmeTaunt"].speed = animRunSpeed;
}
public void AnimGotHit()
{
GetComponent<Animation>().Play("nmeGotHit", PlayMode.StopAll);
GetComponent<Animation>().wrapMode = WrapMode.Once;
GetComponent<Animation>()["nmeGotHit"].speed = animGotHitSpeed;
}
public void SfxNmeThreat()
{
_sfx.PlayOneShot(sfxGrunterThreat, sfxVolume);
}
}
Attack state:
using UnityEngine;
using System.Collections;
/// <summary>
/// Attack state stays within attack.
/// Exiting out of attack sequence is handled within
/// StatePatternEnemy
/// </summary>
public class AttackState : IEnemyState
{
private float searchTimer;
private readonly StatePatternEnemy enemy;
public AttackState (StatePatternEnemy _statePatternEnemy)
{
enemy = _statePatternEnemy;
}
public void UpdateState()
{
Look();
AttackSequence();
}
public void OnTriggerEnter(Collider other)
{
}
public void ToPatrolState()
{
enemy.currentState = enemy._attackState;
searchTimer = 0f;
}
public void ToAlertState()
{
enemy.currentState = enemy._alertState;
searchTimer = 0f;
}
public void ToChaseState()
{
enemy.currentState = enemy._attackState;
searchTimer = 0f;
}
public void ToAttackState()
{
/// create a mono behavior for attack & other animation sequences
/// check for distance
/// if player is in range
/// attack
/// else
/// ToChaseState();
Debug.Log("Attacking");
}
public void ToHitState()
{
enemy.currentState = enemy._hitState;
searchTimer = 0;
}
private void Look()
{
RaycastHit hit;
Vector3 enemyToTarget = ((enemy.chaseTarget.position + enemy.offset) - enemy.eyes.transform.position);
if(Physics.Raycast(enemy.eyes.position, enemyToTarget, out hit, enemy.sightRange) && hit.collider.CompareTag("Player"))
{
//Debug.Log("player found in: " + enemy.currentState);
enemy.chaseTarget = hit.transform;
}
else//if player is hidden then
{
//Debug.Log("player not found in: " + enemy.currentState);
ToAlertState();
}
}
private void AttackSequence()
{
//RaycastHit hit;
//Vector3 enemyToTarget = ((enemy.chaseTarget.position + enemy.offset) - enemy.eyes.transform.position);
//if(Physics.Raycast(enemy.eyes.position, enemyToTarget, out hit, enemy.sightRange) && hit.collider.CompareTag("Player"))
//{
// Debug.Log("start attack");
//}
//face the player
//enemy.transform.Rotate(enemy.chaseTarget.position,enemy.searchTurnSpeed * Time.deltaTime, 0);//original code
enemy._navMeshAgent.transform.LookAt(enemy.chaseTarget.position);
enemy.AnimPunch();
}
}
Finally, the hit state:
using UnityEngine;
using System.Collections;
/// <summary>
/// Attack state stays within attack.
/// Exiting out of attack sequence is handled within
/// StatePatternEnemy
/// </summary>
public class HitState : IEnemyState
{
private float searchTimer;
private readonly StatePatternEnemy enemy;
public HitState (StatePatternEnemy _statePatternEnemy)
{
enemy = _statePatternEnemy;
}
public void UpdateState()
{
Look();
EnemyIsHit();
}
public void OnTriggerEnter(Collider other)
{
if (other.gameObject.CompareTag ("fireBall"))
{
Debug.Log("!");
enemy.currentState = enemy._hitState;
}
}
public void ToPatrolState()
{
enemy.currentState = enemy._patrolState;
searchTimer = 0f;
}
public void ToAlertState()
{
enemy.currentState = enemy._alertState;
searchTimer = 0f;
}
public void ToChaseState()
{
enemy.currentState = enemy._chaseState;
searchTimer = 0f;
}
public void ToAttackState()
{
enemy.currentState = enemy._attackState;
searchTimer = 0f;
}
public void ToHitState()
{
}
private void Look()
{
RaycastHit hit;
Vector3 enemyToTarget = ((enemy.chaseTarget.position + enemy.offset) - enemy.eyes.transform.position);
if(Physics.Raycast(enemy.eyes.position, enemyToTarget, out hit, enemy.sightRange) && hit.collider.CompareTag("Player"))
{
enemy.chaseTarget = hit.transform;
}
else//if player is hidden then
{
ToAlertState();
}
}
private void EnemyIsHit()
{
//Debug.Log("HitState.cs");
//Debug.Log("EnemyIsHit() state: " + enemy.currentState);
//enemy.currentState = enemy._attackState;
//Debug.Log("EnemyIsHit() After state switch: " + enemy.currentState);
enemy._navMeshAgent.transform.LookAt(enemy.chaseTarget.position);
enemy.StopChase(); // calling StopChase method goes back to attackState
}
}
I could really use some help fixing this, thank you
Your answer
Follow this Question
Related Questions
Enemy AI script for 2D platformer 1 Answer
How to make an npc/ai walk next to the player? (C#) 1 Answer
Several NPC with waypoints 1 Answer
how to smoothly transition between animations in the "Any State" state machine. 1 Answer
Enemy watches player and shoots but bullets will not project.. 0 Answers