CharacterControl move problem

I’d be interested to see too as I certainly hope to be walking and attacking at some point

:wink: :wink: :wink:

Ok, let’s give it a try.

PART 1

I won’t go too much into the background of finite state machines. Suffice it to say that there was a time when almost every C application that took input was probably built on a finite state machine. I highly recommend doing some additional research on the subject.

…and if these were trivial to put together in C then certainly we can do so in Java.

If it helps motivate you, finite state machines (FSM) are one of those techniques like entity-component-systems (ECS). They will change the way you think about coding and you will see a bunch of new ways to apply them. Unlike an ECS, an FSM is really simple and breaking up a problem into states is even not so bad.

And I mean really really simple. You can make FSMs complicated with lots of features but I believe when I’m done showing you the simple way you will think “Is that all?!?”

The meat:

A state machine consists of a bunch of conceptual “states” and the transitions or triggers that take you from one state to another. Usually, this is represented as a diagram but before we get to that, let’s think about some states and transitions.

For my example, I’m going to simplify some things from a game perspective. And you’ll see that even in this simple example, things get complicated really fast… complicated in a way that would make traditional

Our inputs:

  • Start Walking (like pressing ‘w’ or whatever)
  • Stop Walking (like releasing ‘w’)
  • Jump (pressing the jump button)
  • Hit Ground (we landed again, ie: jump is over)
  • Attack (pressing the attack button)
  • Attack Done (the non-interruptable attack completed, ie: animation finished, time passed, whatever)

So a naive set of states would be:

  • Idle
  • Walking
  • Jumping
  • Attacking

And the diagram for the naive transitions might look like:

When we are Idle, we can start walking to enter the walking state. Stop walking to return to Idle. Jump to enter jumping state, hit the ground to go back to idle, and so on.

This is overly simple for our game because you can’t jump while walking or walk while jumping, etc… But it will be enough to get us started and then we’ll try adding those things.

Here is what some code might look like (I have not compiled this, I’m only typing it from thin air). I’m also removing a lot of Java boiler plate:

// Define our states
public static final int STATE_IDLE = 0;
public static final int STATE_WALKING = 1;
public static final int STATE_JUMPING = 2;
public static final int STATE_ATTACKING = 3;
public static final int MAX_STATE = 4;

// Define our transition triggers
public static final int T_START_WALKING = 0;
public static final int T_STOP_WALKING = 1;
public static final int T_JUMP = 2;
public static final int T_HIT_GROUND = 3;
public static final int T_ATTACK = 4;
public static final int T_ATTACK_DONE = 5;
public static final int MAX_TRIGGER = 6;

// Build the FSM
Runnable[][] fsm = new Runnable[MAX_STATE][MAX_TRIGGER];
int currentState = STATE_IDLE;

// To make code easier, we'll fill every entry with an empty runnable.
for( Runnable[] state : fsm ) {
    for( int i = 0; i < MAX_TRIGGER; i++ ) {
        state[i] = () -> {};
    }
}

// Now we can define spefic state transitions
fsm[STATE_IDLE][T_START_WALKING] = () -> {  currentState = STATE_WALKING; };
fsm[STATE_IDLE][T_JUMP] = () -> { currentState = STATE_JUMPING; };
fsm[STATE_IDLE][T_ATTACK] = () -> { currentState = STATE_ATTACKING; };

fsm[STATE_WALKING][T_STOP_WALKING] = () -> { currentState = STATE_IDLE; };

fsm[STATE_JUMPING][T_HIT_GROUND] = () -> { currentState = STATE_IDLE; };

fsm[STATE_ATTACKING][T_ATTACK_DONE] = () -> { currentState = STATE_IDLE; };

// Now we use the state machine to do stuff...
// I'm going to abstract away how the triggers get invoked but it's
// something like the following pseudo code:
on key event 'w' {
    if( pressed ) {
        fsm[currentState][T_START_WALKING].run();
    } else {
        fsm[currentState][T_STOP_WALKING].run();
    }
}
on key event ' ' {
    if( pressed ) {
        fsm[currentState][T_JUMP].run();
    }
}
on new collision with ground {
    fsm[currentState][T_HIT_GROUND].run();
}
on left mouse button {
    if( pressed ) {
        fsm[currentState][T_ATTACK].run();
    }
}
on attack animation finished {
    fsm[currentState][T_ATTACK_DONE].run();
}

And that’s it. That’s a very simple FSM that implements our simple state transition diagram. If you code that and hook the triggers up to real code then you have a fully working state machine. The Runnable lambdas that were assigned to the FSM can be more complicated as needed… or even directly call methods or whatever.

fsm[STATE_IDLE][T_ATTACK] = () -> { 
        currentState = STATE_ATTACKING; 
        startAttackAnimation();
    };

Or whatever. You can even have handlers that don’t transition state and just do some other work. For example, if you had to click the mouse twice to attack you could increment a counter or something. (Though technically that could also be a different state, STATE_PREPARE_ATTACK.)

In Part 2, I will complicate the state diagram by allowing a walking jump. I just know some folks are waiting and wanted to give you something to read right way and think about.

3 Likes

PART 2

One thing I will point out about the previous example is that we can also put conditionals in the FSM handlers (Runnable in this case). So for example, maybe you only allow jumping if the player is already on the ground.

The transition could be modified to check that:

fsm[STATE_IDLE][T_JUMP] = () -> { 
        if( on ground ) {
            currentState = STATE_JUMPING; 
        }
    };

Then the state transition won’t happen unless the player is idle and standing on the ground.

This also could have been implemented with multiple states. We could have STATE_IDLE_ON_GROUND and STATE_IDLE_FALLING. With that, we wouldn’t need a conditional in the trigger handler.

