Conception d'application Javascript MVC (canevas)

9

J'ai du mal à comprendre comment structurer / architecturer une application canvas en utilisant une approche similaire à MVC en Javascript. L'interface utilisateur sera assez fluide et animée, les jeux assez simplistes mais avec un fort accent sur l'interpolation et l'animation. Je comprends comment MVC fonctionne en principe mais pas en pratique. J'ai googlé la sodomie de cela, lu énormément, et je suis maintenant aussi confus que je l'étais quand j'ai commencé.

Quelques détails sur le domaine d'application:

  • cadre de jeu multi-écrans - plusieurs jeux seront installés dans ce cadre. Les "écrans" communs de l'interface utilisateur incluent: les paramètres, les informations, la difficulté, le menu principal, etc.
  • plusieurs méthodes de saisie
  • éléments d'interface utilisateur communs tels que la barre de menu supérieure sur certains écrans
  • possibilité d'utiliser différentes méthodes de rendu (canvas / DOM / webGL)

Pour le moment, j'ai un AppModel, AppController et AppView. À partir de là, je prévoyais d'ajouter chacun des "écrans" et de le joindre à l'AppView. Mais qu'en est-il des choses comme la barre de menu supérieure, devraient-elles être une autre triade MVC? Où et comment pourrais-je le fixer sans coupler étroitement les composants?

Est-ce une pratique acceptée d'avoir une triade MVC dans une autre? Est-ce que je peux ajouter chaque "écran" à l'AppView? "Triade" est-il même un terme MVC accepté?!

Mon esprit fond sous les options ... J'ai l'impression de manquer quelque chose de fondamental ici. J'ai une solution déjà opérationnelle sans utiliser une approche MVC, mais je me suis retrouvée avec une soupe étroitement couplée - logique et vues et actuellement combinée. L'idée était de l'ouvrir et de permettre un changement de vue plus facile (par exemple en remplaçant une vue de canevas par une vue basée sur DOM).

Bibliothèques utilisées actuellement: require.js, createJS, underscore, GSAP, implémentation MVC roulée à la main

Tous les pointeurs, exemples, etc., en particulier en ce qui concerne la conception réelle de la chose et la division des "écrans" en M, V ou C appropriés seraient appréciés.

... ou une méthode plus appropriée autre que MVC

[NB, si vous avez vu cette question avant c'est parce que je l'ai posée dans 2 autres communautés de stackexchange incorrectes ... mon cerveau a cessé de fonctionner]

wigglyworm
la source
1
On dirait que vous avez enfin trouvé le bon site. Gamedev ne voulait pas de votre question?
Robert Harvey
@RobertHarvey a pensé que c'était peut-être plus pertinent ici ... du moins je l'espère!
wigglyworm

Réponses:

3

MVC a été couvert dans de nombreux endroits, il ne devrait donc pas y avoir beaucoup de réitérations ici. Essentiellement, vous souhaitez que votre graphique d'objet, vos assistants et votre logique soient contenus dans le niveau modèle. Les vues seront les écrans qui seront poussés pour remplir la partie dynamique de la page (et peuvent contenir une petite quantité de logique et d'aides). Et le contrôleur, qui est une implémentation légère pour servir les écrans en fonction de ce qui était disponible à partir des graphiques d'objets, des assistants et de la logique.

Modèle

Cela devrait être l'endroit où se trouve la viande de l'application. Il peut être hiérarchisé en une couche de service, une couche logique et une couche d'entité. Qu'est-ce que cela signifie pour votre exemple?

Couche d'entité

Cela devrait contenir les définitions des modèles et des comportements internes de votre jeu. Par exemple, si vous aviez un jeu pour dragueur de mines, ce serait là que les définitions de plateau et de carré étaient ainsi que la façon dont ils changent leur état interne.

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;
};

Ainsi, le MineTile connaîtra son état interne, comme s'il montre ou a été examiné ( this.pristine), s'il s'agissait d'une des tuiles qui a une mine ( this.hasMine) mais ne déterminera pas s'il était censé avoir une mine. Ce sera à la couche logique. (Pour aller encore plus loin dans la POO, MineTile pourrait hériter d'une tuile générique).

Couche logique

Cela devrait contenir les façons complexes dont l'application interagira avec les modes changeants, le maintien de l'état, etc. Ce serait donc là qu'un modèle de médiateur serait mis en œuvre afin de maintenir l'état du jeu actuel. Ce serait là que la logique du jeu résiderait pour déterminer ce qui se passe pendant un jeu, par exemple, ou pour configurer quels MineTiles auront une mine. Il effectuerait des appels dans la couche Entity pour obtenir des niveaux instanciés en fonction de paramètres déterminés logiquement.

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];
 }
};

