Model-View-Controller Framework

Download the Framework

Rather than including all the sample code here, this discussion refers to the MVC framework which you can download and use for your own projects.

The framework contains the scene loader, scene scripting engine and managers, lots of utility functions, some handy views and controllers (like a skybox view and a "display an image and wait" controller), and a sample scene to get you started.

Version History

  • 6/22/09 - v2.4
    Released under the MIT license. Added license language to the code.
  • 6/11/09 - v2.3
    Removed 'confirm' from demo scene so it works first time.
  • 6/11/09 - v2.2
    Models now receive the act() message each frame.
    Fixed bug that makes demo scene not work.
    info() gives more information, and more complete information without parameters.
    Fixed thumbnailer to include headings on member thumbnails.
    Added floor() and ceil() math functions.
    Added event() and replaceController() to the Controllers object. The Controllers object is now implemented a little differently, but the API should be the same.
    Added centerImage() imaging lingo routine.
  • 10/5/07 - v2.1
    Fixes memory leak in billboards.
    Fixes last item in a scene not receiving isReady() messages.
    Adds getFlashImage() to retrieve Flash images without making them unreclaimable.
    Deprecated Soundtrack object, replaced with Flash-based DeeJay object for better sound control.
    Now hides loading messages by default (hold down command to show).
    Improvements to gamestate object to facilitate saving and loading games.
  • 6/27/07 - v2.0
    Fixes thumbnailer code, and adds support for downloading scenes and scene content.
  • 2/20/07 - Added updated thumbnailer code. v1.2
  • 12/19/06 - Changed some documentation, made the "game state" object a model instead of a manager. v1.1
  • 11/27/06 - Original release, v1.0

After working for a while in Shockwave3D, I've come up with a pretty solid and flexible framework for presenting and loading Shockwave3D projects.

It uses the Model-View-Controller paradigm for organizing code. In essence, you split most of your code up between one of three types of objects:

Model
Model Objects store the raw data that represents what you are simulating. For instance, in a game, it might manage where the player is, what weapons he has available, etc. There is no display component, nor does it interact with the player at all. Instead, it has functions for changing the state of the model. Models get messages when something interesting happens in the game world, either from player interaction, or from another model doing something.
View
View Objects are responsible for displaying things. They are "dumb" objects in the sense that they really don't know anything about the game, the game world, etc. All they know how to do is display something in the Shockwave3D world. Views get a message every frame to prompt them to update themselves. Typically, they will get direction on how and where to display themselves from one of two places: either they will be given a model object reference, and just query that to display its state, or they will be sent messages by a controller object.
Controller
Controller Objects are responsible for interacting with the player. This is where the interaction logic is placed, and where the logic that ties the model to the view is defined. In my setup, there is only one active controller at a time (but you can make a "master" controller that calls sub-controllers if you need more). The controller gets a message every frame to prompt it to interact with the player.

Here's an example of how the MVC paradigm might work for dealing with a game avatar:

Model: Avatar
The avatar's model contains its game-world position, rotation, velocity, and health. It manages the game-world logic for how not to run into walls, shooting at enemies, etc. It is ignorant of both its display and how input is coming in.
View: Avatar
The avatar's view object manages the 3D model of the avatar in the 3D world. Every frame, it queries the model object for its current position and rotation. It is ignorant of how the avatar is being controlled, or its game-world logic.
View: Heads-Up Display
A HUD is displayed over the game screen with a health bar in it. The health bar queries the model object for the avatar's current health level.
Controller: Gameplay
Every frame, the controller checks the player input and sees how the Avatar should be moved. It sends messages to the avatar model to update this. It also tells enemy model objects to do their thing. If they successfully attack the player, they send a message to the Avatar model to update its health, which will be reflected in the heads-up display when it gets rendered. The controller is ignorant of how the avatar models work and how they are displayed; it just acts as a go-between for the models, views, and the player input.

Note that multiple views can grab information from a single model object, and that you could swap in different views without changing the gameplay. For instance, if you wanted to change from a 3D to a 2D game, you could just write new view objects, and the game logic would stay the same, and if you wanted to have a tutorial or demo playing, you would swap in a controller that reads input from a script instead of the keyboard.

Ideally, each project object will fall cleanly into one of these three categories. However, in the real world, you may end up fuzzing the lines a bit in order to meet deadlines or conserve CPU cycles. But the more cleanly you place your code into one of these three categories, the easier things will be on you in the long run.

Scene Model for Partitioning a Project

I partition up a project into Scenes, which can be thought of as discrete Shockwave3D environments that can be experienced in the project. For instance, in a game, these would be "levels" or "battles" or "cutscenes".

The Scene is the highest-level unit for the project. A skeletal scene loader is used to start up a new Models manager, a new Views manager, and a new Controllers manager, essentially clearing the game world and starting fresh with a new world with new elements.

To do this, I use the Simple Scripting with Explode technique to read the scene's information out of a text member. This text member describes to the scene loader what models, views, and controllers are needed.

