home > brixx

Warning: Missing argument 2 for tab(), called in /customers/6/6/0/johno.se/httpd.www/software/brixx.php on line 31 and defined in /customers/6/6/0/johno.se/httpd.www/functions.php on line 3    Warning: Missing argument 2 for tab(), called in /customers/6/6/0/johno.se/httpd.www/software/brixx.php on line 32 and defined in /customers/6/6/0/johno.se/httpd.www/functions.php on line 3    Warning: Missing argument 2 for tab(), called in /customers/6/6/0/johno.se/httpd.www/software/brixx.php on line 33 and defined in /customers/6/6/0/johno.se/httpd.www/functions.php on line 3    Warning: Missing argument 2 for tab(), called in /customers/6/6/0/johno.se/httpd.www/software/brixx.php on line 34 and defined in /customers/6/6/0/johno.se/httpd.www/functions.php on line 3    Warning: Missing argument 2 for tab(), called in /customers/6/6/0/johno.se/httpd.www/software/brixx.php on line 35 and defined in /customers/6/6/0/johno.se/httpd.www/functions.php on line 3    Warning: Missing argument 2 for tab(), called in /customers/6/6/0/johno.se/httpd.www/software/brixx.php on line 36 and defined in /customers/6/6/0/johno.se/httpd.www/functions.php on line 3   

Brixx - MVC reference game

Background

During lots of architectural discussions over at the Spell of Play forums, I promised that I'd at some point write a reference implementation of my flavor of Model/View/Controller as pertains to games.

I decided to do a simple variant on the classic brick breaking theme, called Brixx, including a simple level editor to illustrate how to implement multiple controllers.

Writing about the stuff is all fine, but nothing speaks as explicitly as source code, so here is the source, released under the GNU Public License (v3.0).

Typical MVC Implementation

My typical style is as follows:

//typical mainloop for a johno game
void mainloop()
{
	//react to (polled) user input and call the various gui/widget methods
	myController.doInput();

	//update game logic
	myModel.update();

	//"program the view" by rendering 2d and 3d stuff
	myController.doOutput();

	//"flush the caches", including any gui stuff
	myView.draw();
}

What this typically means is that there is a stateful Gui instance in the View that caches text strings and rectangles (mostly) between the calls to Controller::doInput() and View::draw(). The reactions to button clicks etc all happen directly (in Controller::doInput()), but the side effect is the short lived cache of "rendering primitives". The same goes for things like vertex and index buffers for "dynamic meshes", things like particles, procedural geometry, etc.

The main reason for complicating things like this is details of the underlying rendering API, which is Direct3D9 in most cases. As it pays to optimized the number of draw calls, it is often helpful to have relatively high-level methods in View that Controller calls during both doInput() and doOutput() which handle the sorting of all of this into vertex buffers and whatnot. In View::draw(), these short lived caches are rendered and flushed.

In effect, View is a scene graph of sorts that only lives during a single frame, without any frame-to-frame coherency at all. Again, this is just in order to present a simpler and more flexible interface to any Controller(s).

Brixx Single Pass Implementation

Brixx turned out to be a good learning experience for me, as I decided to go with a single pass IMGUI in order to avoid the complexity described above (caches and stuff...). This is a new style for me, and I found it to have both pros and cons, but here's the gist of it:

//Brixx mainloop
void mainloop()
{
	//update game logic
	myModel.update();

	//do both input and output (drawing)
	myView.draw(myController);
}

Pros

First of all, since I am using GDI+ to draw stuff, there is no inherent penalty to "having lots of drawing calls". This means I can go ahead and have a View with minimal state, which means less code. The various widget methods are basically stateless function calls that draw something directly to the drawing canvas, poll some input state and return (see the implementation of the Canvas class).

The only thing in View that needs to be stateful is the input sampling stuff (State class and the encapsulated KeyState instances), but that is really an artifact of my implementation. I'm sure it would be completely possible to get away with all the View stuff being a bunch of functions (and not a class).

Cons

The downside is pretty much purely related to Win32 itself. There may be some better way of doing all of this, but I went with triggering WM_PAINT events (via ::InvalidateRect(), see View::draw()). In order to be able to handle the resulting messages, I created a callback interface (IDraw) that MetaController implements, passing control to the current IController.

What's wrong with that? Well, having callback interface like this paints you into a corner when it comes method signatures. I didn't want to explicitly bind all the "main view stuff" (in the view/base folder in the Visual C++ Solution Explorer) to Brixx itself; I wanted to allow people to be able to re-use this stuff for other GDI+ projects if they so desire.

This means I can't have an IEventTarget in IDraw::draw() since this is application specific, and since IDraw::draw() basically replaces my typical IController::doInput() AND IController::doOutput() I'm kind of stuck. My solution here was to just pass any required references to IController's constructor; this is indeed a pretty standard trick.

The thing I don't like, and I admit this is a digression, is that it isn't possible to swap IEventTarget implementations at runtime. This is useful in games where you want to support both local / single-player and remote / multi-player in the same architecture with minimal pain and reallocation of objects. Not really an issue here, hence my choices.

Miscellaneous Details

I hope that most of the code is pretty much self explanatory. Interfacing with the operating system / all the input and output stuff is always messy, and tends to be to most complicated part of any game. I hope I've done a good job (in your opinion) of keeping that as clean as possible.

Traversable

The Traversable class is a pattern that I tend to use a lot; basically it is about relational theory, with the analogies being that a table is a class, and a row is an instance or object. By subclassing Traversable you get a global list of instances with automatic linkage and traversal. This tends to really simplify architectures; my reasoning is that whenever there is a class where the number of instances is variable, I usually use Traversable.

Traversable has the downside of allowing global read and write access to the class in question. This is kind of bad in the case of the Ball class, as it is supposed to be part of the Model; hence you need discipline. The purist approach would be to have a container of Balls in Model with a const accessor, so that IControllers can't mess things up.

By the way, I could of course have had a single Ball instance as a member of Model, but I had been messing around with having multiple balls in the game and just decided to leave that stuff in there. To try it out, just allocate more Ball instances in Model's constructor (or anywhere you wish!)

IEventTarget formalism

For those of you who are new to the idea of IEventTarget and the constant reference to Model, the idea is basically this: "reads are free, writes are formalized". This adds a modicum of order and control to the architecture, where state changing operations that are "important" end up as part of the the "event interface" (IEventTarget) of the application.

Model implements this interface, as it is the owner of the application state, and typically it also holds a reference to a "receiver" of events that gets called back in each case. This is usally the part IController that needs to be able to play sounds, clear particle systems, etc (in this case PlayController) in response to "important state changing events".

The original intent of this formalization of writes was to enable remote-proxies; in multiplayer games the IEventTarget implementation that IControllers talked to was an object that sent the method calls across a network link to the "real" Model. Clients would still read from a local Model instance, but this object could be transparently updated with state from the remote peer / server without any of the dependent parties (Controller or View) ever knowing about it.

All kinds of input buffering, prediction, interpolation, etc, can be implemented completely outside of the scope of any Models, Views, and Controllers - these kinds of architectures offer true network transparency, really good stuff.

 home > brixx
 contact: johno(at)johno(dot)se