Design do aplicativo JavaScript MVC (tela)


9

Estou tendo dificuldades para entender como estruturar / arquitetar um aplicativo de tela usando uma abordagem semelhante ao MVC em Javascript. A interface do usuário será bastante fluida e animada, os jogos bastante simplistas, mas com forte ênfase em interpolação e animação. Entendo como o MVC funciona em princípio, mas não na prática. Eu pesquisei o buggery sobre isso, li muito e agora estou tão confuso quanto estava quando comecei.

Alguns detalhes sobre a área de aplicação:

  • estrutura de jogo com várias telas - vários jogos ficam dentro dessa estrutura "telas" comuns da interface do usuário incluem: configurações, informações, dificuldade de escolha, menu principal etc.
  • vários métodos de entrada
  • elementos comuns da interface do usuário, como a barra de menus superior em algumas telas
  • possibilidade de usar diferentes métodos de renderização (canvas / DOM / webGL)

No momento eu tenho um AppModel, AppController e AppView. A partir daqui, planejava adicionar cada uma das "telas" e anexá-la ao AppView. Mas e as coisas como a barra de menus superior, devem ser outra tríade do MVC? Onde e como conectá-lo sem componentes de acoplamento firmes?

É uma prática aceita ter uma tríade MVC dentro de outra? ou seja, posso adicionar cada "tela" ao AppView? "Tríade" é mesmo um termo aceito do MVC ?!

Minha mente está derretendo sob as opções ... Sinto que estou perdendo algo fundamental aqui. Eu já tenho uma solução em funcionamento sem usar uma abordagem MVC, mas acabei com sopa fortemente acoplada - lógica e visualizações e atualmente combinada. A idéia era abri-lo e permitir uma mudança mais fácil de visualizações (por exemplo, trocar uma visualização de tela por uma visualização baseada em DOM).

Bibliotecas atuais usadas: require.js, createJS, underscore, GSAP, implementação MVC rolada manualmente

Seriam apreciados quaisquer indicadores, exemplos, etc., particularmente no que diz respeito ao design real da coisa e à divisão das "telas" em M, V ou C apropriados.

... ou um método mais apropriado que não seja o MVC

[NB, se você já viu essa pergunta antes, é porque eu a fiz em duas outras comunidades de troca incorreta de pilhas ... meu cérebro parou de funcionar]


11
Parece que você finalmente encontrou o site certo. Gamedev não quis sua pergunta?
Robert Harvey

@RobertHarvey achou que era possivelmente mais relevante aqui ... pelo menos espero que sim!
Wigglyworm # 8/14

Respostas:


3

O MVC foi abordado em muitos lugares, portanto, não deve haver muito o que reiterar aqui. Essencialmente, você deseja que seu gráfico, auxiliares e lógica de objetos estejam contidos na camada do modelo. As visualizações serão as telas empurradas para preencher a parte dinâmica da página (e podem conter uma quantidade pequena de lógica e auxiliares). E o controlador, que é uma implementação leve para atender às telas com base no que estava disponível nos gráficos de objetos, auxiliares e lógica.

Modelo

Deve ser onde fica a carne do aplicativo. Ele pode ser dividido em camadas em uma camada de serviço, uma camada lógica e uma camada de entidade. O que isso significa para o seu exemplo?

Camada de entidade

Isso deve incluir as definições dos modelos e comportamentos internos do seu jogo. Por exemplo, se você tivesse um jogo para o caça-minas, seria aqui que as definições do tabuleiro e do quadrado estavam junto com a forma como elas mudam seu 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;
};

Portanto, o MineTile conhecerá seu estado interno, como se ele está sendo exibido ou foi examinado ( this.pristine), se foi um dos blocos que possui uma mina ( this.hasMine), mas não determinará se deveria ter uma mina. Isso depende da camada lógica. (Para ir ainda mais longe no OOP, o MineTile pode herdar de um Tile genérico).

Camada lógica

Isso deve abrigar as formas complexas pelas quais o aplicativo irá interagir com os modos de mudança, mantendo o estado, etc. Portanto, seria nesse local que um padrão mediador seria implementado para manter o estado do jogo atual. É aí que reside a lógica do jogo para determinar o que acontece durante um game over, por exemplo, ou para definir quais MineTiles terão uma mina. Ele faria chamadas para a camada Entidade para obter níveis instanciados com base em parâmetros determinados 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];
 }
};

Camada de serviço

