Javascript MVC-Anwendungsdesign (Zeichenfläche)

9

Ich habe Schwierigkeiten zu verstehen, wie eine Canvas-Anwendung mit einem MVC-ähnlichen Ansatz in Javascript strukturiert / architektonisch gestaltet wird. Die Benutzeroberfläche wird ziemlich flüssig und animiert sein, die Spiele sind ziemlich simpel, aber mit Schwerpunkt auf Tweening und Animation. Ich verstehe, wie MVC im Prinzip funktioniert, aber nicht in der Praxis. Ich habe die Fehlerbehebung gegoogelt, sehr viel gelesen und bin jetzt genauso verwirrt wie zu Beginn.

Einige Details zum Anwendungsbereich:

  • Multi-Screen-Game-Framework - In diesem Framework befinden sich mehrere Spiele. Zu den allgemeinen "Bildschirmen" der Benutzeroberfläche gehören: Einstellungen, Informationen, Schwierigkeitsgrad auswählen, Hauptmenü usw.
  • mehrere Eingabemethoden
  • Allgemeine UI-Elemente wie die obere Menüleiste auf einigen Bildschirmen
  • Möglichkeit der Verwendung verschiedener Rendering-Methoden (Canvas / DOM / WebGL)

Im Moment habe ich ein AppModel, AppController und AppView. Von hier aus wollte ich jeden der "Bildschirme" hinzufügen und an die AppView anhängen. Aber was ist mit Dingen wie der oberen Menüleiste, sollten sie eine andere MVC-Triade sein? Wo und wie würde ich es anbringen, ohne die Komponenten fest zu koppeln?

Ist es eine akzeptierte Praxis, eine MVC-Triade in einer anderen zu haben? dh kann ich jeden "Bildschirm" zur AppView hinzufügen? Ist "Triade" überhaupt ein akzeptierter MVC-Begriff?!

Mein Geist schmilzt unter den Optionen ... Ich habe das Gefühl, dass mir hier etwas Grundlegendes fehlt. Ich habe bereits eine Lösung in Betrieb genommen, ohne einen MVC-Ansatz zu verwenden, habe jedoch eng gekoppelte Suppenlogik und Ansichten erhalten und bin derzeit kombiniert. Die Idee war, es zu öffnen und das Ändern von Ansichten zu erleichtern (zum Beispiel das Austauschen einer Canvas-Ansicht mit einer DOM-basierten Ansicht).

Derzeit verwendete Bibliotheken: require.js, createJS, Unterstrich, GSAP, handgerollte MVC-Implementierung

Alle Hinweise, Beispiele usw., insbesondere in Bezug auf das tatsächliche Design der Sache und die Aufteilung der "Bildschirme" in richtige M, V oder C, sind willkommen.

... oder eine geeignetere Methode als MVC

[NB, wenn Sie diese Frage schon einmal gesehen haben, weil ich sie in zwei anderen falschen Stackexchange-Communities gestellt habe ... mein Gehirn funktioniert nicht mehr]

wackeliger Wurm
quelle
1
Sieht so aus, als hätten Sie endlich die richtige Seite gefunden. Gamedev wollte deine Frage nicht?
Robert Harvey
@ RobertHarvey dachte, dass es hier möglicherweise relevanter ist ... zumindest hoffe ich es!
Wigglyworm

Antworten:

3

MVC wurde an so vielen Stellen behandelt, dass es hier nicht viel zu wiederholen geben sollte. Im Wesentlichen möchten Sie, dass Ihr Objektdiagramm, Ihre Helfer und Ihre Logik in der Modellebene enthalten sind. Die Ansichten sind die Bildschirme, die herausgedrückt werden, um den dynamischen Teil der Seite auszufüllen (und möglicherweise eine geringe Menge an Logik und Hilfsprogrammen enthalten). Und der Controller, der eine einfache Implementierung darstellt, um die Bildschirme basierend auf den verfügbaren Informationen aus den Objektdiagrammen, Hilfsprogrammen und der Logik bereitzustellen.

Modell

Hier sollte sich das Fleisch der Anwendung befinden. Es kann in eine Serviceschicht, eine Logikschicht und eine Entitätsschicht unterteilt werden. Was bedeutet das für Ihr Beispiel?

Entitätsebene

Dies sollte die Definitionen der Modelle und internen Verhaltensweisen Ihres Spiels enthalten. Wenn Sie beispielsweise ein Spiel für Minensuchboot hatten, befanden sich hier die Definitionen für Bretter und Quadrate sowie die Art und Weise, wie sie ihren internen Status ändern.

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

Das MineTile kennt also seinen internen Zustand, z. B. ob es angezeigt wird oder untersucht wurde ( this.pristine), wenn es eines der Kacheln war, das eine Mine hat ( this.hasMine), aber nicht bestimmt, ob es eine Mine haben sollte. Das liegt an der Logikschicht. (Um noch weiter in OOP einzusteigen, könnte MineTile von einer generischen Kachel erben).

Logikebene

Dies sollte die komplexen Möglichkeiten enthalten, wie die Anwendung mit sich ändernden Modi, dem Beibehalten des Status usw. interagiert. Hier würde also ein Mediatormuster implementiert, um den Status des aktuellen Spiels beizubehalten. Hier befand sich die Spiellogik, um beispielsweise zu bestimmen, was während eines Spiels passiert, oder um festzulegen, welche MineTiles eine Mine haben sollen. Es würde Aufrufe in der Entitätsebene tätigen, um instanziierte Ebenen basierend auf logisch bestimmten Parametern zu erhalten.

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

