Design dell'applicazione Javascript MVC (canvas)


9

Ho difficoltà a capire come strutturare / progettare un'applicazione canvas usando un approccio simile a MVC in Javascript. L'interfaccia utente sarà abbastanza fluida e animata, i giochi abbastanza semplicistici ma con una forte enfasi su interpolazione e animazione. Capisco come funziona MVC in linea di principio ma non in pratica. Ho cercato su Google il problema, ho letto moltissimo e ora sono confuso come quando ho iniziato.

Alcuni dettagli sull'area di applicazione:

  • framework di gioco multi-schermo - più giochi si troveranno all'interno di questo framework "schermate" comuni dell'interfaccia utente includono: impostazioni, informazioni, scegliere difficoltà, menu principale ecc.
  • più metodi di input
  • elementi dell'interfaccia utente comuni come la barra dei menu in alto in alcune schermate
  • possibilità di utilizzare diversi metodi di rendering (canvas / DOM / webGL)

Al momento ho un AppModel, AppController e AppView. Da qui stavo pianificando di aggiungere ciascuna delle "schermate" e collegarla ad AppView. Ma per quanto riguarda cose come la barra dei menu in alto, dovrebbero essere un'altra triade MVC? Dove e come lo collegherei senza componenti ad accoppiamento stretto?

È una pratica accettata avere una triade MVC all'interno di un'altra? cioè posso aggiungere ogni "schermata" ad AppView? "Triade" è anche un termine MVC accettato ?!

La mia mente si sta sciogliendo sotto le opzioni ... Sento che qui mi manca qualcosa di fondamentale. Ho già una soluzione già funzionante senza utilizzare un approccio MVC, ma ho finito con una zuppa strettamente accoppiata - logica e viste e attualmente combinate. L'idea era di aprirlo e consentire un più facile cambio di vista (ad esempio scambiando una vista di tela con una vista basata su DOM).

Librerie correnti utilizzate: request.js, createJS, underscore, GSAP, implementazione MVC laminata a mano

Eventuali puntatori, esempi, ecc., In particolare per quanto riguarda il progetto reale della cosa e la divisione degli "schermi" in M, V o C appropriati sarebbero apprezzati.

... o un metodo più appropriato diverso da MVC

[NB, se hai visto questa domanda prima che sia perché l'ho fatta in altre 2 comunità di scambio di stack errate ... il mio cervello ha smesso di funzionare]


1
Sembra che tu abbia finalmente trovato il sito giusto. Gamedev non voleva la tua domanda?
Robert Harvey,

@RobertHarvey ha pensato che potesse essere più rilevante qui ... almeno lo spero!
wigglyworm,

Risposte:


3

MVC è stato coperto in così tanti posti, quindi non ci dovrebbe essere molto da ripetere. In sostanza, si desidera che il grafico a oggetti, gli helper e la logica siano contenuti nel livello del modello. Le visualizzazioni saranno le schermate che vengono espulse per riempire la parte dinamica della pagina (e possono contenere una leggera quantità di logica e di helper). E il controller, che è un'implementazione leggera per servire gli schermi in base a ciò che era disponibile dai grafici degli oggetti, dagli helper e dalla logica.

Modello

Questo dovrebbe essere dove si trova la carne dell'applicazione. Può essere suddiviso in livelli di servizio, livello logico e livello entità. Cosa significa questo per il tuo esempio?

Livello di entità

Questo dovrebbe contenere le definizioni dei modelli di gioco e dei comportamenti interni. Ad esempio, se avessi un gioco per dragamine, questo sarebbe dove le definizioni del tabellone e del quadrato erano insieme a come cambiano il loro stato interno.

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

Quindi il MineTile conoscerà il suo stato interno, ad esempio se sta mostrando o è stato esaminato ( this.pristine), se era una delle tessere che ha una miniera ( this.hasMine) ma non determinerà se doveva avere una miniera. Questo dipenderà dal livello logico. (Per andare ancora oltre in OOP, MineTile potrebbe ereditare da una tessera generica).

Livello logico

Ciò dovrebbe contenere i modi complessi in cui l'applicazione interagirà con il cambiamento delle modalità, il mantenimento dello stato, ecc. Quindi questo sarebbe il punto in cui verrebbe implementato un modello di mediatore per mantenere lo stato del gioco attuale. Questo sarebbe dove risiedeva la logica di gioco per determinare cosa succede durante una partita per esempio, o per impostare quali MineTiles avranno una miniera. Effettuerebbe chiamate nel livello Entity per ottenere livelli istanziati in base a parametri determinati logicamente.

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

Livello di servizio

Questo è dove il controller ha accesso. Avrà accesso al livello logico per la costruzione dei giochi. È possibile effettuare una chiamata di alto livello nel livello di servizio per recuperare un gioco completamente istanziato o uno stato di gioco modificato.

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

controllore

I controller dovrebbero essere leggeri, in sostanza questo è ciò che è esposto come client al modello. Ci saranno molti controller, quindi strutturarli diventerà importante. Le chiamate alle funzioni del controller saranno quelle che le chiamate javascript hanno riscontrato in base agli eventi dell'interfaccia utente. Questi dovrebbero esporre i comportamenti disponibili nel livello di servizio e quindi popolare o in questo caso modificare le viste per il 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());
};

