Sunday, August 23, 2009

Doing it by hand

Jetblade's big shtick, of course, is procedural content generation. But that's no reason to keep people from being able to make their own maps. Once the game is complete, you should be able to make your own fully-fledged world for other players to explore. And an integral part of being able to do that is the addition of a map editor.



I won't bore you with a guide to how to use the editor, because the new in-game console (courtesy of Pygame-Console) contains an overview:



In contrast to last update's major refactoring of the TerrestrialObject code, which involved a lot of work and didn't create any particularly visible changes, this time around we have many very visible changes without very much work. The actual modification of a map while the game is running just amounts to a couple of very simple function calls, so most of the work in making the map editor was at the interface level. The input processing system, for example, got refactored and is now quite easy to work with (not that it was used at all before now; previously it'd been a holdover from a previous project). All that remained was finding Pygame-Console and integrating it into Jetblade, which wasn't very hard at all.



Of course, the map editor still has a long way to go. You can't place background props or environmental effects with it yet, and there are plenty of planned game features that need to be added that it will of course have to take into account, like enemy placement, powerups, scripted events, and so on. However, the groundwork has been placed.

Friday, August 21, 2009

The FSM isn't just a spaghetti monster

I just pushed 19 changes to the Jetblade code repository. And yet the only significant difference you'll see is that there's a splash screen when you start the game up:



Now, given how long startup can take, this is an important change: you'll have something to contemplate while you wait for Cython modules to be compiled and a map to be generated; you'll be able to wonder just why our armored hero is cowering at the approach of a red four-winged flying narwhal. But it doesn't warrant 19 changes. No, most of what's changed lately is behind the scenes. I've fired the Refactoring Cannon at the code.

If you'd looked at the TerrestrialObject class before, you would have seen that it was a mess. It had a whole tangle of booleans to control what it was doing and how it could move: isGrounded, wasGrounded, justJumped, isHanging, justStoppedHanging, justStartedClimbing, shouldCrawl, isCrawling, wasCrawling, haveChangedAction. And of course, dealing with these inputs required a large tangle of if-then-else statements. You don't need Goto to make spaghetti code, and TerrestrialObject is the proof.

Fortunately, the Flying Spaghetti Monster handed me a solution to my spaghetti code in the form of a Finite State Machine. A FSM describes the behavior of some device, or creature, or program, as a set of states and transitions between states. It's a bit like a flowchart. For example, the player starts out in the Grounded state, which means that they're standing on the ground. While in this state, they can run around, fall off of ledges, jump into the air, and crawl around. Each of these is a transition to a new state. In Jetblade's case, each state is its own class, implementing the interface defined in the ObjectState class, and the states between them describe all the behaviors of a TerrestrialObject.

Now, strictly speaking, TerrestrialObject was already a FSM before the refactoring. All programs are FSMs, after all. However, formalizing the code into an explicit FSM greatly improves its cleanliness. All of those booleans I mentioned before? Gone, or else hidden within a specific state, so that the others need not worry about it. TerrestrialObject used to be 455 lines long; it's now 184 lines. And half of that is documentation, whitespace, and default values.

As a side-effect, now that transitions between states are explicit, several bits of logic that had been causing a lot of weird glitches (specifically, forcing crawls when there's no room to stand, or forcing standing when there's no room to crawl) are now much simpler and less error-prone. I won't claim that they're bug free; after all, there's always one more bug. However, debugging them just got a heck of a lot easier.

If you find yourself dreading working with a piece of code, it's probably time to refactor it. TerrestrialObject is now refactored, and quite frankly it's a joy to work with.