Finite State Machine Platformer

gazugafan

  • Posts: 4
Have you ever had trouble managing how your character can transition into different states? Can a ducking character wall jump? Can a jumping character duck? If you've been tackling these problems with lots of flags and if statements, you might want to give finite state machines a look!

It sounds complicated, but a finite state machine is basically just an object that can be in a single state at a time... like jumping, falling, walking, etc. As the object transitions between states, enter and exit state events are triggered. When the jumping state is entered, for example, you would probably want to start the jumping animation and play a jump sound effect. You could even have an object with multiple state machines. Maybe another state machine for whether you're holding an object, throwing an object, or without an object? Maybe another state machine for who is controlling the character--a script, AI, or the user?

In this implementation, you simply attach the "State Machine" behavior to an actor, and specify a list of state machines that the actor will have, along with a list of the different states each machine can be in. For example, the "Movement" state machine might be able to be in an Idle, Walking, Jumping, Falling, or Ducking state.

Each state corresponds to a behavior.  If you list "Walking" as a possible Movement state on your character actor, for example, your character actor must also have a corresponding "Walking" behavior. All the listed states (behaviors) are disabled by default, except for the first one listed. This is the default starting state. The order you list the states is otherwise unimportant.

From here, you can call a few different methods on the State Machine behavior to change what state the machine is in--thereby activating the new active state (behavior) and disabling the previous one. Only one state (behavior) will ever be active at a time for each state machine. The methods for changing state are...

Change State: Replaces the current state of a machine with a new one.
Add State: Adds a new state to the top of a machine's state stack--changing to the new state.
Remove State: Removes the state from the top of a machine's state stack--restoring the previous state.
Reset State: Clears a machine's state stack--resetting the machine back to its default state.

Whenever a state change occurs, an "ExitState" event is triggered on the previous behavior (the one that's about to be disabled), and an "EnterState" event is triggered on the new behavior (the one that's about to be enabled). You can handle these events in your state behaviors to play animations and other setup/teardown procedures.

The attached example is a complete rework of the original Jump and Run Kit. It comes with a few other noteworthy things...
An animation queue allows you to easily queue up animations. For example, when a character lands on the ground, you could queue up a landing animation that plays once, followed by the idle animation.
A control translator that allows control of an actor to be switched (from user input, to a script, or AI, etc) at runtime.

It might look a little complicated at first glance, but once you wrap your head around it I think you'll find it's a much more manageable structure for large projects. The project I wrote this for is stagnant now, but hopefully someone else will find it useful!


LIBERADO

  • *
  • Posts: 2720
After analyzing it, I can see all the meticulous and ingenious work that you have done. I appreciate all the effort you have made in creating this well-crafted "State Machine" behavior so practical, versatile and useful.

Thanks a lot for sharing it.
I'm spanish, excuse me for my bad English.
I'm not a private teacher. Please, post your questions in the public forum.

gazugafan

  • Posts: 4
Appreciate the kind words, @liberado. Glad you like it!

Apamaster

  • Posts: 129

Good job, but the manual to understand this example from where I download it? : P jaja

Apamaster

  • Posts: 129
I am looking at the code of the finite state machine, and I honestly do not understand it. Because having so many states obviously needs more code. and as you have created special custom blocks with different characteristics it becomes more difficult to decipher the code. But I still don't know where to start, for example when I press the right key I don't know where it detects it to change to the walking state. Would you have a smaller example to show?

gazugafan

  • Posts: 4
Ah, yeah I didn't go into much detail about the Control Translator behavior. It doesn't really have anything to do with the FSM, so don't feel like you need to use it. Here's how it works, though...

You setup controls in your game settings within Stencyl. These are like named actions that have keys assigned to them. So, we have the "Up" control assigned to the Up Arrow key, the "Action1" control assigned to the Z key, etc.

Normally, you'd just check to see if one of these controls is pressed in one of your behaviors, and then do something if it is. If we weren't using the Control Translator, that's exactly what we'd do. For example, in the Idle state/behavior, we want to be able to switch to the Jumping state/behavior when you press the "Action1" control. So, we'd add a "When Action1 Control is Pressed" event to the Idle behavior, and handle it by either adding or changing to the Jumping state (these are custom blocks available from the State Machine behavior). That would disable the Idle behavior, and enable the Jumping behavior. Simple!

But what if you want to be able to control the actor via something other than the keyboard or gamepad? Like, maybe some sort of cutscene behavior should control the actor? Maybe the actor gets taken over by a parasite and should be controlled via an AI behavior? Maybe the actor belongs to a networked player? That's where the Control Translator comes in. It adds an extra layer of abstraction between inputs (coming from a keyboard, script, network, etc) and actions ("up", "left", "action1", etc).

If you want to be able to control the actor via the "controls" like we did above, you would need to add a behavior that listens for all of the pressed/released control events, and then calls the corresponding blocks on the Control Translator behavior. This is exactly what the User Control behavior does. For example, in the User Control behavior, when the Right control is Pressed, we call the "Right Pressed" block on the Control Translator. When the Right control is Released, we call the "Right Released" block on the Control Translator, and so on. There are pressed/released blocks in the Control Translator for up, down, left, right, action1, and action2... the basic set that comes setup in Stencyl. I also added attributes to the behavior to allow you to pick which normal controls relate to which actions in the User Control, in case you wanted to get complicated and add User Control actions that don't relate directly to normal Controls (but that's not the case in the example).

When you call one of those blocks, the Control Translator then triggers an associated event for the actor, which your other behaviors can listen for. For example, when you call the "Right Pressed" block, the Control Translator triggers a "RightPressed" event. It also sets a "Move Right" actor value to true, which you can check anytime you want instead of (or in addition to) listening for the events. The Control Translator will make sure the following actor values are set to true/false correctly: "Move Right", "Move Up", "Move Down", "Move Left", "Action1", and "Action2". You don't need to use them, but they can be nice to have available. It'll even make sure that you can't simultaneously press up and down at the same time, if you want (that's the "Allow Opposites" setting).

So, to recap...
The User Control behavior listens for normal Control events, and calls their associated block in the Control Translator behavior. The Control Translator then "translates" those by triggering a corresponding actor event (and keeping those optional actor values updated correctly). If we were to modify the Idle behavior to use this system, all we would need to do is listen for the "Action1 Pressed" event that the Control Translator triggers, instead of the normal "When Action1 Control is Pressed" event.

Kind've a complicated setup I guess, but once you add the Control Translator behavior and add all the actions to the User Control behavior, everything else basically works like normal. You're just listening for the events that the Control Translator triggers instead of the normal Control events.

... but here's the cool part. Now we can call the Control Translator blocks from anywhere we want. So, instead of our User Control behavior triggering these button presses based on our real keyboard presses, we could have, say, an AI behavior that presses buttons and controls the actor. Then, just turn off the User Control  behavior and turn on the AI behavior, and our actor is being controlled by an AI script. The AI script is just triggering button presses, so there's nothing else that needs to be changed.

The Script Control behavior in my template is a super simple example of this. It just presses the Right button immediately, and then presses Action1 after .25 seconds, and then disables itself and enables the User Control behavior after 1 second--giving the player control. It's like a ridiculously simple cutscene. Your character jumps into the scene and then stands there for you to take control!

And how does it switch itself off and switch the User Control behavior on? Through a finite state machine! Again, it's not really directly related, but since States in the FSM relate to Behaviors, I thought it would be cool to show that you can switch the "Control" state of the actor between "User Control" and "Script Control". I also wanted to show that the State Machine behavior can actually contain multiple state machines if you want. So, the Jumper actor has a State Machine behavior with two state machines: "Movement" and "Control". The "Control" state machine has two possible states, "User Control" and "Script Control", which of course relate to the User Control behavior and Script Control behavior. The default state for the "Control" state machine is "Script Control". So, when the scene first starts, the Script Control behavior is active and the User Control behavior is disabled. The Script Control behavior makes the actor move right and jump by pushing the right button and action1 button (via the Control Translator blocks), and then switches the "Control" state to "User Control".

Apamaster

  • Posts: 129
Read everything one more time, and I will begin to try little by little to see how to put together two states and behave accordingly. When I achieve something I will publish it to see if it is the right path. thanks!!!