Last active
June 7, 2023 08:23
-
-
Save milos1290/a35bca3f1e26bad0b683 to your computer and use it in GitHub Desktop.
C++ Hierarchical Finite State Machine
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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>(); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// 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 */ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// 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