Couche de service

Ce sera là où le contrôleur a accès. Il aura accès à la couche logique pour construire les jeux. Un appel de haut niveau peut être effectué dans la couche de service afin de récupérer un jeu entièrement instancié ou un état de jeu modifié.

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();
}

Manette

Les contrôleurs doivent être légers, c'est essentiellement ce qui est exposé en tant que client au modèle. Il y aura de nombreux contrôleurs, leur structuration deviendra donc importante. Les appels de fonction du contrôleur seront ce que les appels javascript atteindront en fonction des événements de l'interface utilisateur. Ceux-ci doivent exposer les comportements disponibles dans la couche de service, puis remplir ou, dans ce cas, modifier les vues pour le 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());
};

Vue

Les vues doivent être organisées en fonction des comportements du contrôleur. Ils constitueront probablement la partie la plus intensive de votre application, car elle traite du canevas.

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();
}

Alors maintenant, vous avez toute votre configuration MVC pour ce jeu. Ou du moins, un exemple à nu, écrire tout le jeu aurait été excessif.

Une fois que tout cela est fait, il devra y avoir une portée globale pour l'application quelque part. Cela conservera la durée de vie de votre contrôleur actuel, qui est la passerelle vers l'ensemble de la pile MVC dans ce scénario.

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

L'utilisation de modèles MVC est très puissante, mais ne vous inquiétez pas trop d'adhérer à toutes leurs nuances. Au final, c'est l'expérience de jeu qui déterminera si l'application est un succès :)

À considérer: Ne laissez pas les astronautes de l'architecture vous effrayer par Joel Spolsky

Travis J
la source
merci @TravisJ - J'ai voté positivement car c'est une belle explication de MVC par rapport aux jeux. Je ne sais toujours pas certains points, je pense, comme vous le dites, que je m'embourbe dans les nuances des motifs et cela m'empêche d'avancer. Une chose que j'ai vue a été l'utilisation de this.view.Select () dans le contrôleur - ce type de couplage serré est-il nécessaire ou existe-t-il un moyen de découpler davantage?
wigglyworm
@wigglyworm - Il peut toujours y avoir plus de découplage! : D Mais vraiment, le contrôleur devrait être celui qui communique avec le modèle, puis met à jour la vue de sorte que c'est probablement là que la plupart des couplages ont lieu dans MVC.
Travis J
2

Voici ce que vous avez déjà fait de mal - vous avez roulé à la main un MVC dans un état de confusion et sans aucun MVC à votre actif.

Jetez un œil à PureMVC, il est indépendant du langage et peut être une bonne plate-forme pour vous mouiller les pieds en faisant du MVC.

Son code est petit et compréhensible, ce qui vous permettra de l'adapter à vos besoins au fur et à mesure de votre progression.

Commencez à écrire un petit jeu simple avec lui, le dragueur de mines serait bien. Beaucoup de ce que Travis J a dit est bon, en particulier sur le modèle. J'ajouterais seulement que vous devez vous rappeler que les contrôleurs (au moins dans PureMvc) sont apatrides, ils existent, font leur travail BREF et s'en vont. Ce sont eux qui sont bien informés. Ce sont comme des fonctions. "Remplissez la grille, car le modèle a changé", "Mettez à jour le modèle, car un bouton a été enfoncé"

Les vues (médiateurs dans PureMVC) sont les plus stupides et le modèle n'est que légèrement plus intelligent. Les deux résument l'implémentation, de sorte que vous (les contrôleurs) ne touchiez jamais directement à l'interface utilisateur ou à la base de données.

Chaque élément de votre interface utilisateur (comme dans une application Winforms par exemple) a une vue (Mediator - vous voyez pourquoi c'est un meilleur terme maintenant?), Mais des médiateurs peuvent également être créés pour des méta-préoccupations comme "Control Color" ou "Focus Manager "qui opèrent sur les éléments de l'interface utilisateur. Pensez en couches ici.

Les événements d'interface utilisateur et de base de données peuvent appeler automatiquement des contrôleurs (si vous utilisez un schéma de dénomination intelligent), et certains contrôleurs peuvent être supprimés progressivement - un médiateur peut être amené à écouter directement un événement de modification des données du modèle et à recevoir son package de données.

Bien que cela soit une sorte de triche et nécessite que le modèle sache un peu ce qui existe, et que le médiateur comprenne quoi faire avec un package de données, mais cela vous empêchera d'être submergé de contrôleurs banals dans de nombreux cas.

Modèle: muet mais réutilisable; Contrôleurs: intelligents mais moins réutilisables (ils SONT l'application); Médiateurs: stupides mais réutilisables. La réutilisabilité dans ce cas signifie portable sur une autre application.

marque
la source