The best way to solve this is by implementing a game loop and having some state modelling for your characters, in my opinion. Sleep is
absolutely not the way to go.
Say your character had an Update method. In that method, you could add logic for handling a variety of states such as shooting, reloading etc. The simplest way to do this would be to have an enum for each state and handle each case, like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
|
#include <iostream>
class Character
{
public:
enum State
{
Idle,
Reloading,
Shooting
};
Character() : m_currentState(Idle){}
void Update(float dt)
{
switch (m_currentState)
{
case Idle:
std::cout << "Yawn!\n";
break;
case Reloading:
std::cout << "Click!\n";
break;
case Shooting:
std::cout << "Bang!\n";
break;
default:
throw "Unexpected state";
};
}
void SetState(State newState)
{
m_currentState = newState;
}
private:
State m_currentState;
};
int main(int argc, const char* argv[])
{
Character myCharacter;
// Simulating update loop...
myCharacter.Update(0.f);
myCharacter.SetState(Character::State::Shooting);
myCharacter.Update(0.f);
return 0;
}
|
Note, the logic is simplified in this example - you'd have more complex logic for reloading (probably some cooldown timer, which sets the state once it's done). Also, the double update call is in lieu of a functioning update loop, which would be expected in your application.
There are a couple of problems with this approach. That enum isn't all that flexible - what if certain characters don't have certain states (Torbjorn needs a 'Build' state, most others don't care for example). Your Character::State enum will get cluttered pretty quickly.
The other problem is how ugly the logic in your Update method will be. You're going to end up with a giant switch where each case has some potentially complex logic. Every time you need to change something, you'll need to change to innards of that method. Additionally, you'll need to do this for every character.
I
did say it was the simplest way. I
didn't say it was the best.
Personally, I'd have a small state interface and call into that in a characters Update method. That way, the logic for a state is isolated and multiple states could be used across multiple characters.
Bit of DRY fail and tidying needed (it's getting late), but here's an example - hopefully you get the gist. Again, simplified states, simulated updates.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108
|
#include <iostream>
class IStateOwner;
class ReloadState;
class IState
{
public:
virtual void Enter(IStateOwner* pOwner) = 0; // Pre-state logic
virtual void Exit() = 0; // Post-state logic
virtual void Update(float dt) = 0;
virtual ~IState(){}
};
class IStateOwner
{
public:
virtual void ChangeState(IState* pNewState) = 0;
};
class IdleState : public IState
{
public:
void Enter(IStateOwner* pOwner)
{
m_pOwner = pOwner;
std::cout << "Entering idle state...\n";
}
void Exit(){ std::cout << "Exiting idle state...\n"; }
void Update(float dt) { std::cout << "Idle update...\n"; }
private:
IStateOwner* m_pOwner;
};
class ReloadState: public IState
{
public:
ReloadState(const float reloadCooldownTime)
: m_reloadCooldownTime(reloadCooldownTime)
, m_elapsedTimeInState(0.f)
, m_pOwner(nullptr)
{
}
void Enter(IStateOwner* pOwner)
{
m_pOwner = pOwner;
std::cout << "Entering reload state...\n";
}
void Exit() { std::cout << "Exiting reload state...\n"; }
void Update(float dt)
{
std::cout << "Reload update...\n";
m_elapsedTimeInState += dt;
if (m_elapsedTimeInState > m_reloadCooldownTime) {
m_pOwner->ChangeState(new IdleState());
}
}
private:
float m_reloadCooldownTime;
float m_elapsedTimeInState;
IStateOwner* m_pOwner;
};
class Character : public IStateOwner
{
public:
void Update(float dt)
{
if (m_pCurrentState)
{
m_pCurrentState->Update(dt);
}
}
void ChangeState(IState* newState)
{
if (m_pCurrentState) m_pCurrentState->Exit();
delete m_pCurrentState;
m_pCurrentState= newState;
m_pCurrentState->Enter(this);
}
Character() : m_pCurrentState(nullptr){}
~Character()
{
delete m_pCurrentState;
}
private:
IState* m_pCurrentState;
};
int main(int argc, const char* argv[])
{
Character myCharacter;
myCharacter.Update(0.1f);
myCharacter.ChangeState(new ReloadState(5));
myCharacter.Update(4.0f);
myCharacter.Update(1.01f);
myCharacter.Update(0.1f);
return 0;
}
|