On the Specification of State Machines

Good Morning from my Robotics Lab! This is Shadow_8472 with a smaller project segment for the week. Let’s get started!

I’m learning Godot, a free/open source game engine. My chosen project is a Sonic fan game where Cream the Rabbit overhauls a wild chao garden overrun with pollution. Previously, I coded up a cube I could move around in 3D.

What I didn’t cover was how everything flopped when I introduced delta, a variable to adjust calculations to account for the time between frames. While I dream of posting this game as Wii homebrew (3rd party shareware), I understand that most people interested in Chao these days are on PC. Even if this were an exclusive title to a locked-down platform, lag would still need to account for framerate drops. I tried adjusting my jump related constants so many times, but since I missed a delta or two, I was never going to get it right. When I initially gave up, I directed my efforts towards designing a state machine.

It’s almost silly how many states a 3D platformer player character can be in. Walking, running, sprinting, standing still, holding an item – and that doesn’t even touch the number of jump states I can expect Cream or other characters to find themselves in. I used a massive if-else structure to decide what string to describe state. At a certain point, I studied the character movement and animations of Sonc 3 & Knuckles (Sonic Megacollection for GameCube, run on a Wii) and Sonic Adventure 2: Battle (SA2B) as inspiration. In both games, holding jump powers you upwards until you either release or reach a maximum height; it just happens so fast (unless you’re in a low gravity section) that it feels natural. I had to re-think my state machine a few times, but I settled on a 32 bit number to chop up into a mixture of 1 bit flags and other data as needed. In particular, I focused on the least significant byte (the last 8 bits).

I went through several iterations over a couple of days, but the two rightmost bits were consistently used to denote a character’s vertical (Y) and horizontal (XZ) movements as I figured they would be most useful for telling other aspects about a state. Moving left, the next four bits are for progressively more detailed information about what a character is doing “environment” – you can be walking.on_ground.skidding, swimming_underwater.bottom_walking.normally, or using a character’s special move assigned to a reserved set of states.

A good chunk of thought went into representing all these states in a visually digestible format. It wasn’t until I began thinking of the XYZ motion flags as dependent on my environment states that I reduced them to four columns in a chart of sixteen rows that I was readily able to count my vacant states. As of writing, my environment variable uses two bits for “rough” and one each for “fine” and “special.”

The 128’s and 64’s bits are penciled in as a counter for idle animations or how many double jumps a flying character has remaining. Beyond that, I have 24 bits remaining as the game grows. For example: eight bits affords up to 255 unique held items plus a reserved id for when a character holds nothing.

Takeaway

I did not write a single line of code while working on the main event today despite it being a programming project. I did start on the next part where I began implementing this standard, but that felt like I was starting a whole new section, so I’m saving it for later.

Programming programs and composing literature (in the broadest sense) are more similar than people realize. What happened here today is analogous to a novelist making an outline ahead of a first draft. An important puzzle was solved and in important internal standard established.

Final Question

How many basic states are there for an average character in your favorite game?

Leave a Reply