Projektowanie aplikacji JavaScript MVC (płótno)

9

Mam trudność w zrozumieniu, jak utworzyć strukturę / architekturę aplikacji canvas przy użyciu podejścia podobnego do MVC w JavaScript. Interfejs użytkownika będzie dość płynny i animowany, gry dość uproszczone, ale z dużym naciskiem na animację i animację. Rozumiem, jak MVC działa w zasadzie, ale nie w praktyce. Zrobiłem z tego buggy, przeczytałem okropnie dużo i teraz jestem tak zdezorientowany, jak kiedy zaczynałem.

Niektóre szczegóły dotyczące obszaru zastosowania:

  • framework dla wielu ekranów - wiele gier będzie siedziało w tym ramach wspólne ekrany interfejsu użytkownika obejmują: ustawienia, informacje, wybierz trudność, menu główne itp.
  • wiele metod wprowadzania
  • typowe elementy interfejsu użytkownika, takie jak górny pasek menu na niektórych ekranach
  • możliwość zastosowania różnych metod renderowania (canvas / DOM / webGL)

W tej chwili mam AppModel, AppController i AppView. Odtąd planowałem dodać każdy z „ekranów” i dołączyć go do AppView. Ale co z takimi elementami, jak górny pasek menu, czy powinny one być kolejną triadą MVC? Gdzie i jak mam go przymocować bez ścisłego łączenia komponentów?

Czy akceptowaną praktyką jest posiadanie jednej triady MVC w drugiej? tzn. czy mogę dodać każdy „ekran” do AppView? Czy „triada” jest nawet akceptowanym terminem MVC ?!

Mój umysł rozpływa się pod opcjami ... Mam wrażenie, że brakuje mi tutaj czegoś fundamentalnego. Mam już gotowe rozwiązanie bez użycia metody MVC, ale skończyło się na ściśle powiązanej zupie - logice i poglądach, a obecnie połączone. Pomysł polegał na otwarciu go i umożliwieniu łatwiejszej zmiany widoków (np. Zamiana widoku kanwy na widok oparty na DOM).

Aktualnie używane biblioteki: wymagają.js, createJS, podkreślenia, GSAP, ręcznie walcowanej implementacji MVC

Doceniane byłyby wszelkie wskazówki, przykłady itp., Szczególnie w odniesieniu do rzeczywistego projektu rzeczy i podziału „ekranów” na właściwe M, V lub C.

... lub bardziej odpowiednia metoda inna niż MVC

[Uwaga: jeśli widziałeś to pytanie wcześniej, ponieważ zadałem je w 2 innych nieprawidłowych społecznościach wymiany stosów ... mój mózg przestał działać]

wigglyworm
źródło
1
Wygląda na to, że w końcu znalazłeś właściwą stronę. Gamedev nie chciał twojego pytania?
Robert Harvey
@RobertHarvey pomyślał, że to może być bardziej odpowiednie tutaj ... przynajmniej mam taką nadzieję!
wigglyworm

Odpowiedzi:

3

MVC zostało omówione w tak wielu miejscach, więc nie powinno być tu wiele do powtórzenia. Zasadniczo chcesz, aby wykres obiektów, pomocniki i logika były zawarte w warstwie modelu. Widoki będą ekranami wypychanymi w celu wypełnienia dynamicznej części strony (i mogą zawierać niewielką ilość logiki i pomocników). I kontroler, który jest lekką implementacją służącą do obsługi ekranów w oparciu o to, co było dostępne z grafów obiektowych, pomocników i logiki.

Model

To powinno być miejsce, w którym znajduje się mięso aplikacji. Może być podzielony na warstwy usługi, warstwy logiki i warstwy jednostek. Co to oznacza dla twojego przykładu?

Warstwa jednostki

Powinno to zawierać definicje modeli gry i zachowań wewnętrznych. Na przykład, jeśli masz grę dla trałowca, to tutaj znajdowałyby się definicje planszy i kwadratu oraz zmiany ich stanu wewnętrznego.

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

Tak więc MineTile pozna swój stan wewnętrzny, na przykład, czy pokazuje lub został zbadany ( this.pristine), jeśli był to jeden z kafelków, który ma kopalnię ( this.hasMine), ale nie określi, czy miała mieć kopalnię. To zależy od warstwy logicznej. (Aby przejść dalej do OOP, MineTile może dziedziczyć z ogólnej płytki).

Warstwa logiczna

Powinno to obejmować złożone sposoby interakcji aplikacji ze zmieniającymi się trybami, utrzymywaniem stanu itp. W tym miejscu wzorzec mediatora zostałby wdrożony w celu utrzymania stanu obecnej gry. To byłaby logika gry, która określałaby na przykład, co się dzieje podczas gry, lub ustawiała, które MineTiles będą miały kopalnię. Wykonuje wywołania w warstwie Entity, aby uzyskać instancyjne poziomy na podstawie logicznie określonych parametrów.

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

Warstwa usługowa

Będzie to miejsce, do którego kontroler ma dostęp. Będzie miał dostęp do warstwy logicznej do budowania gier. W warstwie serwisowej można wykonać połączenie wysokiego poziomu w celu odzyskania w pełni utworzonej gry lub zmodyfikowanego stanu gry.

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

Kontroler

Kontrolery powinny być lekkie, w zasadzie to właśnie to, co klient jest narażony na model. Będzie wielu kontrolerów, więc ich struktura stanie się ważna. Wywołania funkcji kontrolera będą tym, co wywołają wywołania javascript na podstawie zdarzeń interfejsu użytkownika. Powinny one ujawnić zachowania dostępne w warstwie usługi, a następnie wypełnić lub w tym przypadku zmodyfikować widoki dla klienta.

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

Widok

Widoki powinny być uporządkowane względem zachowań kontrolera. Prawdopodobnie będą one najbardziej intensywną częścią twojej aplikacji, ponieważ dotyczy ona akwizycji.

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

Teraz masz całą konfigurację MVC dla tej jednej gry. A przynajmniej przykład z nagimi kośćmi, wypisywanie całej gry byłoby nadmierne.

Gdy to wszystko zostanie zrobione, gdzieś musi istnieć globalny zasięg dla aplikacji. Utrzyma to żywotność bieżącego kontrolera, który jest bramą do wszystkich stosów MVC w tym scenariuszu.

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

Używanie wzorców MVC jest bardzo wydajne, ale nie martw się zbytnio o przestrzeganie każdego z nich. Ostatecznie to gra sprawi, że aplikacja odniesie sukces :)

Do rozważenia: Nie pozwól, aby astronauci z architektury przestraszyli cię przez Joela Spolsky'ego

Travis J
źródło
dzięki @TravisJ - Głosowałem za dobrym wyjaśnieniem MVC w związku z grami. Nadal niejasne w niektórych kwestiach, myślę, że, jak mówisz, pogrążam się w niuansach wzorów i to powstrzymuje mnie przed pójściem naprzód. Jedną z rzeczy, które widziałem, było użycie this.view.Select () w kontrolerze - czy ten rodzaj ścisłego sprzężenia jest konieczny, czy też istnieje sposób na dalsze odsprzęganie?
wigglyworm
@wigglyworm - Zawsze może być więcej odsprzęgania! : D Ale tak naprawdę kontroler powinien być tym, który komunikuje się z modelem, a następnie aktualizuje widok, tak więc prawdopodobnie tam najwięcej sprzężeń ma miejsce w MVC.
Travis J
2

Oto, co już zrobiłeś źle - ręcznie rzuciłeś MVC w stanie zagubienia i bez MVC pod pasem.

Spójrz na PureMVC, jest on niezależny od języka i może być dobrą platformą do zmoczenia stóp podczas wykonywania MVC.

Jego kod jest mały i zrozumiały, a to pozwoli ci dostosować go do twoich potrzeb w miarę postępu.

Zacznij od napisania małej, prostej gry, trałowiec byłby dobry. Wiele z tego, co powiedział Travis J, jest dobre, szczególnie w przypadku Modelu. Dodam tylko, że należy pamiętać, że kontrolery (przynajmniej w PureMvc) są bezstanowe, powstają, wykonują KRÓTKĄ pracę i odchodzą. Są kompetentni. Są jak funkcje. „Wypełnij siatkę, ponieważ model się zmienił”, „Zaktualizuj model, bo naciśnięto przycisk”

Widoki (Mediatory w PureMVC) są najgłupsze, a model jest tylko nieco mądrzejszy. Oba opisują implementację, więc ty (kontrolery) nigdy nie dotykasz bezpośrednio interfejsu użytkownika ani bazy danych.

Każdy element interfejsu użytkownika (na przykład w aplikacji WinForm) ma widok (Mediator - czy rozumiesz teraz, dlaczego jest to lepszy termin?), Ale mediatorów można także używać w przypadku meta-obaw, takich jak „Kolor kontrolny” lub „Fokus” Manager ”, które działają w elementach interfejsu użytkownika. Pomyśl tutaj warstwami.

Zdarzenia interfejsu użytkownika i bazy danych mogą automatycznie wywoływać kontrolery (jeśli używasz inteligentnego schematu nazewnictwa), a niektóre kontrolery można wycofać - można ustawić mediatora, aby bezpośrednio nasłuchiwał zdarzenia zmiany danych modelu i otrzymał pakiet danych.

Chociaż jest to rodzaj oszustwa i wymaga, aby Model wiedział trochę o tym, co tam jest, a Pośrednik, aby zrozumieć, co zrobić z pakietem danych, ale w wielu przypadkach nie pozwoli ci to zapanować nad przyziemnymi kontrolerami.

Model: głupi, ale wielokrotnego użytku; Kontrolery: inteligentne, ale mniej wielokrotnego użytku (TO SĄ aplikacja); Mediatorzy: głupi, ale wielokrotnego użytku. Ponowne użycie w tym przypadku oznacza możliwość przeniesienia do innej aplikacji.

znak
źródło