É aqui que o controlador tem acesso. Ele terá acesso à camada lógica para a construção dos jogos. Uma chamada de alto nível pode ser feita na camada de serviço para recuperar um jogo totalmente instanciado ou um estado de jogo 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

Os controladores devem ser leves, essencialmente é isso que é exposto como cliente ao modelo. Haverá muitos controladores, portanto, estruturá-los se tornará importante. As chamadas de função do controlador serão as chamadas javascript atingidas com base nos eventos da interface do usuário. Eles devem expor os comportamentos disponíveis na camada de serviço e, em seguida, preencher ou, neste caso, modificar as visualizações do 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());
};

Visão

As visualizações devem ser organizadas em relação aos comportamentos do controlador. Eles provavelmente serão a parte mais intensiva do seu aplicativo, pois trata da tela.

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

Então agora você tem toda a sua configuração do MVC para este jogo. Ou pelo menos, um exemplo simples, escrever o jogo inteiro seria excessivo.

Quando tudo estiver pronto, será necessário haver um escopo global para o aplicativo em algum lugar. Isso manterá a vida útil do seu controlador atual, que é o gateway para toda a pilha MVC neste cenário.

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

O uso de padrões MVC é muito poderoso, mas não se preocupe muito em aderir a todas as nuances deles. No final, é a experiência do jogo que determinará se o aplicativo é um sucesso :)

Para consideração: Não deixe que os astronautas da arquitetura o assuste Joel Spolsky


thanks @TravisJ - Eu votei como uma boa explicação do MVC relacionada a jogos. Ainda não está claro alguns pontos, acho que, como você diz, estou me atolando nas nuances dos padrões e isso está me impedindo de seguir em frente. Uma coisa que vi foi o uso de this.view.Select () no controlador - esse tipo de acoplamento é necessário ou existe uma maneira de se dissociar mais?
Wigglyworm

@wigglyworm - Sempre pode haver mais dissociação! : D Mas, na verdade, o controlador deve ser o que se comunica com o modelo e, em seguida, atualiza a visualização, de modo que provavelmente é onde a maioria dos acoplamentos ocorre no MVC.
Travis J

2

Aqui está o que você já fez de errado - você rolou manualmente um MVC enquanto estava em um estado de confusão e sem nenhum MVC em seu currículo.

Dê uma olhada no PureMVC, ele é independente de idioma e pode ser uma boa plataforma para se molhar na prática do MVC.

Seu código é pequeno e compreensível, e isso permitirá que você o ajuste às suas necessidades à medida que avança.

Comece escrevendo um pequeno jogo simples com ele, o caça-minas seria bom. Muito do que Travis J disse é bom, especialmente sobre o Modelo. Gostaria apenas de acrescentar que você precisa lembrar que os controladores (pelo menos no PureMvc) são apátridas, eles passam a existir, fazem seu trabalho BREVE e desaparecem. Eles são os conhecedores. Eles são como funções. "Preencha a grade, porque o modelo foi alterado", "Atualize o modelo, porque um botão foi pressionado"

As exibições (mediadores no PureMVC) são as mais estúpidas e o modelo é apenas um pouco mais inteligente. Os dois abstraem a implementação, para que você (Controladores) nunca toque diretamente na interface do usuário ou no banco de dados.

Todos os elementos da sua interface do usuário (como em um aplicativo winforms, por exemplo) têm uma Visualização (Mediador - você vê por que esse é um termo melhor agora?), Mas os Mediadores também podem ser criados para meta-preocupações como "Cor de controle" ou "Foco Manager ", que opera através dos elementos da interface do usuário. Pense em camadas aqui.

Os eventos de UI e DB podem chamar automaticamente os Controladores (se você usar um esquema de nomenclatura inteligente), e determinados Controladores podem ser eliminados gradualmente - um Mediador pode ser criado para escutar diretamente um evento de alteração de Dados do Modelo e receber seu pacote de dados.

Embora isso seja uma espécie de trapaça e exija que o Modelo saiba um pouco sobre o que está lá fora, e que o Mediador entenda o que fazer com um pacote de dados, mas isso evitará que você seja inundado por Controladores mundanos em muitos casos.

Modelo: Burro, mas reutilizável; Controladores: inteligentes, mas menos reutilizáveis ​​(eles são o aplicativo); Mediadores: burros, mas reutilizáveis. Reutilização neste caso significa portátil para outro aplicativo.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.