State management

Started by
2 comments, last by Togame 12 months ago

Hi, I am quite new to game development and have issues how to manage my games states.

I have a state A in my game where my player can do certain things while other actions happening in parallel. E.g. an animal sleeps, wakes up and plays around. So after the sleep animation is complete, the wake up animation is started, etc.

Now, if the state changes to B, all animals should run away. I can obviously not make a state for each combination like State-A_animal-1-sleeps_animal-2-wakes-up. My issue is, when I run chained animations (sleep, wake up, run around) I end up doing this in my update loop:

case state === State.A:
  await animal.sleep(5000);
  if (state === State.A) {
    await animal.wakeUp();
    if (state === State.A) {
      animal.runAround();
    }
  }
  break;
case state === State.B:
  animal.runAway();
  break;

So I check if the state is still A before each chained animation, because the state could have changed while the previous animation was running which leads to this spaghetti code.

I hope someone can help or point me in the right direction.

Advertisement

We have several articles and tutorials on the site about using state machines as you described, but which to recommend would depend on which problem you're encountering.

I notice a couple things.

Generally state transitions should be instantaneous, if you need to animate something then you need an additional state that represents the transition. You may need time for entry or exit animations, you may need time for walking or traveling, you may need time to idle while a partner moves into position, each of those are also full states that need their own logic, they're typically not instant transitions.

Second, it looks like you've merged multiple state machines together. You have one state machine that you have State A and State B. You have another state machine that is playing animations. When you try to merge state machines you end up getting a partial combinatorial explosion, all the combinations of one multiply by all the combinations of the other, pruned to whatever ones you consider valid. That explodes in size.

With that in mind, you're probably headed in the direction of behavior trees. They're basically an organized way of asking questions, and triggering behaviors in response:

  • Am I almost dead and in danger? → Run
  • Am I being attacked? → Fight
  • Am I tired? → Sleep
  • Otherwise → Idle

If you need a transition within them, then each behavior would have three states: [Enter], [Run loop], [Exit]. Run the run loop until the behavior tree says to do something different.

Behaviors can have nested actions. A fight run loop might have moving around and playing variations on fighting animations, with jabs, blocks, parries, or whatever.

There are many ways to implement the concept, from states as classes as a shallow and broad inheritance tree, to families of functions and function pointers, to switch statements, to a bunch of if or if/else trees. Those are progressively easier to implement but more difficult to maintain, and all can work depending on the size and complexity of your systems.

Going further, behavior trees aren't goal directed, they're typically based on a hierarchy. You start at the beginning, you check the current node and if true, run the associated machine, otherwise advance to the next node. The final node should always run as an idle loop, if nothing else is triggered then you do that behavior. There are more advanced systems that will prioritize and weight behaviors, and systems that will plan a series of behaviors, but even these use the state machines as building blocks. running machine after machine, rather than trying to merge the state machines together.

@frob Thank you, that actually helps a lot. Your examples ("Am I almost dead and in danger?", …) made me realize that I should move the animal behavior logic in the animal class instead of controlling the animal class from the outside. So instead of animal.wakeUp(); animal.runAround(); I can just call animal.doYourThing() and then call something like animal.scare() once I transition to main state B.

This topic is closed to new replies.

Advertisement