Code Roman Revenant

                                
public enum States
{
    chasePlayer,
    chaseNewPos,
    attack,
    abilityUse
}
                                
                            
This enum has all the possible states the enemy can be in. They are in order from top to bottom, with the top having the least priority and the bottom having the most priority.
                                
//Making an reference to the enum
[SerializeField] States currentState;
private States defaultState;


private void Start()
{
    defaultState = States.chasePlayer;
    currentState = defaultState;
}
                                
                            
The default state of the enemies. Chasing the player! If there is no other state that wants to be activated or if it is done with the other state, it goes to this state.
                                
public void TriggerChaseNewPos()
{
    newPosTimer += Time.deltaTime;
    distanceToNewPos = Vector3.Distance
    (newPos, enemyNewPosGen.transform.position);

    //If the "ping" goes off
    if (newPosTimer >= randomTime)
    {
        WantsToBeNextState(States.chaseNewPos);
    }

    //If the new target has been reached, reset timers and state
    if (currentState == States.chaseNewPos && distanceToNewPos < 1)
    {
        ResetTimers();
        GoToDefaultState();
    }
}

//Priority check
private void WantsToBeNextState(States possableNewState)
{
    if (currentState < possableNewState)
    {
        GoToNextState(possableNewState);
    }
}

//It becomes the new state
private void GoToNextState(States newState)
{
    currentState = newState;
}
                                
                            
The second state that the enemy can go to, is the new pos state. How it works: randomly there goes a timer off, and the code gives sort of a ping which is like "Hey, I want to be the main state now". After that ping, it goes through a priority check if the "ping" state is a higher priority than the current state. If it is, it becomes that state.

For the other state, ability use, It works the same as the new pos state. But for the other state, the attack state, its "ping" works differently. The attack state gives a ping if the enemy is within range of the player.
                                
private void StateInfo()
{
    if (currentState == States.chasePlayer)
    {
        //The enemy is chasing the player
        enemy.enemyAgent.destination = playerTransform.transform.position;
    }
    if (currentState == States.chaseNewPos && !newPosAlreadyCalledOnce)
    {
        /*Function get called for generating new position
        After that the enemy is chasing that new position (new pos)*/
        newPosAlreadyCalledOnce = true;
        newPos = enemyNewPosGen.CreateNewPos();
        enemy.enemyAgent.destination = newPos;
    }
    if (currentState == States.attack && !attackAlreadyCalledOnce)
    {
        //Function get called for attacking the player
        attackAlreadyCalledOnce = true;
        enemy.enemyAgent.speed = 0f;
        enemyMainAttack.CheckForDesync();
    }
    if (currentState != States.attack && 
    currentState != States.abilityUse && !enemy.isDead)
    {
        //Resetting the attack and speed
        enemy.canAttackPlayer = false;
        enemy.enemyAgent.speed = enemy.enemySpeed;
    }
    if (currentState == States.abilityUse && !abilityUseAlreadyCalledOnce)
    {
        abilityUseAlreadyCalledOnce = true;
        //Checking if the enemy has an ability
        if (enemy.data.Ability == EnemyAbilities.none)
        {
            GoToDefaultState();
            ResetTimers();
            return;
        }
        //If he has, call function to activate the ability
        enemy.enemyAgent.speed = 0;
        StartCoroutine(enemyAbility.DoAbilityAnimation());
    }
}
                                
                            
This function manages what is going to happen if it's in a certain state. I only want the function to be called once in the if statement, so that's why there is a "! -any state- AlreadyCalledOnce" and the end of all the if statements and a "= true" in the first line in the if statements.
Results:

The Full Script
|
v


                                
using System;
using System.Collections;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Threading;
using UnityEngine;

public enum States
{
    chasePlayer,
    chaseNewPos,
    attack,
    abilityUse
}

public class StateMachine : MonoBehaviour
{
    [HideInInspector] public GameObject playerTransform;

    //Making an reference to the enum
    [SerializeField] States currentState;
    private States defaultState;

    [SerializeField] float abilityTimer;
    private float randomTime;
    private float newPosTimer;

    private bool newPosAlreadyCalledOnce;
    private bool attackAlreadyCalledOnce;
    private bool abilityUseAlreadyCalledOnce;


    private Vector3 newPos;
    private float distanceToNewPos;

    private EnemyNewPosGeneration enemyNewPosGen;
    private Enemy enemy;
    private EnemyAbility enemyAbility;
    private EnemyMainAttack enemyMainAttack;

