Enum state function fires in another state

Hi! I’m trying to implement a “lazy state machine” using enums for simple enemy AI but for some reason the logic doesn’t work as intended. I need my enemy to stop from time to time to be in Idle state. But when Idle switch fires, my enemy continues to move despite movement requiring another state to fire.

What am I doing wrong?

 private void Update()
    {
        Debug.Log(state);
        onGround = Physics2D.Raycast(gameObject.transform.position, Vector2.down, groundLength, groundLayer);
        Debug.DrawRay(gameObject.transform.position, Vector2.down * 1f, Color.red);

        isAnythingThere = Physics2D.Raycast(wallDetection.position, Vector2.right * speed, 0.3f, sideInfo);
        RaycastHit2D groundInfo = Physics2D.Raycast(groundDetection.position, Vector2.down, distance);
        RaycastHit2D isPlayerInSight = Physics2D.Raycast(wallDetection.position, Vector2.right * speed, 3f, playerLayer);

        switch (state)
        {
            default:
            case State.patrol:

                if ((groundInfo.collider == false || isAnythingThere) && !isPlayerInSight)
                {
                    if (movingRight == true)
                    {
                        Debug.Log("Turn");
                        transform.eulerAngles = new Vector3(0, -180, 0);
                        movingRight = false;
                        speed = -speed;
                    }
                    else
                    {
                        transform.eulerAngles = new Vector3(0, 0, 0);
                        movingRight = true;
                        speed = -speed;
                    }
                }
                if (isPlayerInSight)
                {
                    state = State.attack;
                }
                if (enemyIdleCheck == false)
                {
                    StartCoroutine("IfEnemyWantsToIdle");
                }
                break;

            case State.attack:

                RaycastHit2D isPlayerThere = Physics2D.Raycast(wallDetection.position, Vector2.right * speed, 3f, playerLayer);
                Debug.DrawRay(wallDetection.transform.position, Vector2.right * speed * 3f, Color.red);
                if (isPlayerThere)
                {
                    attackDirection = (playerPosition.position - transform.position).normalized;
                }
                else
                {
                    if (!jumpCooldown)
                    {
                        state = State.patrol;
                    }
                }
                if (jumpCooldown == false)
                {
                    jumpTimer = Time.time + jumpCooldownInSeconds;
                }
                break;

            case State.idle:

                if (isPlayerInSight)
                {
                    state = State.attack;
                }
                if (!isInIdleState)
                {
                    StartCoroutine("IdlingTime");
                }
                break;
        }
    }

    private IEnumerator IfEnemyWantsToIdle()
    {
        enemyIdleCheck = true;
        Debug.Log("Checking for idle possibility");
        yield return new WaitForSeconds(2);
        if (Random.Range(0, 20) > 10)
        {
            state = State.idle;
        }
        enemyIdleCheck = false;

        if (isPlayerInSight)
        {
            state = State.attack;
            enemyIdleCheck = false;
            yield break;
        }
    }

    private IEnumerator IdlingTime()
    {
        isInIdleState = true;
        while (state == State.idle)
        {
            yield return new WaitForSeconds(Random.Range(2, 4));
            isInIdleState = false;
            state = State.patrol;
        }
        if (isPlayerInSight)
        {
            isInIdleState = false;
            yield break;
        }
    }

    private void FixedUpdate()
    {
        if (state == State.patrol)
        {
            if (!enemy.isTakingDamage)
            {
                rb.velocity = new Vector2(movingSpeed * speed, rb.velocity.y);
            }
        }

        if (state == State.attack)
        {
            if (!enemy.isTakingDamage)
            {
                rb.velocity = new Vector2(attackDirection.x * attackSpeed, rb.velocity.y);
            }

            if ((Random.Range(0, 100) > 90) && !jumpCooldown && onGround)
            {
                Debug.Log("Jumped!");
                //rb.velocity = new Vector2(rb.velocity.x, rb.velocity.y * jumpForce);
                rb.AddForce(Vector2.up * jumpForce, ForceMode2D.Impulse);
                jumpCooldown = true;
                Invoke("JumpCooldownTime", 1f);
            }
        }
    }

So you have your switch statement in Update and then you also have state switching in FixedUpdate. Update is happening every frame and Fixed is happening almost every frame so i am guessing you have some conflicting code but its hard to tell at first glance.

I suggest changing up your FSM a bit, here is what works well for me:

    public enum EnemyActionType { Idle = 0, Patrol = 1, Attack = 2};
    public EnemyActionType CurrentState = EnemyActionType.Idle;
   
    private void Start()
    {
        ChangeState(CurrentState);
    }
   
    public void ChangeState(EnemyActionType NewState)
    {
        StopAllCoroutines();
        CurrentState = NewState;

        switch (NewState)
        {
            case EnemyActionType.Idle:
                StartCoroutine(Idle());
                break;

            case EnemyActionType.Patrol:
                StartCoroutine(Patrol());
                break;

            case EnemyActionType.Attack:
                StartCoroutine(Attack());
                break;
        }
    }
   
    public IEnumerator Patrol()
    {
        while(CurrentState == EnemyActionType.Patrol)
        {
            //Do my raycast here
            if(some condition)
            {
                ChangeState(EnemyActionType.Attack);
            }
            if(some other condition)
            {
                ChangeState(EnemyActionType.Idle);
            }
            yield return null;
        }
    }
   
    public IEnumerator Idle()
    {
        while(CurrentState == EnemyActionType.Idle)
        {
            //Do my raycast here
            if(some condition)
            {
                ChangeState(EnemyActionType.Attack);
            }
            if(some other condition)
            {
                ChangeState(EnemyActionType.Patrol);
            }
            yield return null;
        }
    }
   
    public IEnumerator Attack()
    {
        while(CurrentState == EnemyActionType.Attack)
        {
            //Do my raycast here
            if(some condition)
            {
                ChangeState(EnemyActionType.Patrol);
            }
            if(some other condition)
            {
                ChangeState(EnemyActionType.Idle);
            }
            yield return null;
        }
    }
       
   
    private void FixedUpdate()
    {
        if (turning == true && !facingRight)
            Flip();
        else if (turning == false && facingRight)
            Flip();
    }

    void Flip()
    {
        facingRight = !facingRight;
        Vector3 theScale = transform.localScale;
        theScale.x *= -1;
        transform.localScale = theScale;
    }

You will notice that I have to redo my Raycast every state. There is probably a way around this but I do it so I dont have 3 hit variables in FixedUpdate or Update and then have to use 3 if statements in each State action.

But, essentially you are removing all Update and FixedUpdate state switches and making them dependent on each other. That is, the actual switching of states is handled by the function ChangeState. You then call that ChangeState whenever a condition is met within your states.

For my idle, I set up a basic timer. If the raycast is null and the timer has expired, go to patrol state. If the patrol state is active, it does the same thing: Go to 2 or 3 patrol points if raycast is null, then wait some seconds before going back to idle. If the raycast hits the player, switch to attack state, etc. etc. etc.

1 Like

@Cornysam thanks, interesting solution. I was thinking about making my FSM based on Coroutines before though I’m not sure if it would be ideal to put physics and state logic in a coroutine together. Looks like I’ll have to implement a full scale abstract state machine eventually)
What was the solution for my problem - the velocity in my script was constant so I had to null it in another FixedUpdate state:

case State.idle:
                rb.velocity = Vector2.zero;
                break;

It’s working fine now.

Glad you got it working. I made that script like 2 and a half years ago when i was first learning about basic AI enemies and got it working so i havent really changed it since. So far, no issues with physics and state logic together but maybe ill find out some day haha