在MVC javascript游戏中正确处理多个视图

时间:2014-01-19 00:40:19

标签: javascript design-patterns model-view-controller separation-of-concerns

我正在制作一个简单的回合制太空游戏。玩家与代表星系/可玩区域的2D地图交互。地图由扇区组成,每个扇区可能包含许多行星。玩家可以通过将航天器移动到每个扇区并根据需要进行殖民化来与地图进行交互。

所以,我的模型层的一部分看起来像这样(我在这里过于简化只是为了简短):

function Map() {

    // An array of Sectors
    this.sectors = [];
    . . .
}

function Sector() {

    // Array of Planets
    this.planets = [];

    // Array of player built space stations
    . . .
    this.spaceStations = [];
}

function Planet() {

    // Array of player built buildings
    this.structures = []
    . . .
}

游戏目前有两个视图,一个呈现可见世界的MapView:

function MapView() {

    // @param _map The game Map object
    this.render = function(_map) {

        this.canvas = new Canvas();

        // Camera can examine the Map to find the part that is currently visible on screen
        this.camera = new Camera();
        . . .

        foreach(_map.sectors as sector) {

            LOTS of code and drawImage commands here to . . .
            -ask camera if each sector is in currently in view (no point drawing the entire map)
            -draw each sector background image (star field or nebula or whatever helps make the game look more natural)
            -draw sector boundary lines so we end up with a nice nasty grid over the whole map
            -determine what planet sprite image to draw depending on planet type (i.e ROCK, EARTH, GAS)
            -draw every planet in each sector (the player can watch these planet sprites ‘orbit’ their sun)
            -draw any player created structures in each sector
            -draw any ships currently in each sector
        }
    }
}

和一个ExamineSectorView,它呈现一个选定的扇区:

function ExamineSectorView() {

    // @param _sector A game Sector object
    this.render = function(_sector) {

        this.canvas = new AnotherCanvas();
        . . .
        not-so-much-but-growing-suspiciously-large-amount-of-code here to . . .
        - foreach { draw planets in currently selected sector (selected from Map) }
        - draw space stations
        - you get the idea
    }
}

views

我现在只有一个控制器,MapController。它在键盘/鼠标上创建事件监听器,以便播放器可以与Map交互,即:

  • 玩家点击MapView中的某个部门 - >查看ExamineSectorView中的扇区
  • 玩家使用箭头键 - >滚动地图相机。

启动时,控制器调用Map上的方法(生成它),创建视图并启动setInterval计时器以进行视图渲染。

我想要帮助的是我的两种观点。

我觉得他们做得太多了。

它们确实包含逻辑,但它只是与视图相关的东西,并且需要渲染 - 就像10开关语句来确定行星类型并绘制适当的图像或者行星的'.size'属性的一点点乘法弄清楚它有多大。因此,我认为逻辑在视图中位于正确的位置。

问题是所有这些特定于视图的逻辑都会增加很多。我的观点已经失控,并希望以某种方式分离视图的每个方面的呈现。例如,像这样:

function MapView() {

    this.render = function() {

        this.canvas = new Canvas();

        // Camera can examine the Map to find the part that is currently visible on screen
        this.camera = new Camera();
        . . .

       foreach(_map.sectors as sector) {

           mapSectorView = new MapSectorView();
           mapSectorView.render(sector);
       }
    }
}

function MapSectorView() {

    this.render = function(_sector) {

        this.canvas = new Canvas();

        . . .

       foreach(sector.planets as planet) {

           planetView = new MapSectorPlanetView();
           planetView.render(planet);
       }
    }
}

function MapSectorPlanetView() {

    this.render = function(_planet) {

        this.canvas = new Canvas();

        . . .

       foreach(planet.structure as structure) {

           structureView = new StructureView();
           structureView.render(structure);
       }
    }
}

. . .

我已阅读本网站上的其他帖子,状态视图不应相互创建。如果是这样,上面的替代方法是在控制器中创建所有必需的视图并开始传递它们;即

mapView.render(mapModelObject, mapSectorView, mapPlanetView);

我不太确定这是解决这个问题的方法。

我可以想象这个游戏会很快变大,我知道在我开发游戏时视图会变得更加复杂,所以我很欣赏任何有关如何在这样的MVC游戏中管理渲染的建议,尤其是关于在JS /画布环境中分离关注点。

我刚刚找到:MVC: Data Models and View Models

我想知道这是否是一个可行的解决方案;创建一些视图模型,即MapViewModel,它包含所需的所有视图并呈现每个视图。

谢谢!

1 个答案:

答案 0 :(得分:0)

我认为ViewModel方法是可行的方法。

我现在有以下内容:

UniverseDomainObject(以及其他各种Domain对象,如'Planet')

  • 创建域名;星星,行星等(这里没有像素值,即位置在0.0 - 1.0范围内)

<强> GameController

  • 实例化UniverseDomainObject
  • 实例化MainMapViewModel
  • 在每个游戏渲染循环中调用MainMapViewModel.prepare(universeDomainObject)
  • 从MainMapViewModel侦听(玩家输入)事件并适当调用域方法

<强> MainMapViewModel

  • 实例化与渲染器无关的摄像头(基本上是一组方法/属性,以便于处理'地图'滚动/缩放)
  • 实例化MainMapView - 传递摄像头
  • 根据型号'尺寸'
  • 计算总地图尺寸(以像素为单位)
  • 设置精灵的基本像素大小(starSizeInPixels,planetSizeInPixels等)
  • 在每个渲染周期中:

    • 决定要绘制的项目(在相机视图中)
    • 计算要绘制的每个项目的像素位置/大小;基于模型位置,像素大小和相机位置/缩放
    • 创建一个POJO数组 - 精灵 - 包含每个对象的计算位置/大小和图像
    • 调用MainMapView.render(spritesList,_cameraData)
    • 收听输入并将适当的事件发布到GameController

<强> MainMapView

  • 使用相机尺寸创建画布
  • 在每个渲染周期中:

    • 迭代传递精灵列表并将每个图像渲染到画布

到目前为止,此解决方案运行良好。当我添加更多视图(即SectorView)时,MainMapViewModel可能仍然会变得非常大,但它确实让我能够让MainMapView保持完全愚蠢。

MainMapView只迭代传递的POJO列表并将每个'精灵'渲染到它自己的画布上。它还具有非常少量的特定于画布的旋转代码,以使行星围绕它们的恒星运行。

MainMapViewModel完全没有意识到MainMapView中的画布逻辑。这很酷;我可以在没有太多问题的情况下切换到OpenGL渲染器。

我正在使用一个简单的Observable / Listener方法向GameController发送事件;因此,它现在更专注于“游戏”事件,而不是特定的按钮输入。

我正在考虑将MainMapViewModel分成不同的类。目前,它准备了从星系和恒星到卫星和空间站的各种渲染数据。将这些东西放入单独的类而不是一个大块代码中会很好。另一方面,它准备的渲染数据是视图正确渲染所需的所有内容,因此我认为它确实有一个单一的责任。

我很感激任何评论,特别是关于我的MainMapViewModel的实现和职责。承担过多责任吗?我在罢工。