3 min read

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


Playa Vista, California
Audio Engineer

Digital Extremes

London, Ontario, Canada
Communications Director

High Moon Studios

Carlsbad, California
Senior Producer

Build a Rocket Boy Games

Edinburgh, Scotland
Lead UI Programmer
More Jobs   


Register for a
Subscribe to
Follow us

Game Developer Account

Game Developer Newsletter


Register for a

Game Developer Account

Gain full access to resources (events, white paper, webinars, reports, etc)
Single sign-on to all Informa products

Subscribe to

Game Developer Newsletter

Get daily Game Developer top stories every morning straight into your inbox

Follow us


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