Enemies Finite State Machine (FSM)

From IrrWizard

Jump to: navigation, search

Introduction

This short tutorial shows how you can control the various enemy game logic & animations by using a 'Finite State Machine' pattern. A finite state machine (FSM) or finite automaton is a model of behaviour composed of states, transitions and actions. A state stores information about the past, i.e. it reflects the input changes from the system start to the present moment.

A transition indicates a state change and is described by a condition that would need to be fulfilled to enable the transition. An action is a description of an activity that is to be performed at a given moment.

These are the action types:

  • Entry action - Execute the action when entering the state
  • Exit action - Execute the action when exiting the state
  • Input action - Execute the action dependent on present state and input conditions
  • Transition action - Execute the action when performing a certain transition

wizard03.jpg


Finite State Machine

The following classes of interest are created when you choose the 'Full Framework' option while running IrrWizard (default).

  • CGameEntity
  • CGameEnemy
  • CGameEnemyOwnedStates

An enemy state is represented by a class, and contains all the logic needed for it's own state. (pluggable logic). The GameEnemyOwnedStates.cpp file holds 6 basic states an enemy can be in at any particular time. EntityStateStand, EntityStateRun, EntityStatePursue, EntityStateEvade, EntityStateAttack and EntityStateDeath. More states can easily be added, these are just to get you up and running.

Once an enemy is added to the EnemeyManager in the CGameStateLevel01::Init() function, all of the above default states will be activated.

...
pManager->getEnemyManager()->AddEntity(enemy);
...
           


Each state has an Enter(), Execute() and Exit() function.

Initialisation code like setting the MD2 animation goes in the Enter() function, enemy logic and calling the next state goes in the Execute() function and de-initialisation and tidy -up code in the Exit().

When you change an enemy state, the Exit() function of the current state is called automatically before the Init() of the next state. The Execute() function of the EnemyOwnedState should be called from within the main GameState's Update() function each game loop.

Here is an example of changing from one enemy state to another:

void EntityStateStand::Execute(CGameEnemy* pEnemy)
{
   // being attacked, change state to attack!!
   if (pEnemy->getHealth() < pEnemy->getMaxHealth())
       pEnemy->ChangeState(EntityStateAttack::Instance());     
}
           

CGameEnemyManager is created to control and oversee all enemy entities. This is called from the main GameState's Update() function each game loop as previously mentioned


pManager->getEnemyManager()->Update();
   

Also you will notice it takes a CGameEnemy* pEnemy as the parameter. You will need to create the pEnemy in the CGameStateLevel01::Init() function when you add the enemy node:

...
enemyNode->setMD2Animation(scene::EMAT_STAND);
enemyNode->setScale(core::vector3df(1.5,1.5,1.5));
enemyNode->setID(ID_ENEMY);
CGameEnemy* enemy = new CGameEnemy(ENTITY_MERC);  // any unique ID
enemy->setModel(enemyNode);
enemy->setHealth(100);
enemy->init(pManager);
pManager->getEnemyManager()->AddEntity(enemy);


Adding a new state

To add a new state, a new class and header file with the following functions are required.

Header file, .h:

//! Enemy state test
class EntityStateTest: public IGameEnemyState
{
private:
  EntityStateTest(){}
  //copy ctor and assignment should be private
  EntityStateTest(const EntityStateTest&);
  EntityStateTest& operator=(const EntityStateTest&);
public:
  //this is a singleton
  static EntityStateTest* Instance();
  virtual void Enter(CGameEnemy* enemy);
  virtual void Execute(CGameEnemy* enemy);
  virtual void Exit(CGameEnemy* enemy);
};


Source file, .cpp:

EntityStateTest* EntityStateTest::Instance()
{
       static EntityStateTest instance;
       return &instance;
}
void EntityStateTest::Enter(CGameEnemy* pEnemy)
{  
    	// change animation here
}
void EntityStateTest::Execute(CGameEnemy* pEnemy)
{
  	// state logic here 
}
void EntityStateTest::Exit(CGameEnemy* pEnemy)
{
       // clean up if necessary here
}
	 

It's a good idea to have each state in it's own class and header file, as this will be easier to maintain when more are added. The default states are in one class file to keep things compact and together. Alternatively, to get used to creating states, it will probably be easier to cut/paste the EntityStateStand from within the CGameEnemyOwnedStates.cpp and .h files to start with.

Personal tools