1

I am making a simplistic turn-based space game. The player interacts with a 2D map which represents the galaxy/playable area. The map is made up of Sectors and each Sector may contain a number of Planets. Players can interact with the map by moving spacecraft to each sector and colonising as desired.

So, part of my model layer looks like this (I’m oversimplifying here just to be brief):

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 = []
    . . .
}

The game currently has two views, a MapView which renders the visible universe:

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

and a ExamineSectorView, which renders a selected sector:

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

I just have one controller right now, MapController. It creates Event listeners on the keyboard/mouse so that the player can interact with the Map, i.e:

  • player clicks on a sector in MapView -> view that sector in the ExamineSectorView
  • player uses the arrow keys -> scroll the map camera.

At startup, the controller calls methods on the Map (to generate it), creates the views and starts a setInterval timer for view rendering.

What I would like help with is my two views.

I feel that they are both doing far too much.

They do contain logic, but it is view-relevant stuff only and required for the render - like a 10-switch statement to determine a planet type and draw the appropriate image or a little multiplication of a planet's '.size' property to work out how big to draw it. So I think that logic is in the correct place, in the view.

The trouble is that all this view-specific logic adds up to a lot. My view is getting out of hand and would like to separate the rendering of each aspect of the views somehow. For example, like this:

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

. . .

I’ve read other posts on this site that state views should not create one another. If so, an alternative to the above would be to create all required views in the controller and start passing them around; i.e

mapView.render(mapModelObject, mapSectorView, mapPlanetView);

I’m not too sure that is the way to solve this issue either.

I can imagine this game is going to get big fairly quickly and I know the view will get much more complex as I develop the game so I’d appreciate any advice on how to manage rendering in a MVC game like this, especially regarding separation of concerns in a JS/canvas environment.

I just found: MVC: Data Models and View Models

I'm wondering if that is a possible solution here; create some viewmodels i.e MapViewModel that holds all the views it needs and renders each one.

Thanks!

Community
  • 1
  • 1
whoshotdk
  • 286
  • 2
  • 14

1 Answers1

0

I think the ViewModel method is the way to go.

I now have the following:

UniverseDomainObject (and other miscellaneous Domain objects like 'Planet')

  • Creates domain; Stars, Planets etc (no pixel values here, i.e positions are in range 0.0 - 1.0)

GameController

  • Instantiates UniverseDomainObject
  • Instantiates MainMapViewModel
  • Calls MainMapViewModel.prepare(universeDomainObject) on every game render loop
  • Listens for (player input) events from MainMapViewModel and calls domain methods appropriately

MainMapViewModel

  • Instantiates a renderer-agnostic camera (essentially a set of methods/properties for easier handling of 'map' scrolling/zooming)
  • Instantiates MainMapView - passes camera
  • Calculates total map size in pixels based on model 'size'
  • Sets basic pixel sizes for sprites (starSizeInPixels, planetSizeInPixels etc)
  • On every render cycle:

    • Decide which items to draw (are they within camera's view)
    • Calculate pixel position/size of every item to draw; based on position from model, pixel size and camera position/zoom
    • Create an array of POJOs - sprites - containing calculated positions/sizes and image for each object
    • Calls MainMapView.render(spritesList, _cameraData)
    • Listens for input and publishes appropriate events to GameController

MainMapView

  • Creates canvas with camera dimensions
  • On every render cycle:

    • Iterate passed sprite list and render each image to canvas

This solution is working quite well so far. The MainMapViewModel probably will probably still grow quite large as I add more views (i.e SectorView) but it does allow me to keep MainMapView completely dumb.

MainMapView just iterates passed POJO list and renders each 'sprite' to it's own canvas. It also has a very little amount of canvas-specific rotation code to make the planets orbit their stars.

The MainMapViewModel is completely unaware of the canvas logic in the MainMapView. This is pretty cool; I could switch to OpenGL renderer without too many problems perhaps.

I'm using a simple Observable/Listener method to send events to GameController; as such it is now more tightly focused on 'Game' events rather than specific button input.

I'm considering separating the MainMapViewModel into different classes. At the moment it prepares render data for everything from galaxies and stars right down to moons and space stations. It'd be nice to get this stuff into separate classes rather than one big block o' code. On the other hand, the render data it prepares is everything required by the view to render correctly, so it kinda does have a single responsibility I think.

I'd appreciate any comments, especially regarding the implementation and responsibilities of my MainMapViewModel. Is it taking on too much responsibility or not? I'm on the fence.

whoshotdk
  • 286
  • 2
  • 14