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
}