Return of the States... not the United States...


Announcement

I'm very happy to say that Diego Pontes is going to help me with SFX and Music and he'll be giving us, in the form of devlogs, right here, some insights into how he produces his awesome audio files! Very excited to work with him, he's the person that made the already awesome music for the GodotJam 2018 prototype.

States... not the United States

First, let's see where we are in terms of mechanics. The patterns are back! And this time better than ever. Alright... lame commercial :) followed by a lame state of affairs:

MunchIt Player Interaction

Fig. 1. It doesn't look like much but believe me stuff is happening! :) This here actually runs in a turn-based loop, it can be seen because there are no other entities around, other than the player, but everything for the turn-based system is in place and working! I'm also experimenting with keyboard interaction so that the player isn't forced to use the mouse, but with so many available movement positions it's tricky figuring out a good system. We'll definitely look at more of this in later devlogs.

The States ah... managing the States. It's a pain frankly, keeping track of everything that happens is pretty much a pain. Where to place code that manages state of certain Nodes or other instances of custom classes - it's not an easy task, that's why there's very highly payed positions for software engineering. And in a game engine such as Godot which gives you a lot of flexibility which simplifies the thinking process on the high level, there's still a problem of organizing the code. Where do you put the code that changes state for certain things? Because of interdependence it isn't an easy task.

Ah... interdependence. For this little game here, being turn based, FSM (finite state machines) seem to be a good fit. Now FSMs can get out of hand easily because there's no clean way of being in two or more states at the same time. Why might you want to be in multiple states simultaneously? Well because say you have a platformer character that can duck and move at the same time. The basic FSM for this kind of character might start with having the separate states:moving & ducking (among other ones). Now the problem is what happens if you want move while ducking? Not so easy to fix. What do you do? Do you create other states that reuse code from both moving and ducking or do we handle ducking behavior inside of moving directly? What would that mean for moving and jumping? So you can see, duplication of code and behavior with FSM is very easy to achieve. Not nice at all!

FSM in Godot

Still, FSM isn't completely useless, especially in a game that's turn based where you can only be in one state at a time. With that in mind, what I'd normally do is make a hierarchy of the sorts from Fig. 2.

FSM hierarchy in Godot

Fig. 2. FSM hierarchy in Godot. Check out the simple nodes: States, Idle, Moving, Gathering, etc.

This works and gives you some advantages. You can define (in attached scripts) properties on the nodes that are available for you to tweak in the inspector. And what I'd normally do is keep track of the "parent" by having something like onready var entity = $"../.." inside the states themselves in order to have easy access to the thing we want to attach behavior. This... turns out is messy.

Separation of concerns is not easy. For example the above states would not work on their own, they necessarily have to be attached to nodes in the specific format from Fig. 2. in order for them to do anything useful and not crash the game.

Introducing FSM 2.0

It turns out that for personal projects at least, I don't really need to change custom properties in the inspector, I'm more comfortable with tweaking the things from within the script and creating clever interdependence that goes one way in order for the bottom nodes (components) would depend on the parent and set appropriate properties on themselves if need be instead of me tweaking them manually. There's actually a strong case that properties you'd normally declare as constants are very good candidates for exposed variables in the inspector, because these are often the ones you need tweaking. Lars Kokemohr makes a good case for this.

Alright, back to figuring out this interdependence. I scratched the idea of having states as nodes and have them reference the entity (onready var entity = $"../..") and instead I'm trying a new approach with the states being custom classes and all their methods require the entity parameter to be passed in. At first I tried a similar approach as having the entity stored as a member variable and have the state constructor take this entity and set up properly the variable. This would be very similar to what's happening in Fig. 2. and here's my reasons why I decided against it:

  1. having the entity as a member variable inside the states would mean that there's extra... state to the state to keep track of. There's a couple of ways around this. For example you might want to create and destroy states from scratch on every transition between them. It's an OK method, but I think it's cleaner to just pass in the state which brings me to the second point
  2. being a turn based game it means that I can (and want to) reuse the same exact states where possible. For example, the AI brain (or controller) will actually be stored in the Idle state, that's where the decision is take to move or to pass the turn or any other behavior. Which means that for example, in the case of movement, we just need to pass in the appropriate info so that the Moving executes properly. The Moving state doesn't actually figure out the move, it just executes. So with this set-up, the Moving state can be identical for both AI and Player, the only difference is that the player controller is stored in a separate IdlePlayer state, while the AI has it's own IdleAI state, but the Moving state is identical and entirely reusable. So with passing in the entity instead of storing it in the state itself makes for a cleaner interface. At least that's what I think
  3. I have to admit that some of this thinking rubbed off from haskell and functional programming in general, that is minimizing state changing & tracking as much as possible

Next time

Next time we'll get into some debugging, a bit more on states probably and how I manage the very simple turn-based loop in a real time engine like Godot.

Get MunchIt

Leave a comment

Log in with itch.io to leave a comment.