Javascript MVC应用程序设计(画布)


9

我很难掌握如何在Javascript中使用类似于MVC的方法来构造/构造画布应用程序。用户界面将非常流畅和动画化,游戏也将非常简单化,但会重点强调补间和动画。我了解了MVC的工作原理,但实际上却没有。我已经从中搜查了一些漏洞,阅读了很多文章,现在和我刚开始时一样困惑。

有关应用程序区域的一些详细信息:

  • 多屏幕游戏框架-多个游戏将位于该框架内常见的UI“屏幕”包括:设置,信息,选择难度,主菜单等。
  • 多种输入法
  • 常见的UI元素,例如某些屏幕上的顶部菜单栏
  • 使用不同渲染方法(画布/ DOM / webGL)的可能性

目前,我有一个AppModel,AppController和AppView。从这里开始,我计划添加每个“屏幕”并将其附加到AppView。但是,诸如顶部菜单栏之类的东西又应该是另一个MVC三合会吗?在不紧密耦合组件的情况下,我将在何处以及如何安装它?

将一个MVC三合一组合在另一个中是否被接受?即我可以将每个“屏幕”添加到AppView吗?“ triad”甚至是公认的MVC术语吗?

我的想法在这些选择下融化了……我觉得这里缺少基本的东西。我已经有一个不使用MVC方法就可以启动并运行的解决方案,但是最后却得到了紧密耦合的解决方案-逻辑和视图,目前已经结合在一起。这个想法是打开它并使视图更容易更改(例如,将画布视图换成基于DOM的视图)。

当前使用的库:require.js,createJS,下划线,GSAP,手动MVC实现

任何指针,示例等,特别是关于事物的实际设计以及将“屏幕”划分为适当的M,V或C的指针,示例等都将受到赞赏。

...或除MVC以外更合适的方法

[注意,如果您之前曾看过这个问题,是因为我在另外两个不正确的stackexchange社区中提出了这个问题……我的大脑已经停止运作了]


1
看来您终于找到了正确的网站。Gamedev不想要您的问题吗?
罗伯特·哈维

@RobertHarvey认为这里可能更有意义...至少我希望如此!
wigglyworm

Answers:


3

MVC在很多地方都涉及过,因此在这里不必重复太多。本质上,您希望对象图,助手和逻辑包含在模型层中。视图将是被弹出以填充页面的动态部分的屏幕(并且可能包含少量逻辑和帮助器)。控制器是一种轻量级的实现,可以根据对象图,助手和逻辑中的可用内容为屏幕提供服务。

模型

这应该是应用程序所在的位置。可以将其分为服务层,逻辑层和实体层。这对您的示例意味着什么?

实体层

这应该包含游戏模型和内部行为的定义。例如,如果您有一个扫雷游戏,那么这将是木板和正方形定义以及它们如何更改其内部状态的地方。

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

因此,MineTile将知道其内部状态,例如它是否正在显示或已检查(this.pristine),如果它是具有地雷的图块之一(this.hasMine),不会确定它是否应该具有地雷。这将取决于逻辑层。(要进一步进入OOP,MineTile可以从通用Tile中继承)。

逻辑层

这应该包含应用程序与更改模式交互,保持状态等的复杂方式。因此,将在这里实现调解器模式以保持当前游戏的状态。例如,这就是游戏逻辑所在的位置,用于确定游戏期间发生的情况,或设置哪个MineTiles拥有地雷。它将调用实体层,以基于逻辑确定的参数获取实例化的级别。

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

服务层

这将是控制器可以访问的位置。它将有权访问用于构建游戏的逻辑层。可以在服务层中进行高层调用,以检索完全实例化的游戏或修改后的游戏状态。

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

控制者

控制器应该轻巧,从本质上讲,这就是模型的客户端所暴露的东西。将有许多控制器,因此构造它们将变得很重要。控制器函数调用将是基于UI事件的javascript调用命中的对象。这些应该公开服务层中可用的行为,然后填充或在这种情况下修改客户端的视图。

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

视图

视图应相对于控制器的行为进行组织。它们可能是应用程序中最密集的部分,因为它处理画布。

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

因此,现在您已经为这一游戏设置了完整的MVC。或者至少是一个空洞的例子,把整个游戏写出来是多余的。

一旦完成所有这些操作,就需要在某个地方为应用程序提供全局作用域。这将保留当前控制器的生命周期,该控制器是此方案中所有MVC堆栈的网关。

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

使用MVC模式非常强大,但是不必太担心遵循它们的每一个细微差别。最后,游戏体验将决定应用程序是否成功:)

考虑:不要让建筑宇航员吓到您了Joel Spolsky


谢谢@TravisJ-我赞扬它与游戏相关的MVC很好的解释。正如您所说,我仍然不清楚某些方面,我陷入了模式细微差别的泥潭,这阻止了我前进。我看到的一件事是在控制器中使用this.view.Select()-这种紧密耦合是必需的还是有进一步解耦的方法?
wigglyworm

@wigglyworm-总会有更多的解耦!:D但是,实际上,控制器应该是与模型进行通信然后更新视图的控制器,因此这可能是MVC中大多数耦合发生的地方。
特拉维斯J

2

这是您已经做错了的事情-您在混乱状态下手动滚动了MVC,没有任何MVC在您的控制之下。

看看PureMVC,它与语言无关,并且可以是一个很好的平台,可以让您真正地做MVC。

它的代码小巧易懂,可让您根据需要进行调整。

开始用它编写一个简单的小游戏,地雷扫雷会很好。特拉维斯·J(Travis J)所说的很多话都很好,尤其是关于Model。我只想补充一点,您需要记住控制器(至少在PureMvc中)是无状态的,它们应运而生,完成它们的Brief工作并消失。他们是知识渊博的人。它们就像函数。“填充网格,更改模型,”“更新模型,按下按钮”

视图(PureMVC中的调解器)最笨拙,而模型则稍微聪明一点。两者都抽象了实现,因此您(控制器)从不直接接触UI或DB。

用户界面的每个元素(例如在winforms应用程序中)都有一个视图(介体-您现在知道为什么这是个更好的术语吗?),但也可以将调解器用于元关注点,例如“控件颜色”或“焦点”管理器”,可跨UI元素进行操作。在这里分层思考。

UI和DB事件可以自动调用Controller(如果使用智能命名方案),并且某些Controller可以被淘汰-可以使Mediator直接侦听Model Data更改事件并传递其数据包。

尽管这是一种作弊,并且需要模型对那里的内容有所了解,而调解器也要了解如何处理数据包,但是在许多情况下,它可以使您避免被平凡的Controller所淹没。

型号:哑但可重复使用;控制器:智能但不易重复使用(它们是应用程序);调解人:愚蠢但可重复使用。在这种情况下,可重用性意味着可以移植到另一个应用程序。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.