When to make a new state and when to add a conditional is one of those harder design choices. You will get a feel for it over time but I caution you:
If you rely too heavily on if statements within your trigger handlers then you may rapidly end up in a situation similar to what started this thread in the first place.

Either way, it’s not so hard to change it later.

That leads us to how we would make it so that we can jump while walking.

The naive way to implement this would be to reuse the existing states and make a transition between walking and jumping:

This would be easy enough to implement. Just add:

fsm[STATE_WALKING][T_JUMP] = () -> { currentState = STATE_JUMPING; };

But now we run into some problems.

The player can start walking then hit the jump button and start jumping but when they hit the ground then they won’t be walking anymore. We’re back to Idle. And worse yet, an implementation detail of our triggers means that they have to release the ‘w’ and press it again before we can start walking.

We are left with three ways to handle this problem:

  1. Change how we trigger states by making ‘w’ a continuous trigger.
  2. Add a state transition back to Walking if we know we came from walking before. (DO NOT DO THIS: there are dragons and monsters down that path)
  3. Add an additional state to represent the walking jump.

That’s what we’ll do here so you can see what I mean. Like this diagram:

With this, we don’t have any new triggers but now we have an additional state so we’ll update the state constants:

// Define our states
public static final int STATE_IDLE = 0;
public static final int STATE_WALKING = 1;
public static final int STATE_JUMPING = 2;
public static final int STATE_ATTACKING = 3;
public static final int STATE_JUMPING_FORWARD = 4;
public static final int MAX_STATE = 5;

Then we’ll add some additional triggers: (insert into old code as appropriate)

fsm[STATE_WALKING][T_JUMP] = () -> { currentState = STATE_JUMPING_FORWARD; };

fsm[STATE_JUMPING][T_START_WALKING] = () -> { currentState = STATE_JUMPING_FORWARD; };

fsm[STATE_JUMPING_FORWARD][T_STOP_WALKING] = () -> { currentState = STATE_JUMPING; };
fsm[STATE_JUMPING_FORWARD][T_HIT_GROUND] = () -> { currentState = STATE_WALKING; };  

Putting it all together, here is what the complete state machine definition would look like:

fsm[STATE_IDLE][T_START_WALKING] = () -> {  currentState = STATE_WALKING; };
fsm[STATE_IDLE][T_JUMP] = () -> { currentState = STATE_JUMPING; };
fsm[STATE_IDLE][T_ATTACK] = () -> { currentState = STATE_ATTACKING; };

fsm[STATE_WALKING][T_STOP_WALKING] = () -> { currentState = STATE_IDLE; };
fsm[STATE_WALKING][T_JUMP] = () -> { currentState = STATE_JUMPING_FORWARD; };

fsm[STATE_JUMPING][T_HIT_GROUND] = () -> { currentState = STATE_IDLE; };
fsm[STATE_JUMPING][T_START_WALKING] = () -> { currentState = STATE_JUMPING_FORWARD; };

fsm[STATE_ATTACKING][T_ATTACK_DONE] = () -> { currentState = STATE_IDLE; };

fsm[STATE_JUMPING_FORWARD][T_STOP_WALKING] = () -> { currentState = STATE_JUMPING; };
fsm[STATE_JUMPING_FORWARD][T_HIT_GROUND] = () -> { currentState = STATE_WALKING; };

We don’t have to add any new triggers so the rest of the code is the same and now we already support this new state. Kind of cool, eh?

It can sometimes feel a little weird to add these kinds of state but I’d like to highlight that in this case it’s super-likely that the “Jump straight up” and “Jump forward” animations could be different. It is quite often the case that the animation configurations line up nicely with states. (This is why so many animation libraries tend to include some kind of state machine.)

Unless there are specific questions, I think that’s all I’m going to write on this subject for now.

I will point out some interesting things to think about:
What if you decided that you wanted to have a jump attack for when you land on an enemy? One way to implement that would be to split the “Jumping” state into a “Jumping” state and a “Falling” state. The falling state could be triggered any time the player is falling… so a transition from Jumping, Walking, or maybe even Idle.

From that state, you could have a “StartAttack” trigger on the Falling state that transitions to a new “JumpAttack” state.

Really the sky is the limit for how this stuff gets wired together.

Let me know if you have any questions.

P.S.: If you found this super useful and want to buy me a donut then I’m on patreon Paul Speed is creating Free Open Source Video Game Tech | Patreon
Or if you just want to show support for free then you can follow me on twitter and beef up my follower count: https://twitter.com/Simsilica/ :slight_smile:
Or subscribe to my youtube channel: https://www.youtube.com/c/PaulSpeed42/featured
Note: i’ve considered live-streaming these kinds of discussions but I kind of think they work better packed up with text and diagrams. Though this did take 1-2 hours out of my normal Mythruna development time. I hope you guys find it useful.

Edit: by the way, the diagrams were created with https://app.diagrams.net
…which is a super-cool free online diagram tool. I use it tons.

4 Likes

some heroes run towards danger :sunglasses:

jk and yes these make more sense to me initially then ECS. I think in my head without much terminology I was thinking of organizing this way so it’s very helpful to see it laid out and also some of the things involved as well as comparing and thinking about what the alternative would be.

tyvm for sharing

1 Like

hi pspeed ,
After the professional translation and reading of the article, I would like to ask some questions

  • Why use Runnable instead of Enum

  • These state are affected by player input(Instead of passing from one state to the next)

  • Thanks for your help and a great tutorial, can I forward this article to my website?

Because you need to put code there not just a value. The state transitions need to do something.

Yes, like in the example. States move from player input.

I guess so.

1 Like