Tuesday, July 3, 2012

EPI #9: States

After yesterday's whirlwind of coding and tangible results, I started out this morning with the intention of adding a little man to my map and having him move around in response to keyboard input.  From a purely technical point of view, this is not a hard thing to do.  However, a little bit of thinking made two things clear to me: 1) When I write a particular component of the engine, I need to do it in the context of the entire engine; and 2) this can be very hard to do.  When I skipped steps in my flowchart yesterday, I did myself a bit of a disservice, because now I need to modify code I've already written in order to un-skip those steps.  There's a trade-off at work here.  On the one hand, it's important for me to be able to generalize my way past things I don't want or need to implement eo ipso tempore, but on the other hand, I can't just ignore inconvenient stuff.

So I decided to warm up by taking care of a smaller detail I mentioned yesterday: handling window resizes.  A little bit of Googling revealed my main gtk.Window receives a check_resize event every time it's resized.  I attached a handler to this to update the MapWidget's geometry and tell it to redraw itself, and it worked.  Immediately afterward, I realized that the an expose_event was being received by the MapWidget every time I resized the gtk.Window.  So I changed my approach and inserted logic to update the MapWidget's geometry to match that of its parent gtk.Window every time it tries to redraw itself.  This turns out to be a much more elegant solution than attaching a handler to the gtk.Window.  I'd put of a video of me resizing windows like crazy and everything being drawn correctly, but it's actually not that exciting.

Having warmed up, I thought some more about what needs to happen before I'd be able to draw a map with a man on it to the screen.  Basically, before anything gets drawn, the MGEET needs to determine the game state.  What do I mean by this?  I'll explain with an example.  Think back to Pokemon Red (or Blue, or Yellow, or Green, if you're Japanese).  In Pewter City, if you try to advance to route 3 (the approach to Mt. Moon) before defeating Brock, there's a trainer who blocks your way.  After you defeat Brock, when you get to route 3, the trainer has mysteriously moved, and you are free to continue your quest to catch 'em all.  What's happening behind the scenes here is pretty straightforward, but I'll explain it anyways:
  • The trainer has a state (probably just a number) associated with him.
  • When you enter Pewter City, he's in a particular state.  Let's call it state '1'.  For as long as he's in state 1, he stands in a certain place (exactly in your way), and speaks certain dialog when you talk to him.
  • When you defeat Brock, his state changes to a new state (we'll call it '2').  In state 2, he has a different set of attributes-- he stands in a different place and says different things when you talk to him.
When you save your game after beating Brock (because he's the first boss!  That was hard!  You don't want to have to do that again!), the game records Brock's defeat, as well as our trainer friend's new state, so that when you return to Kanto the next morning, your progress is exactly as you remember it.  Now, I don't know that this is exactly how GameFreak implemented Pokemon, but it's about as reasonable a guess as anyone could make.

Returning to Prospero's Island, the game state is the collection of the states of every single stateful thing in the game.  For my purposes in EPI, I define a MapEntity as a thing with which the player can interact.  Since players can interact with them, every MapEntity must have behavior associated with at least one state.  MapEntity state changes happen as a result of player actions and manifest themselves as changes in the game world.  NPCs, treasure chests, and wall switches are all examples of potential MapEntitys.

The reason, then, that I need to know the game state before I try to draw anything is because the game state carries with it almost all of the information I need to draw things.

But how can I know the game state without knowing about all the MapEntitys in the game?  I can't.  So I thought harder about how scenarios need to be structured.  Here's what I decided on:
  • global.xml contains a section for defaults and a section for settings.  Defaults contain paths to XML specifications for the bestiary, the master item list, the default map to draw, and the master entity list.
  • Settings affect the way the MGEET conducts its business behind the scenes.  A good example might be the damage formula, which is used to calculate the amount of damage something receives after it suffers an attack.
  • The bestiary, master item list, and master entity list contain specifications for every monster, item, and MapEntity, respectively, in the game.  Each one of these carries a unique numeric ID, by which other resources (such as maps) identify them.
With all that straightened out, then, it's fairly straightforward to create a game state.  Basically, I'll define a function init_game(), which, in exchange for certain arguments (the main window, a master dictionary entities, a dictionary of scenario-specific settings, and optionally any relevant save data), initializes the game state and draws the necessary map to the screen.

So maybe later tonight but probably tomorrow I'll finalize how I want to represent MapEntitys internally, and implement init_game().  Once that's done, I should be able to draw a little man on the screen and move around him around with the arrow keys.



Bonus:  I did actually draw the little man today.  His name is Kenfold:


He's blurry because he's only 32x32.

No comments:

Post a Comment