0
\$\begingroup\$

I want to use the finite state machine for the ennemies AI in my game. I implemented the pattern and it work fine for my first enemy (enemy A):

  • Enemy is in IdleState by default
  • When enemy see the player, it move to AttackState

But I want to implement an AI for a new enemy (enemy B) which is the same as enemy A, except that when he see the player, it move to DefenseState (instead of AttackState). The problem is that I see no other way than create a second IdleStateB class which inherit from the IdleState class (IdleStateB: IdleState). It bother me because I don't want to have as much Idle class as I have enemy behavior.

In fine, I want a way to control the next state which will be launched after some conditions are met. Thank you for your help!

Here is some code I use:

FSM.cs

public class FSM : MonoBehaviour {

    private Enemy enemy;
    public IEnemyState currentState;

    public FSM(Enemy enemy)
    {
        this.enemy = enemy;
    }

    public void Update()
    {
        if (currentState != null)
        {
            currentState.Execute(enemy);
        }
    }

    public void ChangeState(IEnemyState newState)
    {
        if (currentState != null)
        {
            currentState.Exit(enemy);
        }
        currentState = newState;
        currentState.Enter(enemy);
        currentState.Execute(enemy);
    }
}

IEnemyState.cs

public interface IEnemyState
{
    void Execute(Enemy enemy);
    void Enter(Enemy enemy);
    void Exit(Enemy enemy);
    void OnTriggerEnter(Collider2D other);

}

IdleState.cs

public class IdleState : IEnemyState
{

    public void Enter(Enemy enemy)
    {

    }

    public void Execute(Enemy enemy)
    {
        //Debug.Log("Idle State");
        Idle(enemy);
    }

    public void Exit(Enemy enemy)
    {

    }

    public void OnTriggerEnter(Collider2D other)
    {
        Debug.Log("OnTriggerEnter");
    }

    private void Idle(Enemy enemy)
    {
        enemy.GetComponent<EnemyMove>().Move(false);

        //  Si le joueur est dans la ligne de vue de l'ennemi, celui-ci change d'état
        if (enemy.CheckForPlayerInSight())
        {
            enemy.fsm.ChangeState(new MovingState());
        }
    }
}

Enemy.cs

public class Enemy : MonoBehaviour
{

    public Transform player;        // Reference to the player's transform.

    public float idlingDistance = 15f;
    public float attackingDistance = 1.5f;

    // finite state machine
    public FSM fsm;

    // Use this for initialization
    public virtual void Start()
    {
        fsm = new FSM(this);
        fsm.ChangeState(new IdleState());
    }

    // Update is called once per frame
    public virtual void Update()
    {
        fsm.Update();
    }

    void OnTriggerEnter2D(Collider2D collider)
    {
       // currentState.OnTriggerEnter(collider);
    }

    // Vérifie si le joueur est en ligne de mire ou non
    public bool CheckForPlayerInSight()
    {
        RaycastHit2D? sightTest = RaycastToPlayer(idlingDistance, Color.red);

        if (sightTest != null)
        {
            return true;
        }

        return false;
    }

    public bool CheckForPlayerInAttackRange()
    {
        RaycastHit2D? sightTest = RaycastToPlayer(attackingDistance, Color.blue);

        if (sightTest != null)
        {
            return true;
        }

        return false;
    }

    public bool IsPlayerAtLeft()
    {
        RaycastHit2D? sightTest = RaycastToPlayer(1000f, Color.green);

        if (sightTest != null)
        {
            if (sightTest.Value.point.x < transform.position.x)
            {
                return true;
            }
            else
            {
                return false;
            }
        }

        return false;
    }

    public virtual void Loot()
    {
        // Loot de l'expérience
        Instantiate(Resources.Load("Prefabs/Loots/exp_orb"), transform.position, transform.rotation);
    }

    #region utility

    private RaycastHit2D? RaycastToPlayer(float distance, Color raycastColor)
    {
        // Si le joueur n'existe plus, retourne null
        if (player == null)
        {
            return null;
        }

        //precompute our ray settings
        Vector3 start = transform.position;
        Vector3 direction = (player.GetComponent<Renderer>().bounds.center - transform.position).normalized;

        //draw the ray in the editor
        Debug.DrawRay(start, direction * distance, raycastColor);

        //do the ray test
        RaycastHit2D[] sightTests = Physics2D.RaycastAll(start, direction, distance);
        //now iterate over all results to work out what has happened
        for (int i = 0; i < sightTests.Length; i++)
        {
            RaycastHit2D sightTest = sightTests[i];
            if (sightTest.collider != null)
            {
                if (sightTest.collider.gameObject.transform == player)
                {
                    return sightTest;
                } else if (sightTest.collider.tag == Tags.SIGH_BLOCKING)
                {
                    return null;
                }
            }
        }

        return null;
    }

    #endregion 
}
\$\endgroup\$

1 Answer 1

3
\$\begingroup\$

Add a method public IEnemyState CreatePlayerReactState() to the Enemy class. For one enemy, have it return a DefenseState, for the other enemy, return an AttackState. You can implement that by creating two subclasses of Enemy with different implementations of that method, or by adding a private variable to Enemy which says how it reacts to the player.

Then, in your method which detects the player, do enemy.fsm.ChangeState(enemy.CreatePlayerReactState()).

\$\endgroup\$
1
  • \$\begingroup\$ It's a good idea, I'll give it a try then mark this as answer if it's a success. Thank you ;) \$\endgroup\$
    – ApheX
    Oct 18, 2016 at 7:11

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .