I am writing a JavaScript game that provides a top down view of the player, to eventually create a ray caster that can render the view in faux 3D (the top down view will end up as the mini-map).
A while ago, I asked the question Should actors in a game be responsible for drawing themselves?, to which I received the suggestion of having my game objects have a draw descriptor which provides the renderer with the information required to draw it.
I have a world
object and a renderer
object. I am trying to do this the right way, so I gave my world
object a getDrawDescription()
method which returns information required to render itself.
However, it's not quite as simple as a game actor, which may return an x
and y
offset and an image resource. My world's data is a matrix of information about cells, each with a number that relates to a different cell/texture. It may be best to show the implementation of my two objects (and some code to work with them)...
var world = {
cells: [
[/* ... */]
],
getDimensions: function() {
return { width: this.cells[0].length, height: this.cells.length };
},
getDrawDescription: function() {
return {
type: 'cells',
cellSize: 16,
data: this.cells
};
}
};
var renderer = {
objects: [],
width: 0,
height: 0,
ctx: null,
canvas: null,
addObj: function(obj) {
this.objects.push(obj);
},
setDimensions: function(width, height) {
this.width = width;
this.height = height;
},
setCanvas: function(canvas) {
this.canvas = canvas;
canvas.width = this.width;
canvas.height = this.height;
this.ctx = canvas.getContext("2d");
},
draw: function() {
var ctx = this.ctx;
this.objects.forEach(function(obj) {
var drawingDescription = obj.getDrawDescription();
switch(drawingDescription.type) {
case 'cells':
var cellSize = drawingDescription.cellSize;
drawingDescription.data.forEach(function(row, y) {
row.forEach(function(cell, x) {
// Obviously, this is a major violation of having
// the object provide all the drawing info.
var fillStyles = {
0: "#ccc",
1: "#000",
2: "#776",
3: "#fr2",
4: "#f0f"
};
ctx.fillStyle = fillStyles[cell];
ctx.fillRect(x * cellSize, y * cellSize, cellSize, cellSize);
});
});
break;
}
});
}
};
var game = {
loop: function() {
renderer.draw();
setTimeout(this.loop.bind(this), 500);
},
init: function() {
renderer.addObj(world);
this.loop();
}
};
document.addEventListener("DOMContentLoaded", function() {
var dimensions = world.getDimensions();
var description = world.getDrawDescription();
renderer.setDimensions(dimensions.width * description.cellSize, dimensions.height * description.cellSize);
renderer.setCanvas(document.getElementsByTagName("canvas")[0]);
game.init();
});
As you can see, I wasn't sure how to provide the render with the information required to render the world. I could have a function such as getColorById()
, but it would require the renderer to know about the world's implementation (possibly OK?) and would require a lot of function calls (maybe a function with returns an object which provides the mappings would be OK).
So, how is this typically achieved in game development? It may be a case of YAGNI, but I figure if it provides a good separation of concerns, it's a win (I'm unlikely to switch the renderer from canvas
, though I could prove its usefulness by having it write elements with a background-color
, for example).
I'm still fairly new to game development, so help me out if I'm doing anything wrong on a more fundamental level.