    private void Start()
    {
        defaultState = States.chasePlayer;
        currentState = defaultState;
        randomTime = UnityEngine.Random.Range(1, 10);

        enemyNewPosGen = GetComponent<EnemyNewPosGeneration>();
        enemy = GetComponent<Enemy>();
        enemyAbility = GetComponent<EnemyAbility>();
        enemyMainAttack = GetComponent<EnemyMainAttack>();
    }

    private void Update()
    {
        if (enemy == null)
        {
            return;
        }
        if (playerTransform == null)
        {
            playerTransform = GameObject.FindWithTag("Player");
        }
        StateInfo();
        TriggerChaseNewPos();
        TriggerAbilityUse();
        TriggerAttack();
    }

    public void TriggerChaseNewPos()
    {
        newPosTimer += Time.deltaTime;
        distanceToNewPos = Vector3.Distance
        (newPos, enemyNewPosGen.transform.position);

        //If the "ping" goes off
        if (newPosTimer >= randomTime)
        {
            WantsToBeNextState(States.chaseNewPos);
        }

        //If the new target has been reached, reset timers and state
        if (currentState == States.chaseNewPos && distanceToNewPos < 1)
        {
            ResetTimers();
            GoToDefaultState();
        }
    }

    public void TriggerAttack()
    {
        float distance = Vector3.Distance
        (playerTransform.transform.position, transform.position);

        //If the player is in range
        if (distance < enemy.attackRange)
        {
            WantsToBeNextState(States.attack);
        }

        //If the player is not in range, reset to default state
        else if (currentState == States.attack)
        {
            currentState = defaultState;
        }
    }

    private void TriggerAbilityUse()
    {
        abilityTimer += Time.deltaTime;

        //If the "ping" goes off
        if (abilityTimer >= enemy.abilityCooldown)
        {
            WantsToBeNextState(States.abilityUse);
        }

        if (currentState == States.abilityUse && enemyAbility.EndAbility)
        {
            ResetTimers();
            GoToDefaultState();
            enemyAbility.EndAbility = false;
        }
    }

    //Priority check
    private void WantsToBeNextState(States possableNewState)
    {
        if (currentState < possableNewState)
        {
            GoToNextState(possableNewState);
        }
    }

    //It becomes the new state
    private void GoToNextState(States newState)
    {
        currentState = newState;
    }

    //All info what all the states do
    private void StateInfo()
    {
        if (currentState == States.chasePlayer)
        {
            //The enemy is chasing the player
            enemy.enemyAgent.destination = playerTransform.transform.position;
        }
        if (currentState == States.chaseNewPos && !newPosAlreadyCalledOnce)
        {
            /*Function get called for generating new position
            After that the enemy is chasing that new position (new pos)*/
            newPosAlreadyCalledOnce = true;
            newPos = enemyNewPosGen.CreateNewPos();
            enemy.enemyAgent.destination = newPos;
        }
        if (currentState == States.attack && !attackAlreadyCalledOnce)
        {
            //Function get called for attacking the player
            attackAlreadyCalledOnce = true;
            enemy.enemyAgent.speed = 0f;
            enemyMainAttack.CheckForDesync();
        }
        if (currentState != States.attack && 
        currentState != States.abilityUse && !enemy.isDead)
        {
            //Resetting the attack and speed
            enemy.canAttackPlayer = false;
            enemy.enemyAgent.speed = enemy.enemySpeed;
        }
        if (currentState == States.abilityUse && !abilityUseAlreadyCalledOnce)
        {
            abilityUseAlreadyCalledOnce = true;

            //Checking if the enemy has an ability
            if (enemy.data.Ability == EnemyAbilities.none)
            {
                GoToDefaultState();
                ResetTimers();
                return;
            }

            //If he has, call function to activate the ability
            enemy.enemyAgent.speed = 0;
            StartCoroutine(enemyAbility.DoAbilityAnimation());
        }
    }

    //Reset to the default state (chasing the player)
    public void GoToDefaultState()
    {
        currentState = defaultState;
        newPosAlreadyCalledOnce = false;
        attackAlreadyCalledOnce = false;
        abilityUseAlreadyCalledOnce = false;
    }

    //Resetting all timers
    public void ResetTimers()
    {
        newPosTimer = 0;
        randomTime = UnityEngine.Random.Range(1, 10);
        abilityTimer = 0;
    }
}