Navigation Menu

Skip to content

Instantly share code, notes, and snippets.

@milos1290
Last active June 7, 2023 08:23
Show Gist options
  • Star 5 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save milos1290/a35bca3f1e26bad0b683 to your computer and use it in GitHub Desktop.
Save milos1290/a35bca3f1e26bad0b683 to your computer and use it in GitHub Desktop.
C++ Hierarchical Finite State Machine
class OnGroundState : public State
{
public:
OnGroundState(Hero* hero);
virtual ~OnGroundState(){};
virtual void didEnterWithPreviousState(State* previousState);
virtual void updateWithDeltaTime(float delta);
virtual void willExitWithNextState(State* nextState);
virtual bool isValidNextState(State* state);
};
class IdleState : public OnGroundState
{
public:
IdleState(Hero* hero);
virtual void didEnterWithPreviousState(State* previousState);
virtual void updateWithDeltaTime(float delta);
virtual void willExitWithNextState(State* nextState);
virtual bool isValidNextState(State* state);
};
class RunState : public OnGroundState
{
public:
RunState(Hero* hero);
virtual void didEnterWithPreviousState(State* previousState);
virtual void updateWithDeltaTime(float delta);
virtual void willExitWithNextState(State* nextState);
virtual bool isValidNextState(State* state);
};
void initMachine()
{
auto stateMachine = StateMachine::create();
stateMachine->addState<IdleState>(_hero);
stateMachine->addState<RunState>(_hero);
stateMachine->enterState<IdleState>();
}
//
// state.h
//
#ifndef state_h
#define state_h
class StateMachine;
class State
{
public:
/**
* Called when entering state
*
* @param previousState previous state or null if this is the first state
*/
virtual void didEnterWithPreviousState(State* previousState) {};
/**
* Called every frame by state machine
*
* @param delta time
*/
virtual void updateWithDeltaTime(float delta) {};
/**
* Checks if next state is valid for transition
*
* @param state next state
*
* @return true if valid, false otherwise
*/
virtual bool isValidNextState(State* state) {return false;};
/**
* Called when exiting current state
*
* @param nextState next state
*/
virtual void willExitWithNextState(State* nextState) {};
/**
* Get state machine
*
* @return state machine
*/
StateMachine* getStateMachine()
{
return _stateMachine;
}
/**
* Set State machine, this will be set when state has been added to state machine
*
* @param stateMachine parent state machine
*/
void setStateMachine(StateMachine* stateMachine)
{
_stateMachine = stateMachine;
}
virtual ~State(){};
protected:
StateMachine* _stateMachine;
};
#endif /* state_h */
//
// state_machine.h
//
#ifndef state_machine_h
#define state_machine_h
#include <unordered_map>
#include "state.h"
class StateMachine
{
public:
/**
* Create state machine
*
* @return new state machine
*/
static StateMachine* create()
{
auto ref = new (std::nothrow) StateMachine();
return ref;
}
/**
* Find state by id and return casted state
*
* @return casted state
*/
template<class T>
T* findState()
{
const std::string name = typeid(T).name();
if (_states.count(name) != 0)
{
return static_cast<T*>(_states[name]);
}
return nullptr;
}
/**
* Add new state to state machine
*
* @param args arguments to pass to constructor of state
*/
template<typename T, class... Args>
void addState(Args&&... args)
{
auto typeId = typeid(T).name();
auto state = new T(std::forward<Args>(args)...);
state->setStateMachine(this);
_states.insert({typeId, state});
}
/**
* Check if we can enter state
*
* @return true if this state is valid, false otherwise
*/
template<typename T>
bool canEnterState()
{
if (_state == nullptr)
{
return true;
}
else
{
auto state = findState<T>();
if (state)
{
return _state->isValidNextState(state);
}
}
return false;
}
/**
* Enters new state
*
* Before entering new state old state will check if it is a valid state to execute
* transaction
*
* Order of execution:
*
* willExitWithNextState will be called on current state
* didEnterWithPreviousState will be called on new state
*
* @return true if entered, false otherwise
*/
template<typename T>
bool enterState()
{
auto state = findState<T>();
if (state)
{
if (_state == nullptr)
{
_state = state;
_state->didEnterWithPreviousState(nullptr);
return true;
}
else
{
if (_state->isValidNextState(state))
{
_state->willExitWithNextState(state);
state->didEnterWithPreviousState(_state);
_state = state;
return true;
}
}
}
return false;
}
/**
* Enters new state without any check if next state is valid
*
*
* Order of execution:
*
* willExitWithNextState will be called on current state
* didEnterWithPreviousState will be called on new state
*
* @return true if entered, false otherwise
*/
template<typename T>
bool setState()
{
auto state = findState<T>();
if (state)
{
if (_state == nullptr)
{
_state = state;
_state->didEnterWithPreviousState(nullptr);
return true;
}
else
{
_state->willExitWithNextState(state);
state->didEnterWithPreviousState(_state);
_state = state;
return true;
}
}
return false;
}
/**
* Update state machine delta time, this will call updateWithDeltaTime on current state
*
* @param delta delta time
*/
void updateWithDeltaTime(float delta)
{
if (_state != nullptr)
{
_state->updateWithDeltaTime(delta);
}
}
/**
* Get current state
*
* @return current state
*/
State* getState()
{
return _state;
}
~StateMachine()
{
_state = nullptr;
}
private:
std::unordered_map<std::string, State*> _states;
State* _state;
StateMachine():
_state(nullptr)
{
}
};
#endif /* state_machine_h */
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment