Thanks to @GameEndeavor recently for linking me to this tutorial of his on state machines, I learned how useful they are for implementing player and enemy behaviors. While the way I’ve been working with them recently doesn’t exactly match how the video does it, the idea behind simple state machines is really helpful.
One way of setting up a state machine involves making a general StateMachine script to be inherited from by the different objects that will use it. These objects, like the player or enemies, will have a generic Node with their own state machine script attached to it, like so:
The general StateMachine script has some functions to override, a _physics_process() method that calls the logic handling method and the state transitions method, and a set_state() function for actually switching between different states. The set_state() function should not be overridden in child scripts.
Whenever set_state() is called, both _exit_state() and _enter_state() are called, so any logic that should happen when switching states should be put in these two methods. The _state_logic() method should handle any state-specific logic that does not involve switching states (ex: chasing the player if in an aggressive state, or wandering if in an idle state), and the _state_transition() method handles any logic that does involve switching states. While you could group these two functions into one, it’s a bit cleaner and easier to read having two separate functions like this.
Here’s an example state machine for a generic enemy that chases you when you get too close, and is idle when you run away far enough.
The enemy starts in the IDLE state, and _state_logic() dictates how the enemy should behave depending on which state it’s currently in. The _state_transition() method is responsible for checking when the enemy should switch between IDLE and CHASING; in this case it’s based on the distance to the player. Finally, _enter_state() will run any code that should execute when switching to a new state. So for example, maybe the enemy plays an animation when it starts to chase the player, or maybe a question mark sprite should popup when it goes back to IDLE.
State machines are overall really helpful to have. I’ve written messy player and enemy code so many times before in game jams, so I was never really exposed to a more structured, organized way of implementing states. I’m glad I’ve picked up this new concept now.