Expanding on the State Machine

So currently, you'll notice that in the Idle state we are setting our velocity to 0,0 every single frame. In this simple example, it's not a terribly big deal, but this overdoing of calculations is something that we'd like to avoid in the future. We only really need to do it once, right when we enter the state. We may also want to do certain actions when we leave the state, but we won't be able to do that in the current form of our State Machine, so we are going to need to redo some stuff.

First, let's go back to the ChasePlayerComponent.h file and add the following bold function definitions:

class ChasePlayerComponent : public M5Component 
{
public:
ChasePlayerComponent(void);
virtual void Update(float dt);
virtual void FromFile(M5IniFile& iniFile);
virtual M5Component* Clone(void);
virtual void EnterState(State state);
virtual void UpdateState(State state, float dt);
virtual void ExitState(State state);
virtual void SetNewState(State state, bool initialState = false);
private:
float m_speed;
float m_followDistance;
float m_loseDistance;

void FollowPlayer();
float GetDistanceFromPlayer();
State m_currentState;

};

So instead of having our Update function handle everything, we've now created three functions for each of the different times that our state can be in: entering a new state, updating based on that state, and then what to do when we leave the state. Aside from that, we also have a SetNewState function which will take care of changing the state to something else. All of the functions take in a State enum to choose how to execute, with the Update state also having the time that passed this frame, and the SetNewState having an option for saying it's the first time you've set a state so you don't need to leave the previous one. After that, we need to actually add in the functionality for these new functions:

void ChasePlayerComponent::EnterState(State state) 
{
// Depending on what state we are in, do different things
switch (state)
{
case Idle:
// No longer move if we were
if (m_pObj)
{
m_pObj->vel = M5Vec2(0, 0);
}

M5DEBUG_PRINT(" Idle: Enter");
break;

case Follow:
M5DEBUG_PRINT(" Follow: Enter");
break;

case Death:
m_pObj->isDead = true;
M5DEBUG_PRINT(" Death: Enter");
break;
}
}

void ChasePlayerComponent::UpdateState(State state, float)
{
// Depending on what state we are in, do different things
switch (state)
{
case Idle:
//M5DEBUG_PRINT(" Idle: Update");
// If the player gets too close, the enemy notices them
if (GetDistanceFromPlayer() < m_followDistance)
{
// And will begin to give chase
SetNewState(Follow);
}

break;

case Follow:
//M5DEBUG_PRINT(" Follow: Update");

// Follow the player
FollowPlayer();

// If the player manages to get away from the enemy
if (GetDistanceFromPlayer() > m_loseDistance)
{
// Stop in your tracks
SetNewState(Idle);
}
break;
}
}

void ChasePlayerComponent::ExitState(State state)
{
// Depending on what state we are in, do different things
switch (state)
{
case Idle:
M5DEBUG_PRINT(" Idle: Exit");
break;

case Follow:
M5DEBUG_PRINT(" Follow: Exit");
break;
}
}

// initialState by default is false, so will only need to give
// second parameter the first time it is called
void ChasePlayerComponent::SetNewState(State state, bool initialState)
{
if (!initialState)
{
// Exit of our old state
ExitState(currentState);
}

// Then start up our new one
m_currentState = state;
EnterState(m_currentState);
}

And then, we need to update our Update function to just call our correct function:

void ChasePlayerComponent::Update(float dt) 
{
UpdateState(m_currentState, dt);
}

We also need to change our constructor so that instead of setting the current state, we set it ourselves:

/*************************************************************************/ 
/*!
Sets component type and starting values for player
*/
/*************************************************************************/
ChasePlayerComponent::ChasePlayerComponent(void):
M5Component(CT_ChasePlayerComponent),
m_speed(1)
{
SetNewState(Idle, true);
}

First of all, note that I am calling the M5DEBUG_PRINT function. This is to make it easy to tell that we are changing between different states. For the purposes of this demonstration, I commented out the Update function's version, but it could be useful for you to check it out. Note in this version, we have a switch statement for each of the functions and do something differently based on the state that is set in there.

In my version of the editor, by default the text will not be displayed on the screen. To fix this issue, go to the SplashStage.cpp file and comment out the following bold code:

SplashStage::~SplashStage(void) 
{
//We are done this with ArcheType so lets get rid of it.
M5ObjectManager::RemoveArcheType(AT_Splash);
//M5DEBUG_DESTROY_CONSOLE();
}

Now let's run our project!

You can tell from the editor when we are switching our states and that the code is being called correctly!

This version works pretty well, but there are some issues with it; namely that it involves a lot of rewriting, and we will need to copy/paste this functionality and make changes anytime we want to make a new version. Next, we will take a look at the State Machine included in the Mach5 Engine and the advantages that it has over what we've been talking about so far.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.144.31.163