Saturday, April 17, 2010

Introduction to magecrawl's architecture

So magecrawl is broken up into three libraries and an executable that pulls everything togeather and provides "the game". Since I have a few minute between yard work excursions, it seemed like a good idea to write a quick introduction.

Utilities - This is the base library that the other three components depend upon. It provides a few common data structures that c# doesn't (Pair, Point), a few extension methods on basic types, and a Preferences infrastructure for reading config files. Mostly stuff that needs to go somewhere everyone depends upon.

GameEngine - This is the library that "runs" the game world. It provides an interface IGameEngine which other modules use to request data to display and input player actions. Right now, it handles everything not GUI related almost: level generation, save/load, monster AI, physics, etc. It provides interfaces like ISpell, IItem, ICharacter and such so the GUI doesn't have to depend on concrete implementation classes.

GameUI - This library handles displaying everything. From dialogs like inventory and spell listings, to the map and character info, there should be no other place that any drawing code goes (Exception, the title screen goes elsewhere).

Magecrawl - This is the executable that the user runs. It sets up the libtcod console and displays the "welcome window". After a game is started, it then creates a GameEngine instance and runs the game's main loop. There are multiple keyboard handlers, which switch off responsibility depending on the state of the game. They handle keyboard keystrokes, creating requests to the GUI to display various dialogs, and sending various action requests to the game engine.

One area I'd like to talk about is handling "psydo-asynchronous" events. Things like monsters using ranged attacks between player's turn. By default, after the player does an action that causes time to step forward, all the monsters to act before the player gets anther turn happen consecutively, without UI updates. This keeps the action going. However, seeing ranged attacks is really nice. They way we handle this is via a callback.

The main magecrawl event loop registers a few events delegates with the game engine, to be called when the player dies/wins or an event that needs to be drawn happens on a non-player turn. When the game engine runs across one of these, it calls this callback, which then draws the animation and return. It looks something like this.

Main Event Loop -> Handle Keystroke -> Game Engine Do Action - > Game Engine Pass Time -> Monsters Act -> Physics Engine Display Ranged Attack -> Main Event Loop Handle Animation.

After the animation is drawn, we can return back to the other monster's action, and back all the way to the handle keystroke and main event loop.

This is done so there is only one place we "pump messages", in terms of the draw/flush loop. We also don't have the game engine trying to either draw updates or call Magecrawl functions, causing a circular loop.

I am very happy with this design in general. The GameUI/GameEngine divide provides a strong UI/logic separation, and the Magecrawl executable is the only person running event loops. Previous attempts at writing magecrawl have floundered due to event loop or ui/logic problems.

I threw a bunch of ideas down in the space of a page of text, so I'm sure i glossed over a bunch of details. Let me know if there's parts that weren't clear.

No comments: