Diseño de la aplicación Javascript MVC (lienzo)

9

Tengo dificultades para comprender cómo estructurar / diseñar una aplicación de lienzo utilizando un enfoque similar a MVC en Javascript. La interfaz de usuario será bastante fluida y animada, los juegos serán bastante simplistas pero con un fuerte énfasis en la interpolación y la animación. Entiendo cómo funciona MVC en principio pero no en la práctica. He buscado en Google el buggery de esto, leí muchísimo, y ahora estoy tan confundido como cuando empecé.

Algunos detalles sobre el área de aplicación:

  • Marco de juego multipantalla: varios juegos se ubicarán dentro de este marco. Las "pantallas" comunes de la interfaz de usuario incluyen: configuración, información, elegir dificultad, menú principal, etc.
  • múltiples métodos de entrada
  • Elementos comunes de la interfaz de usuario como la barra de menú superior en algunas pantallas
  • posibilidad de usar diferentes métodos de renderizado (canvas / DOM / webGL)

Por el momento tengo un AppModel, AppController y AppView. Desde aquí estaba planeando agregar cada una de las "pantallas" y adjuntarla a la AppView. Pero ¿qué pasa con cosas como la barra de menú superior, deberían ser otra tríada MVC? ¿Dónde y cómo lo adjuntaría sin acoplar firmemente los componentes?

¿Es una práctica aceptada tener una tríada MVC dentro de otra? es decir, ¿puedo agregar cada "pantalla" a AppView? ¿Es "tríada" incluso un término MVC aceptado?

Mi mente se está derritiendo bajo las opciones ... Siento que me falta algo fundamental aquí. Ya tengo una solución en funcionamiento sin utilizar un enfoque MVC, pero terminé con una sopa estrechamente acoplada: lógica y puntos de vista y actualmente combinados. La idea era abrirlo y permitir un cambio de vista más fácil (por ejemplo, cambiar una vista de lienzo con una vista basada en DOM).

Bibliotecas actuales utilizadas: require.js, createJS, subrayado, GSAP, implementación MVC enrollada a mano

Se agradecería cualquier puntero, ejemplo, etc., en particular con respecto al diseño real de la cosa y la división de las "pantallas" en M, V o C.

... o un método más apropiado que no sea MVC

[Nota: si has visto esta pregunta antes es porque la hice en otras 2 comunidades incorrectas de intercambio de pila ... mi cerebro ha dejado de funcionar]

gusano
fuente
1
Parece que finalmente encontraste el sitio correcto. Gamedev no quería tu pregunta?
Robert Harvey
@RobertHarvey pensó que posiblemente era más relevante aquí ... ¡al menos eso espero!
wigglyworm

Respuestas:

3

MVC se ha cubierto en tantos lugares, por lo que no debería haber mucho para repetir aquí. Esencialmente, desea que su gráfico de objetos, ayudantes y lógica estén contenidos en el nivel del modelo. Las vistas serán las pantallas que se expulsan para llenar la parte dinámica de la página (y pueden contener una pequeña cantidad de lógica y ayudantes). Y el controlador, que es una implementación ligera para servir las pantallas en función de lo que estaba disponible en los gráficos de objetos, ayudantes y lógica.

Modelo

Esto debería ser donde se sienta la carne de la aplicación. Se puede agrupar en una capa de servicio, una capa lógica y una capa de entidad. ¿Qué significa esto para tu ejemplo?

Capa de entidad

Esto debería contener las definiciones de los modelos y comportamientos internos de tu juego. Por ejemplo, si tuvieras un juego para buscaminas, aquí sería donde se encontraban las definiciones de tablero y cuadrado junto con cómo cambian su estado 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;
};

Por lo tanto, MineTile sabrá su estado interno, como si se está mostrando o si se examinó ( this.pristine), si era uno de los mosaicos que tiene una mina ( this.hasMine), pero no determinará si se supone que debe tener una mina. Eso dependerá de la capa lógica. (Para ir aún más lejos en OOP, MineTile podría heredar de un mosaico genérico).

Capa lógica

Esto debería albergar las formas complejas en que la aplicación interactuará con los modos cambiantes, el estado de mantenimiento, etc. Así que aquí sería donde se implementaría un patrón de mediador para mantener el estado del juego actual. Aquí sería donde residía la lógica del juego para determinar qué sucede durante un juego terminado, por ejemplo, o para configurar qué MineTiles tendrá una mina. Haría llamadas a la capa Entity para obtener niveles instanciados basados ​​en parámetros determinados lógicamente.

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