Serviceschicht

Hier hat der Controller Zugriff. Es wird Zugriff auf die Logikschicht zum Erstellen der Spiele haben. Ein Anruf auf hoher Ebene kann in die Serviceschicht getätigt werden, um ein vollständig instanziiertes Spiel oder einen modifizierten Spielstatus abzurufen.

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

Regler

Controller sollten leicht sein, im Wesentlichen ist dies das, was als Client dem Modell ausgesetzt ist. Es wird viele Controller geben, daher wird die Strukturierung dieser Controller wichtig. Controller-Funktionsaufrufe sind das, was die Javascript-Aufrufe basierend auf UI-Ereignissen treffen. Diese sollten die in der Service-Schicht verfügbaren Verhaltensweisen offenlegen und dann die Ansichten für den Client füllen oder in diesem Fall ändern.

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

Aussicht

Die Ansichten sollten relativ zum Verhalten des Controllers organisiert sein. Sie werden wahrscheinlich der intensivste Teil Ihrer Anwendung sein, da sie sich mit der Leinwand befasst.

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

Jetzt haben Sie Ihr gesamtes MVC-Setup für dieses eine Spiel. Zumindest wäre es übertrieben gewesen, das ganze Spiel aufzuschreiben.

Sobald dies alles erledigt ist, muss irgendwo ein globaler Bereich für die Anwendung vorhanden sein. Dies hält die Lebensdauer Ihres aktuellen Controllers, der in diesem Szenario das Gateway zum gesamten MVC-Stack darstellt.

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

Die Verwendung von MVC-Mustern ist sehr leistungsfähig, aber Sie müssen sich nicht zu sehr darum kümmern, jede Nuance von ihnen einzuhalten. Am Ende ist es das Spielerlebnis, das entscheidet, ob die Anwendung erfolgreich ist :)

Zur Überlegung: Lassen Sie sich von Architekturastronauten nicht erschrecken von Joel Spolsky

Travis J.
quelle
danke @TravisJ - Ich habe eine nette Erklärung für MVC im Zusammenhang mit Spielen abgegeben. Immer noch unklar über bestimmte Punkte, denke ich, wie Sie sagen, bin ich in den Nuancen der Muster festgefahren und es hindert mich daran, vorwärts zu kommen. Eine Sache, die ich gesehen habe, war die Verwendung von this.view.Select () in der Steuerung - ist diese Art der engen Kopplung notwendig oder gibt es eine Möglichkeit, sie weiter zu entkoppeln?
Wigglyworm
@wigglyworm - Es kann immer mehr Entkopplung geben! : D Aber eigentlich sollte der Controller derjenige sein, der mit dem Modell kommuniziert und dann die Ansicht aktualisiert, sodass dort wahrscheinlich die meiste Kopplung in MVC stattfindet.
Travis J
2

Folgendes haben Sie bereits falsch gemacht: Sie haben eine MVC in einem Zustand der Verwirrung und ohne MVC von Hand gerollt.

Werfen Sie einen Blick auf PureMVC, es ist sprachunabhängig und kann eine gute Plattform sein, um Ihre Füße mit MVC nass zu machen.

Der Code ist klein und verständlich, sodass Sie ihn im Laufe der Zeit an Ihre Bedürfnisse anpassen können.

Beginnen Sie damit, ein kleines einfaches Spiel damit zu schreiben. Minensuchboot wäre gut. Vieles, was Travis J gesagt hat, ist gut, besonders was das Modell betrifft. Ich möchte nur hinzufügen, dass Sie sich daran erinnern müssen, dass Controller (zumindest in PureMvc) zustandslos sind, entstehen, ihre KURZE Arbeit erledigen und verschwinden. Sie sind die Fachkundigen. Sie sind wie Funktionen. "Füllen Sie das Raster, weil das Modell geändert wurde", "Aktualisieren Sie das Modell, weil eine Taste gedrückt wurde"

Die Ansichten (Mediatoren in PureMVC) sind die dümmsten, und das Modell ist nur geringfügig intelligenter. Beide abstrahieren die Implementierung, sodass Sie (Controller) niemals direkt die Benutzeroberfläche oder die Datenbank berühren.

Jedes Element Ihrer Benutzeroberfläche (wie zum Beispiel in einer Winforms-App) verfügt über eine Ansicht (Mediator - sehen Sie, warum dies jetzt ein besserer Begriff ist?). Mediatoren können jedoch auch für Meta-Probleme wie "Kontrollfarbe" oder "Fokus" erstellt werden Manager ", die über UI-Elemente hinweg funktionieren. Denken Sie hier in Schichten.

UI- und DB-Ereignisse können automatisch Controller aufrufen (wenn Sie ein intelligentes Namensschema verwenden), und bestimmte Controller können auslaufen. Ein Mediator kann dazu gebracht werden, direkt auf ein Modelldatenänderungsereignis zu warten und sein Datenpaket zuzustellen.

Dies ist zwar eine Art Betrug und erfordert, dass das Modell ein wenig darüber weiß, was da draußen ist, und dass der Mediator versteht, was mit einem Datenpaket zu tun ist, aber es wird Sie in vielen Fällen davon abhalten, mit alltäglichen Controllern überflutet zu werden.

Modell: Dumm, aber wiederverwendbar; Controller: Intelligent, aber weniger wiederverwendbar (sie sind die App); Mediatoren: Dumm, aber wiederverwendbar. Wiederverwendbarkeit bedeutet in diesem Fall, auf eine andere App portierbar zu sein.

Kennzeichen
quelle