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\$
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\$
  • \$\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 '16 at 7:11

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.