Question

I'm having difficulty grasping how to structure/architect a canvas application using an MVC like approach in Javascript. UI will be fairly fluid and animated, the games fairly simplistic but with heavy emphasis on tweening and animation. I get how MVC works in principle but not in practice. I've googled the buggery out of this, read an awful lot, and am now as confused as I was when I started.

Some details about the application area:

  • multi screen game framework - multiple games will sit within this framework common UI "screens" include: settings, info, choose difficulty, main menu etc.
  • multiple input methods
  • common UI elements such as top menu bar on some screens
  • possibility of using different rendering methods (canvas/DOM/webGL)

At the moment I have an AppModel, AppController and AppView. From here I was planning to add each of the "screens" and attach it to the AppView. But what about things like the top menu bar, should they be another MVC triad? Where and how would I attach it without tightly coupling components?

Is it an accepted practice to have one MVC triad within another? i.e. can I add each "screen" to the AppView? Is "triad" even an accepted MVC term?!

My mind is melting under the options... I feel like I'm missing something fundamental here. I've got a solution already up and running without using an MVC approach, but have ended up with tightly coupled soup - logic and views and currently combined. The idea was to open it up and allow easier change of views (for e.g. swapping out a canvas view with a DOM based view).

Current libraries used: require.js, createJS, underscore, GSAP, hand rolled MVC implementation

Any pointers, examples etc., particularly with regards to the actual design of the thing and splitting the "screens" into proper M, V or C would be appreciated.

...or a more appropriate method other than MVC

[NB, if you've seen this question before it's because I asked it in 2 other incorrect stackexchange communities... my brain has stopped functioning]

Was it helpful?

Solution

MVC has been covered in so many places so there shouldn't be much to re-iterate here. Essentially you want your object graph, helpers, and logic to be contained in the model tier. The views will be the screens that get pushed out to fill the dynamic part of the page (and may contain a light amount of logic and helpers). And the controller, which be a lightweight implementation to serve the screens based on what was available from the object graphs, helpers, and logic.

Model

This should be where the meat of the application sits. It can be tiered out into a service layer, a logic layer, and an entity layer. What does this mean for your example?

Entity layer

This should house the definitions of your game's models and internal behaviors. For example, if you had a game for minesweeper, this would be where the board and square definitions were along with how they change their internal state.

function Location(x,y){
 this.x = x;
 this.y = y;
}
function MineTile(x,y){
 this.flagged = false;
 this.hasMine = false;
 this.pristine = true;
 this.location = new Location(x,y);
}
MineTile.prototype.expose = function(){
 if( this.hasMine ) return false;
 this.pristine = false;
 return this.location;
};

So the MineTile will know its internal state, such as if it is showing or was examined (this.pristine), if it was one of the tiles that has a mine (this.hasMine) but will not determine if it was supposed to have a mine. That will be up to the logic layer. (To go even further into OOP, MineTile could inherit from a generic Tile).

Logic layer

This should house the complex ways that the application will interact with changing modes, keeping state, etc. So this would be where a mediator pattern would be implemented in order to maintain the state of the current game. This would be where the game logic resided for determining what happens during a game over for example, or for setting up which MineTiles will have a mine. It would make calls into the Entity layer to get instantiated levels based on logically determined parameters.

var MineSweeperLogic = {
 construct: function(x,y,difficulty){
  var mineSet = [];
  var bombs = 7;
  if( difficulty === "expert" ) bombs = 15;
  for( var i = 0; i < x; i++ ){
   for( var j = 0; i j < y; j++ ){
    var mineTile = new MineTile(i,j);
    mineTile.hasMine = bombs-- > 0;
    mineSet.push(mineTile);
   }
  }
  return mineSet;
 },
 mineAt: function(x,y,mineSet){
  for( var i = 0; i < mineSet.length; i++ )
   if( mineSet[i].x === x && mineSet[i].y === y ) return mineSet[i];
 }
};

Service layer

This will be where the controller has access to. It will have access to the logic layer for building the games. A high level call may be made into the service layer in order to retrieve a fully instantiated game or a modified game state.

function MineSweeper(x,y,difficulty){
 this.x = x;
 thix.y = y;
 this.difficulty = difficulty;
 this.mineSet = MineSweeperLogic.construct(x,y,difficulty);
}
MineSweeper.prototype.expose = function(x,y){
 return MineSweeperLogic.mineAt(x,y,this.mineSet).expose();
}

Controller

Controllers should be light weight, essentially this is what is exposed as the client to the model. There will be many controllers, so structuring them will become important. Controller function calls will be what the javascript calls hit based on UI events. These should expose the behaviors available in the service layer and then populate or in this case modify views for the client.

function MineSweeperController(ctx){
 var this.context = ctx;
}
MineSweeperController.prototype.Start = function(x,y,difficulty){
 this.game = new MineSweeper(x,y,difficulty);
 this.view = new MineSweeperGameView(this.context,this.game.x,this.game.y,this.game.mineSet);
 this.view.Update();
};
MineSweeperController.prototype.Select = function(x,y){
 var result = this.game.expose(x,y);
 if( result === false ) this.GameOver();
 this.view.Select(result);
};
MineSweeperController.prototype.GameOver = function(){
 this.view.Summary(this.game.FinalScore());
};

View

The views should be organized relative to the controller's behaviors. They will probably be the most intensive part of your application since it deals with canvasing.

function MineSweeperGameView(ctx,x,y,mineSet){
 this.x = x;
 this.y = y;
 this.mineSet = mineSet;
 this.context = ctx;
}
MineSweeperGameView.prototype.Update = function(){
 //todo: heavy canvas modification
 for(var mine in this.mineSet){}
 this.context.fill();
}

So now you have your entire MVC setup for this one game. Or at least, a bare bones example, writing the whole game out would have been excessive.

Once this is all done, there will need to be a global scope for the application somewhere. This will hold the lifetime of your current controller, which is the gateway to all of the MVC stack in this scenario.

var currentGame;
var context = document.getElementById("masterCanvas").getContext('2d');
startMineSweeper.click = function(){
 currentGame = new MineSweeperController(context);
 currentGame.Start(25,25,"expert");
};

Using MVC patterns are very powerful, but do not worry too much about adhering to every nuance of them. In the end, it is the game experience that will determine if the application is a success :)

For consideration: Don't Let Architecture Astronauts Scare You by Joel Spolsky

OTHER TIPS

Here is what you've done wrong already -- you have hand-rolled an MVC while in a state of confusion, and without any MVC under your belt.

Take a look at PureMVC, it is language agnostic and can be a good platform to get your feet wet with actually doing MVC.

Its code is small and comprehensible, and this will allow you to tweak it to your needs as you progress.

Start out writing a small simple game with it, minesweeper would be good. A lot of what Travis J said is good, especially about the Model. I would only add that you need to remember that Controllers (at least in PureMvc) are stateless, they come into existence, do their BRIEF work and go away. They are the knowledgeable ones. They are like functions. "Fill the grid, cuz the Model changed", "Update the Model, cuz a button was pushed"

The Views (Mediators in PureMVC) are the dumbest, and the Model is only slightly smarter. Both abstract the implementation, so you (Controllers) never directly touch UI or DB.

Every element of your UI (like in a winforms app for example) has a View (Mediator - you see why this is a better term now?), but Mediators can also be made for meta-concerns like "Control Color" or "Focus Manager" which operate across UI elements. Think in layers here.

UI and DB events can automatically invoke Controllers (if you use a smart naming scheme), and certain Controllers can be phased out -- a Mediator can be made to directly listen for a Model Data change event and be delivered its data package.

Although this is kind of a cheat and requires the Model to know a bit about what is out there, and the Mediator to understand what to do with a data package, but it will keep you from getting swamped with mundane Controllers in many cases.

Model: Dumb but resusable; Controllers: Smart but less reusable (they ARE the app); Mediators: Dumb but reusable. Reusability in this case means portable to another app.

Licensed under: CC-BY-SA with attribution
scroll top