Here's an example Scene script:

Although you may not know already what all these things mean, it should be clear that you can add in and remove different aspects of a level pretty easily once you have this setup in place.

For instance, a skybox is being added to the game world as a view object. If I want to remove the skybox, I just comment out that line, or remove it. If I want to change which graphics it uses or which camera it will get its rotation information from, I just change the parameters.

Here's a list of what each keyword in the Scene loading script does:

Comments
In any given line, as soon as it hits a lingo-style comment, namely "--", it ignores everything to the end of the line. This allows for nice visual separators like you see in the code above.
Echo
Echo generates a debugging message which gets displayed in the console during development. Once the game ships, you can have it ignore these lines, or leave them in as feedback for the player if you write them correctly.
World
Tells the scene which Shockwave3D member to use as the base world for this scene. Typically, this will be a .w3d member that has most of the scene-specific models in it, which is consistent with the idea of scenes as game levels. However, it could also be a blank shockwave3D member, into which models are loaded dynamically.
Clear
Clears the cameras for the Shockwave3D member. See my article on using multiple cameras for more information.
Model, View
This loads and initializes a model or a view. The first argument is the label the model or view will be known as, and the second argument is the name of the script member to load. Any remaining arguments are passed in as a linear list to new() in the script. Labels are converted to symbols, and script names are prepended with the type of object being loaded: "Model:" or "View:". (Note that naming scripts this way makes it easy to visually distinguish them using code thumbnailing.)
Controller
This pushes a controller onto the interface stack. The first argument is the name of the controller to load, and any remaining arguments are passed as a linear array into new() in the controller script. As with models and views, the type of object being loaded is prepended to get the script name, i.e., "Controller:".

Pretty simple, eh? There are a few more commands that are available, such as registering interpreters so that you can add new commands to the script, but for the most part, these can get you through 99% of potential game worlds easily.

Managing Scene Loading

One difficulty with Shockwave3D is that it sometimes takes a few moments for all of the Shockwave3D content to "get settled" so that you can work with it.

For this reason, every element that can get added to the scene has an opportunity to hold off until it is ready before the scene loading script proceeds.

In the case of the Shockwave3D world, it waits until the member is fully loaded.

In the case of Managers, Models, Views, and Controllers, the scene loader calls an isReady() function each frame, and holds off moving to the next frame if it gets back a string (which it interprets as an error message to display in the load log) or the symbol #fail (which it interprets as a generic error). If it receives anything else, it assumes that the object is fully initialized and moves on to the next item.

Most simple objects will be able to just do their initialization in just one call of the isReady() function, but if you're doing something long, or which requires you to wait for media to get ready, such as waiting for another Shockwave3D member to be loaded so that you can clone models out of it, the isReady() convention can allow you to break up your initialization task in a more manageable and frame-friendly way.

The Model Manager

The Model Manager is actually quite simple. All it does is act as a repository for the model objects, so that any object can get a handle on any model object.

Essentially, the Scene Loader adds models to the Model Manager's "pool" of models as it loads, associating each model object with a symbol identifier. Once the scene is running, you can get a reference to that loaded model by calling:

gManagers.models.getModel( #identifier )

The only other thing it does is provide a handy way to notify all models that the scene is ending.

The View Manager

The View Manager is very similar to the Model Manager, except that it works with views instead of models. The main difference between them is that the View Manager provides the means by which you render the view. It does this by calling render() in all the views registered with it.

The Controller Manager

The Controller Manager is different from the Model and View Managers. This is because only one controller is active at a time, and controllers are kept in a stack so they may be pushed onto and popped off of the stack.

This is mainly because there are many situations where it is good to temporarily take over the interaction with the user, but not in a destructive way. For instance, you might have a controller for an options window, which pushes a controller for a drop-down menu onto the stack. When the user is done selecting the drop-down, the drop-down controller just pops itself off of the stack, and the options window regains control.

The controller manager also provides some nice utility functions for controllers so that you can easily add code for responding to state changes. When a controller object gains control, it receives the focus() event. The first parameter to this is #push or #pop, depending on whether it gained focus as a result of a push or a pop. Similarly, when a controller object loses control, it receives the blur() event, again with a first parameter of #push or #pop.

The Main Game Loop

Once everything is loaded up, the Scene Loader passes control over to the main game loop. It's a simple frame script that just loops in place, letting the current controller interact with the player and the current views render themselves each frame. That's it! Everything else is handled by the models, views, and controllers.

Using the MVC Framework

It is recommended that you leave the MVC code in a cast by itself, and then use other casts for your specific use. That way, when an upgrade to the MVC framework comes out, you can just download the new version and add your casts to it to take advantage of the new features and bug fixes.

License?

The MVC framework is distributed under the MIT Open Source license.

Final Notes

Using this model, I've been able to architect nearly all cases needed for our Shockwave3D development. About the only thing it won't handle is a context where you cannot split your game or project up into discrete scenes. (And even then, this model could drive a single, very large scene that encompasses the entire project.)

Made on Mac