Capa de servicio

Aquí será donde el controlador tiene acceso. Tendrá acceso a la capa lógica para construir los juegos. Se puede realizar una llamada de alto nivel en la capa de servicio para recuperar un juego completamente instanciado o un estado de juego modificado.

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

Controlador

Los controladores deben ser ligeros, esencialmente esto es lo que está expuesto como cliente al modelo. Habrá muchos controladores, por lo que estructurarlos será importante. Las llamadas a la función del controlador serán lo que las llamadas de JavaScript golpean según los eventos de la interfaz de usuario. Estos deben exponer los comportamientos disponibles en la capa de servicio y luego completar o, en este caso, modificar las vistas para el cliente.

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

Ver

Las vistas deben organizarse en relación con los comportamientos del controlador. Probablemente serán la parte más intensiva de su aplicación, ya que trata con el lienzo.

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

Así que ahora tienes toda tu configuración de MVC para este juego. O al menos, un ejemplo básico, escribir todo el juego hubiera sido excesivo.

Una vez hecho todo esto, será necesario que exista un alcance global para la aplicación en algún lugar. Esto mantendrá la vida útil de su controlador actual, que es la puerta de entrada a toda la pila MVC en este escenario.

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

El uso de patrones MVC es muy poderoso, pero no se preocupe demasiado por adherirse a cada matiz de ellos. Al final, es la experiencia del juego la que determinará si la aplicación es exitosa :)

Para consideración: no dejes que los astronautas de la arquitectura te asusten por Joel Spolsky

Travis J
fuente
gracias @TravisJ - Elegí como una buena explicación de MVC en relación con los juegos. Todavía no estoy seguro de ciertos puntos, creo que, como usted dice, me estoy atascando en los matices de los patrones y eso me impide avanzar. Una cosa que vi fue el uso de this.view.Select () en el controlador: ¿es necesario este tipo de acoplamiento estrecho o hay alguna forma de desacoplarlo más?
wigglyworm
@wigglyworm - ¡Siempre puede haber más desacoplamiento! : D Pero en realidad, el controlador debe ser el que se comunica con el modelo y luego actualiza la vista, de modo que es allí donde probablemente se produce la mayor parte del acoplamiento en MVC.
Travis J
2

Esto es lo que ya ha hecho mal: ha enrollado a mano un MVC mientras está en un estado de confusión y sin ningún MVC en su haber.

Eche un vistazo a PureMVC, es independiente del lenguaje y puede ser una buena plataforma para mojarse los pies haciendo MVC.

Su código es pequeño y comprensible, y esto le permitirá ajustarlo a sus necesidades a medida que avanza.

Comience escribiendo un pequeño juego simple con él, el buscaminas sería bueno. Mucho de lo que dijo Travis J es bueno, especialmente sobre el Modelo. Solo agregaría que debe recordar que los Controladores (al menos en PureMvc) no tienen estado, aparecen, hacen su BREVE trabajo y desaparecen. Ellos son los conocedores. Son como funciones. "Rellene la cuadrícula, porque el modelo cambió", "Actualice el modelo, porque se presionó un botón"

Las Vistas (Mediadores en PureMVC) son las más tontas, y el Modelo es solo un poco más inteligente. Ambos resumen la implementación, por lo que usted (Controladores) nunca toca directamente UI o DB.

Cada elemento de su interfaz de usuario (como en una aplicación winforms, por ejemplo) tiene una Vista (Mediador, ¿ve por qué este es un término mejor ahora?), Pero también se pueden hacer mediadores para meta-preocupaciones como "Control Color" o "Focus Administrador "que opera a través de elementos de la interfaz de usuario. Piensa en capas aquí.

Los eventos de UI y DB pueden invocar automáticamente Controladores (si usa un esquema de nombres inteligente), y ciertos Controladores se pueden eliminar gradualmente: se puede hacer que un Mediador escuche directamente un evento de cambio de Datos del Modelo y se entregue su paquete de datos.

Aunque esto es una especie de trampa y requiere que el Modelo sepa un poco sobre lo que está ahí fuera, y que el Mediador entienda qué hacer con un paquete de datos, pero en muchos casos evitará que te invadan los Controladores mundanos.

Modelo: tonto pero reutilizable; Controladores: inteligentes pero menos reutilizables (son la aplicación); Mediadores: tontos pero reutilizables. La reutilización en este caso significa portátil a otra aplicación.

marca
fuente