UML State Charts for C++ continued

Recently I found some time to polish on my state chart library presented in the first post...

Now that our current game project has reached the finishing line, I found some time to polish on the state chart library which I outlined in the first post. I also tried it in the real life context and I gained the following insights:

1/ I gave up searching for a perfect textual representation of the state chart. It is hopeless. As soon as applied to real life examples, it simply gets messy. Because of: a) the essential complexity of representing complex nested graph structures and b) accidental complexity of representing it as text. 

2/ I gave up implementing the full UML state chart specification of the form

SOURCE -- event [guardCondition] /action --> TARGET.

Instead of that, I changed it to:

SOURCE -- [transitionCondition] /action --> TARGET

Basically, summarizing event [guardCondition] to [transitionCondition].
This makes it easier to use the library in system which are not event-driven.

I didn't investigate this further, but I think "event [guardCondition] /action" could be seen as a specification of the more generic  "[transitionCondition] /action" definition.

3/ I got rid of macros in definition, because it gets messy easily. Instead of that I use overloaded >>, <<, [], and / operators to specify the transition rules.

SOURCE [transitionCondition] /action >> TARGET;

TARGET [transitionCondition] /action << SOURCE;

4/ Finally, I implemented nested states, which significantly contribute to reducing the size of the state chart definition.


The following image depicts the (reverse engineered) state chart of the PlantAttack object from our game (-> I apologize for the free-style notation, however, I wasn't able to find a good tool capable of representing state charts with nested states.

PlantAttack State Chart

PlantAttack in Action

The code bellow shows the definition in the code:

PlantAttack::StateMachineDef* PlantAttack::getStateMachineDef() {
    static State      Init ...;
    static State      Inactive ...;
    static State      Active ...;
    static StateFnPtr Active_Approaching ...;
    static StateFnPtr Active_Sprout (
        ATTACK_PLANT_MODE_SPROUT,   // int id of this node
        "Sprout",                   // string id of this node
        ...                         // ...other parameters...
        NULL,                       // no update function
        &PlantAttack::sSproutEnter, // enter function 
        NULL);                      // no exit function
    static StateFnPtr Disappear	...;
    static State      Dead ...;

    Init     >> Inactive;
    Inactive >> Active;
    Active              [gc(TakeFireDamage) 
                         && gc(TakeDamage)] /tf(TakeDamage) >> Charred;
    Charred             [gc(Timeout)]                       >> CharredDisappear;
    Active              [!gc(TakeFireDamage)  
                         && gc(TakeDamage)] /tf(TakeDamage) >> Rot;
    Inactive            [gc(Timeout)]             << CharredDisappear;
    Active_Approaching  [gc(Timeout)]             >> Active_Sprout;
    Active_Sprout       [gc(Timeout)]             >> Active_BiteCooldown;
    Active_BiteCooldown [gc(BarrierInteraction)]  >> Active_Grow;
    Active_BiteCooldown [gc(Timeout)]             >> Active_Effective;
    Active_Effective    [gc(Starved)||gc(FinishRequested)] >> Rot;
    Rot                 [gc(Timeout)]             >> AfterRot;
    AfterRot_Falling    [gc(HeadAtFloor)]         >> AfterRot_Lie;
    Disappear           [gc(LastDrawPointZero)]   << AfterRot_Lie;
    Inactive            [gc(Timeout)]             << Disappear;
    Active_Effective    [gc(BarrierInteraction)]  >> Active_Grow;
    Active_Effective    [!gc(BarrierInteraction)] << Active_Grow;
    Active_Effective    [gc(BitableInRange)]      >> Active_BitePrepare;
    Active_BitePrepare  [gc(Timeout)]             >> Active_BiteExecute;
    Active_BiteCooldown [gc(Timeout)]             << Active_BiteExecute;

void PlantAttack::sRot(float dTime) ...
void PlantAttack::sAfterRot(float dTime) ...
void PlantAttack::sFallingEnter() ...
void PlantAttack::sFalling(float dTime) ...
void PlantAttack::sSproutEnter() ...
void PlantAttack::sBiteExecuteEnter() ...
void PlantAttack::sBiteExecuteExit() ...
void PlantAttack::sGrow(float dTime) ...
void PlantAttack::sGrowExit() ...

void PlantAttack::tfTakeDamage() ...

bool PlantAttack::gcBitableInRange(State* ctx) ...
bool PlantAttack::gcLastDrawPointZero(State* ctx) ...
bool PlantAttack::gcHeadAtFloor(State* ctx) ...
bool PlantAttack::gcStarved(State* ctx)	...
bool PlantAttack::gcFinishRequested(State* ctx) ...
bool PlantAttack::gcBarrierInteraction(State* ctx) ...
bool PlantAttack::gcTakeFireDamage(State* ctx) ...
bool PlantAttack::gcTakeDamage(State* ctx) ...
bool PlantAttack::gcTimeout(State* ctx)	...

Note: in order to simplify the creation of GuardConditions and TransitionActions, I define two simple macros called gc and tf. gc simplifies the creation of GuardCondition objects, which point to member functions of type bool(*)(State*) and tf simplifies the creation of TransitionFunction objects, which point to member functions of the type void(*)().

Implementation and a simple example can be found here:

Latest Jobs


Hybrid, Cambridge, MA or Chicago, IL
Quality Assurance Lead

Bladework games

Remote (United States)
Senior Gameplay Engineer

High Fidelity, Inc.

Game Interaction Designer

Fred Rogers Productions

Hybrid (424 South 27th Street, Pittsburgh, PA, USA
Producer - Games & Websites
More Jobs   


Explore the
Advertise with
Follow us

Game Developer Job Board

Game Developer


Explore the

Game Developer Job Board

Browse open positions across the game industry or recruit new talent for your studio

Advertise with

Game Developer

Engage game professionals and drive sales using an array of Game Developer media solutions to meet your objectives.

Learn More
Follow us


Follow us @gamedevdotcom to stay up-to-date with the latest news & insider information about events & more