Visualizza

Le viste dovrebbero essere organizzate in relazione ai comportamenti del controller. Probabilmente saranno la parte più intensa della tua applicazione poiché si occupa del 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();
}

Quindi ora hai la tua intera configurazione MVC per questo gioco. O almeno, un esempio di ossa nude, scrivere l'intero gioco sarebbe stato eccessivo.

Una volta fatto tutto ciò, ci sarà bisogno di un ambito globale per l'applicazione da qualche parte. Ciò manterrà la durata del controller corrente, che è il gateway per tutto lo stack MVC in questo scenario.

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

L'uso dei modelli MVC è molto potente, ma non preoccuparti troppo di aderire ad ogni sfumatura di essi. Alla fine, è l'esperienza di gioco che determinerà se l'applicazione ha successo :)

Per considerazione: non lasciare che gli astronauti dell'architettura ti spaventino di Joel Spolsky


grazie @TravisJ - Ho votato come una bella spiegazione di MVC in relazione ai giochi. Ancora poco chiaro di alcuni punti, penso che, come dici tu, mi sto impantanando nelle sfumature degli schemi e mi impedisce di andare avanti. Una cosa che ho visto è stato l'uso di this.view.Select () nel controller: è necessario questo tipo di accoppiamento stretto o esiste un modo per disaccoppiare ulteriormente?
wigglyworm,

@wigglyworm - Ci può sempre essere più disaccoppiamento! : D Ma in realtà, il controller dovrebbe essere quello che comunica con il modello e quindi aggiorna la vista in modo che sia probabilmente lì che avviene la maggior parte degli accoppiamenti in MVC.
Travis J

2

Ecco cosa hai già fatto di sbagliato: hai fatto rotolare a mano un MVC mentre sei in uno stato di confusione e senza alcun MVC sotto la cintura.

Dai un'occhiata a PureMVC, è un linguaggio agnostico e può essere una buona piattaforma per bagnarti i piedi con MVC.

Il suo codice è piccolo e comprensibile e questo ti permetterà di modificarlo in base alle tue esigenze man mano che avanzi.

Inizia a scrivere un piccolo gioco semplice con esso, il dragamine sarebbe buono. Molto di ciò che Travis J ha detto è buono, specialmente riguardo al Modello. Vorrei solo aggiungere che è necessario ricordare che i controller (almeno in PureMvc) sono apolidi, nascono, fanno il loro BREVE lavoro e vanno via. Sono quelli ben informati. Sono come funzioni. "Riempi la griglia, perché il modello è cambiato", "Aggiorna il modello, perché è stato premuto un pulsante"

Le viste (mediatori in PureMVC) sono le più stupide e il modello è solo leggermente più intelligente. Entrambi astraggono l'implementazione, quindi voi (controller) non toccate mai direttamente l'interfaccia utente o il database.

Ogni elemento dell'interfaccia utente (come ad esempio in un'app winforms) ha una vista (Mediatore: capisci perché questo è un termine migliore adesso?), Ma i mediatori possono anche essere creati per meta-preoccupazioni come "Control Color" o "Focus Manager "che opera attraverso elementi dell'interfaccia utente. Pensa a strati qui.

Gli eventi UI e DB possono invocare automaticamente i controller (se si utilizza uno schema di denominazione intelligente) e alcuni controller possono essere gradualmente eliminati: è possibile creare un mediatore per ascoltare direttamente un evento di modifica dei dati del modello e ricevere il pacchetto di dati.

Anche se questo è un po 'un trucco e richiede che il Modello sappia un po' di quello che c'è là fuori, e il Mediatore per capire cosa fare con un pacchetto di dati, ma in molti casi ti impedirà di essere sommerso da banali Controller.

Modello: stupido ma riutilizzabile; Controller: intelligenti ma meno riutilizzabili (SONO l'app); Mediatori: stupidi ma riutilizzabili. Riusabilità in questo caso significa portabile su un'altra app.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.