0
\$\begingroup\$

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.

This script is attached to the NPC:

using UnityEngine;
using System.Collections;
/// <summary>
/// State pattern enemy.
/// Handles animation sequences & states
/// </summary>
[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 script:

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();
    }
}

Hit script:

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 figuring this out.... Thank you

\$\endgroup\$
3
  • 2
    \$\begingroup\$ That's a lot of code you're asking a group of strangers to do your work of debugging for free. To be considerate of other users' time, it's a good idea to try to make a minimal repro case that has the bug you want to fix, rather than pasting your whole system. Often, going through this exercise will even help you isolate the problem without outside help. \$\endgroup\$
    – DMGregory
    Sep 25 '15 at 16:37
  • \$\begingroup\$ I apologize for copying the entirety of the code(s)... \$\endgroup\$ Sep 26 '15 at 2:55
  • \$\begingroup\$ I know where the problem is but haven't been able to find a proper fix...in my previous implementation, I called the EnemyGotHit method in the HitState. EnemyGotHit would play the hit animation, stop the NavmeshAgent, wait 'x' seconds, & return to AttackState / attack sequence. The _hitState is called in the collider- but I just can't seem to get this working properly. \$\endgroup\$ Sep 26 '15 at 3:16
0
\$\begingroup\$

It seems to keep looping thru because it is heading back to Look then it finds you again and then goes right into the first attack over and over.

so in the chase state at the top of private void Look() add something like

float distance = Vector3.Distance(enemy.transform.position, enemy.chaseTarget.transform.position);
    if (distance < 5f)
        return;

you may also have to put that in the attack state also or setup the state update checks differently so you don't have to clog up the rest of the state scripts.

\$\endgroup\$
1
  • \$\begingroup\$ thanks Justin, I got it sorted...I ended up adding a boolean within the attackState to change to hitState if the enemy got hit \$\endgroup\$ Sep 30 '15 at 17:53
0
\$\begingroup\$

Added a boolean check to switch to the hit state & back:

private void AttackSequence()
{
    ///if the enemy got hit play the hit sequence otherwise keep attacking
    if(enemy.gotHit)
    {
        enemy.currentState = enemy._hitState;
        searchTimer = 0f;
    }
    else
    {
        enemy._navMeshAgent.transform.LookAt(enemy.chaseTarget.position);//always look at player
        enemy.AnimPunch();
    }
}
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.