/**
* Renderer handles rendering a map file to the canvas
* @param {stage} gamestage EaselJS stage
* @constructor
*/
function Renderer(gamestage) {
this.activeObjectsContainer = new createjs.Container();
this.backgroundContainer = new createjs.Container();
this.complexObjects = [];
this.container = new createjs.Container();
this.container.x = 0;
this.container.y = 0;
this.doneRendering = false;
this.fogOfWarContainer = new createjs.Container();
this.fogOfWarGrid = [[],[]];
this.foregroundContainer = new createjs.Container();
this.gamestage = gamestage; // this is a link to the parent gamestage
this.mapcounter = 0;
this.mapData = undefined; // this will be our tiled JSON data
this.imageData = undefined; // this will be the tileset graphic
this.simpleObjects = [];
this.tileset = new Image();
// new tickaction code:
this.moving = false;
this.moveToX = 0;
this.moveToY = 0;
this.movingObject = {};
this.movingToCellTarget = {};
this.movingToCellStart = {};
this.movementGraph = {};
this.movementSearchResult = [];
this.centered = true;
var fogOfWarSpriteSheet = new createjs.SpriteSheet({
"images": [loader.getResult("fogofwar")],
"frames": {
"width": 16,
"height": 16,
"count": 0
},
"animations": {
"still": {
"frames": [0],
"next": "still",
"speed": 2
}
}
});
/**
* Run at the end of our map rendering process. Adds various containers to the gamestage and
* declares doneRendering to be true, which lets the rest of the program continue.
* @private
*/
function completeRenderer() {
this.gamestage.removeAllChildren(); // clear the gamestage since we are ready to render.
this.doneRendering = true;
this.gamestage.addChild(this.backgroundContainer);
this.gamestage.addChild(this.container);
this.gamestage.addChild(this.foregroundContainer);
this.gamestage.addChild(this.activeObjectsContainer);
this.gamestage.addChild(this.fogOfWarContainer);
this.beginCaching(this.backgroundContainer);
this.beginCaching(this.container);
this.beginCaching(this.foregroundContainer);
if (LOG_FPS) {
renderer.activeObjectsContainer.addChild(fpsLabel);
}
// this is kind of a hack. what it does is it waits approx 2 frames before caching fogofwar container
// this forces the fog of war to be removed from around the player's initial location
setTimeout(function() {
this.beginCaching(this.fogOfWarContainer);
}.bind(this), 120);
};
/**
* Parses the mapData tilesets and Initialize all of the layers in the map
* @private
*/
function initLayers() {
console.log('initing layers');
var w = this.mapData.tilesets[0].tilewidth;
var h = this.mapData.tilesets[0].tileheight;
this.simpleobjects = [];
this.complexObjects = [];
var imageData = {
images : [ this.imageData ],
frames : {
width : w,
height : h
}
};
this.container = new createjs.Container();
// create spritesheet
var tilesetSheet = new createjs.SpriteSheet(imageData);
// loading each layer one at a time
for (var i = 0; i < this.mapData.layers.length; i++) {
var layer = this.mapData.layers[i];
if (layer.type === 'tilelayer') { // one of these for each tile layer we add in tiled
if (i === 0) { // background 2
this.backgroundContainer.addChild(initDrawableLayer.call(this, layer, tilesetSheet, this.mapData.tilewidth, this.mapData.tileheight));
} else if (i === 1) { // background 1
this.backgroundContainer.addChild(initDrawableLayer.call(this, layer, tilesetSheet, this.mapData.tilewidth, this.mapData.tileheight));
} else if (i === 2) { // simpleobjects
this.activeObjectsContainer.addChild(initSimpleObjects.call(this, layer, tilesetSheet, this.mapData.tilewidth, this.mapData.tileheight));
} else if (i === 3) { // collision
this.container.addChild(initDrawableLayerWithCollisionArray.call(this, layer, tilesetSheet, this.mapData.tilewidth, this.mapData.tileheight));
} else if (i === 4) { // foreground
this.foregroundContainer.addChild(initDrawableLayer.call(this, layer, tilesetSheet, this.mapData.tilewidth, this.mapData.tileheight));
}
}
if (layer.type === 'objectgroup') { // this is the list of tiled objects, if any
for (var j = 0; j < layer.objects.length; j++) {
// this is where we will handle cases for object files
if (layer.objects[j].type === "StartPoint") {
new StartPoint(layer.objects[j].x, layer.objects[j].y, parseInt(layer.objects[j].properties.startpoint_id));
} else if (layer.objects[j].type === "MapLink") {
new MapLink(layer.objects[j].x, layer.objects[j].y, layer.objects[j].properties.graphic, layer.objects[j].properties.link, parseInt(layer.objects[j].properties.startpoint));
} else if (layer.objects[j].type === "DisableFogOfWar") {
new DisableFogOfWar();
}
}
}
}
initFogOfWar.call(this, this.mapData.layers[0], this.mapData.tilewidth, this.mapData.tileheight);
}
/**
* Initialization method for simple objects
* @private
* @param {Array} layerData Tiled JSON data
* @param {Image} tilesetSheet
* @param {number} tilewidth
* @param {number} tileheight
* @return {Container}
*/
function initSimpleObjects(layerData, tilesetSheet, tilewidth, tileheight) {
console.log('initing simple objects');
var container = new createjs.Container();
for (var y = 0; y < layerData.height; y++) {
for (var x = 0; x < layerData.width; x++) {
// layer data has single dimension array
var idx = x + y * layerData.width;
if (layerData.data[idx] !== 0) {
// each different simple object will have an idx entry here
if (layerData.data[idx] === 1) { // This is the chest (end game goal)
console.log('pushing endgame');
activeObjects.push(new EndGame(x * tilewidth, y * tileheight));
} else if (layerData.data[idx] === 2) {
console.log('pushing robot!');
activeObjects.push(new Robot(x * tilewidth, y * tileheight, 1));
} else if (layerData.data[idx] === 3) {
console.log('pushing dragon!');
activeObjects.push(new Dragon(x * tilewidth, y * tileheight, 1));
}
}
}
}
return container;
}
/**
* Draws a single visible layer to a container
* @private
* @param {Array} layerData Tiled JSON data
* @param {Image} tilesetSheet
* @param {number} tilewidth
* @param {number} tileheight
* @return {Container}
*/
function initDrawableLayer(layerData, tilesetSheet, tilewidth, tileheight) {
var container = new createjs.Container();
for (var y = 0; y < layerData.height; y++) {
for (var x = 0; x < layerData.width; x++) {
// create a new Bitmap for each cell
// layer data has single dimension array
var idx = x + y * layerData.width;
if (layerData.data[idx] !== 0) {
var cellBitmap = new createjs.Sprite(tilesetSheet);
// tilemap data uses 1 as first value, EaselJS uses 0 (sub 1 to load correct tile)
cellBitmap.gotoAndStop(layerData.data[idx] - 1);
cellBitmap.x = x * tilewidth;
cellBitmap.y = y * tileheight;
// add bitmap to gamestage
container.addChild(cellBitmap);
// internalgamestage.addChild(cellBitmap);
}
}
}
container.tickEnabled = false;
container.snapToPixel = true;
return container;
}
/**
* Draws fog of war
* @private
* @param {Array} layerData Tiled JSON data
* @param {Image} tilesetSheet
* @param {number} tilewidth
* @param {number} tileheight
* @return {Container}
*/
function initFogOfWar(layerData, tilewidth, tileheight) {
this.fogOfWarContainer = new createjs.Container();
this.fogOfWarGrid = new Array(layerData.height);
for (var y = 0; y < layerData.height; y++) {
this.fogOfWarGrid[y] = new Array(layerData.width);
for (var x = 0; x < layerData.width; x++) {
var fog = new createjs.Sprite(fogOfWarSpriteSheet);
this.fogOfWarGrid[y][x] = fog;
fog.x = x * tilewidth;
fog.y = y * tileheight;
// add bitmap to gamestage
this.fogOfWarContainer.addChild(fog);
}
}
this.fogOfWarContainer.tickEnabled = false;
this.fogOfWarContainer.snapToPixel = true;
}
/**
* Same as initDrawableLayer, but also builds the renderer's collision array
* @private
* @param {Array} layerData Tiled JSON data
* @param {Image} tilesetSheet
* @param {number} tilewidth
* @param {number} tileheight
* @return {Container}
*/
function initDrawableLayerWithCollisionArray(layerData, tilesetSheet, tilewidth, tileheight) {
var container = new createjs.Container();
collisionSystem.collisionArray = new Array(layerData.height);
for (var y = 0; y < layerData.height; y++) {
collisionSystem.collisionArray[y] = new Array(layerData.width);
for (var x = 0; x < layerData.width; x++) {
// create a new Bitmap for each cell
// layer data has single dimension array
var idx = x + y * layerData.width;
if (layerData.data[idx] !== 0) {
var cellBitmap = null;
cellBitmap = new createjs.Sprite(tilesetSheet);
// tilemap data uses 1 as first value, EaselJS uses 0 (sub 1 to load correct tile)
cellBitmap.gotoAndStop(layerData.data[idx] - 1);
cellBitmap.x = x * tilewidth;
cellBitmap.y = y * tileheight;
container.addChild(cellBitmap);
collisionSystem.collisionArray[y][x] = 0;
} else {
collisionSystem.collisionArray[y][x] = 1;
}
}
}
this.movementGraph = new Graph(collisionSystem.collisionArray);
container.tickEnabled = false;
container.snapToPixel = true;
return container;
};
/**
* Cleans up movement fields after a player's turn is done
* @private
*/
function cleanUpMovement() {
this.moving = false;
this.movingObject.cleanUpMovement();
this.movingObject = {};
this.moveToX = 0;
this.moveToY = 0;
this.movingToCellTarget = {};
this.movementSearchResult = [];
playerTurn = false;
}
/**
* Shifts the entire map except for the active player
* @private
* @param {number} xamount
* @param {number} yamount
*/
function shiftMap(xamount, yamount) {
// right here we will actually have to iterate over other players and watched objects not contained by the renderer and move them in the opposite direction as well
for (var i = 0; i < activeObjects.length; i++) {
if (activeObjects[i] !== this.movingObject) {
activeObjects[i].animations.x -= xamount;
activeObjects[i].animations.y -= yamount;
}
}
this.backgroundContainer.x -= xamount;
this.backgroundContainer.y -= yamount;
this.container.x -= xamount;
this.container.y -= yamount;
this.foregroundContainer.x -= xamount;
this.foregroundContainer.y -= yamount;
this.fogOfWarContainer.x -= xamount;
this.fogOfWarContainer.y -= yamount;
}
/**
* Shifts the entire map without regard for active or inactive players
* @private
* @param {number} xamount
* @param {number} yamount
*/
function shiftEntireMap(xamount, yamount) {
for (var i = 0; i < activeObjects.length; i++) {
activeObjects[i].animations.x -= xamount;
activeObjects[i].animations.y -= yamount;
}
this.backgroundContainer.x -= xamount;
this.backgroundContainer.y -= yamount;
this.container.x -= xamount;
this.container.y -= yamount;
this.foregroundContainer.x -= xamount;
this.foregroundContainer.y -= yamount;
this.fogOfWarContainer.x -= xamount;
this.fogOfWarContainer.y -= yamount;
}
/**
* Forces caching on a container, caching the entire map size. disallows in animations in map
* @param {Container} container
*/
this.beginCaching = function(container) {
container.cache(0, 0, this.getMapWidth(), this.getMapHeight()); //this.mapData.tilesets[0].tileheight * this.mapData.layers[0].height);
};
/**
* Initializes the renderer, gets it ready to render a new map
* @public
*/
this.initMap = function () {
// getting imagefile from first tileset
if (this.mapData.tilesets[0].image.indexOf("..\/") > -1) {
this.mapData.tilesets[0].image = this.mapData.tilesets[0].image.replace("..\/", "");
}
this.tileset.src = this.mapData.tilesets[0].image;
initLayers.call(this);
completeRenderer.call(this);
this.doneRendering = true;
};
/**
* Reinitialize all of our variables here, gets us ready to draw a new map
* @param {Object} mapData new map JSON
*/
this.prepareRenderer = function(mapData) {
this.activeObjectsContainer = new createjs.Container();
this.backgroundContainer = new createjs.Container();
this.complexObjects = [];
this.container = new createjs.Container();
this.container.x = 0;
this.container.y = 0;
this.doneRendering = false;
this.foregroundContainer = new createjs.Container();
this.mapcounter = 0;
this.mapData = mapData; // this is our tiled JSON data
this.simpleObjects = [];
this.tileset = new Image();
this.backgroundContainer.x = 0;
this.backgroundContainer.y = 0;
this.container.x = 0;
this.container.y = 0;
this.foregroundContainer.x = 0;
this.foregroundContainer.y = 0;
this.fogOfWarContainer.x = 0;
this.fogOfWarContainer.y = 0;
};
/**
* Shifts the map in a certain way
* @param {Object} trackedObject
* @param {number} targetx
* @param {number} targety
* @return {boolean}
*/
this.moveObjectTo = function(trackedObject, targetx, targety) {
this.movingObject = trackedObject;
targetx -= this.container.x;
targety -= this.container.y;
if (!collisionSystem.checkCellValid(targetx, targety) ||
(collisionSystem.getCollisionCoordinateFromCell(trackedObject.x, trackedObject.y).x === collisionSystem.getCollisionCoordinateFromCell(targetx, targety).x &&
collisionSystem.getCollisionCoordinateFromCell(trackedObject.x, trackedObject.y).y === collisionSystem.getCollisionCoordinateFromCell(targetx, targety).y)) {
cleanUpMovement.call(this);
return false;
}
this.moveToX = targetx;
this.moveToY = targety;
this.movingToCellTarget = collisionSystem.getCollisionCoordinateFromCell(this.moveToX, this.moveToY);
this.movingToCellStart = collisionSystem.getCollisionCoordinateFromCell(this.movingObject.x, this.movingObject.y);
this.moving = true;
};
/**
* Gets the canvas width in pixels
* @return {number}
*/
this.getCanvasWidth = function() {
return gamestage.canvas.width;
};
/**
* Gets the canvas height in pixels
* @return {number}
*/
this.getCanvasHeight = function() {
return gamestage.canvas.height;
};
/**
* Tells the renderer to attempt to center on an object
* @public
* @param {Object} centeredObject
* @param {callback} centeringCallback
*/
this.centerMapOnObject = function(centeredObject, centeringCallback) {
this.centeredObject = centeredObject;
this.centeringCallback = centeringCallback;
this.centered = false;
};
/**
* centerMapOnObjectTick attempts to center the map on an object. returns true once the map is either centered
* or the map has reached an edge when trying to center
* @public
* @return {boolean} true if centered false if not
*/
this.centerMapOnObjectTick = function() {
if (tryCentering.call(this, this.centeredObject, MAP_MOVE_SPEED) === false) {
this.centeringCallback();
this.centered = true;
}
};
/**
* Attempts to center the screen, should get closer every frame
* @param {Object} obj
* @param {number} speed
* @return {boolean} false if the screen can't get more centered
*/
function tryCentering(obj, speed) {
var distanceX = distanceFromCenteredX.call(this, obj);
var distanceY = distanceFromCenteredY.call(this, obj);
if (Math.abs(distanceX) < (2 * speed)) {
distanceX = 0;
}
if (Math.abs(distanceY) < (2 * speed)) {
distanceY = 0;
}
var mapShiftX = 0;
var mapShiftY = 0;
if (distanceX > 0) { // we need to shift map right
mapShiftX = speed;
if (!canShiftMapRight.call(this, speed)) {
mapShiftX = 0;
}
} else if (distanceX < 0) { // we need to shfit map left
mapShiftX = -speed;
if (!canShiftMapLeft.call(this, speed)) {
mapShiftX = 0;
}
}
if (distanceY > 0) { // we need to shift map down
mapShiftY = speed;
if (!canShiftMapUp.call(this, speed)) {
mapShiftY = 0;
}
} else if (distanceY < 0) { // we need to shift map up
mapShiftY = -speed;
if (!canShiftMapDown.call(this, speed)) {
mapShiftY = 0;
}
}
if (mapShiftX === 0 && mapShiftY === 0) {
return false;
}
shiftEntireMap.call(this, mapShiftX, mapShiftY);
}
/**
* Returns distance from the center of the screen
* positive means needs containers need to shift right by half returned number of pixels to be centered
* positive means needs object need to shift left by half returned number of pixels to be centered
* @param {Object} obj
* @return {number}
*/
function distanceFromCenteredX(obj) {
return (obj.x + obj.spriteSheet._frameWidth) + renderer.container.x - (gamestage.canvas.width / 2);
}
/**
* Returns true if the map can shift left, false if not
* @param {number} speed
* @return {boolean}
*/
function canShiftMapLeft(speed) {
return renderer.container.x + (speed / 2) < 0;
}
/**
* Returns true if the map can shift right, false if not
* @param {number} speed
* @return {number}
*/
function canShiftMapRight(speed) {
return renderer.container.x + renderer.getMapWidth() - (speed / 2) > gamestage.canvas.width;
}
/**
* Returns distance from the center of the screen
* positive means containers needs to go up by half returned number of pixels to be centered
* positive means object needs to go down by half returned number of pixels to be centered
* @param {Object} obj
* @return {number}
*/
function distanceFromCenteredY(obj) {
return ((obj.y + obj.spriteSheet._frameHeight) + (renderer.container.y) - (gamestage.canvas.height / 2));
}
/**
* Returns true if the map can shift down, false if not
* @param {number} speed
* @return {boolean}
*/
function canShiftMapDown(speed) {
return renderer.container.y + (speed / 2) < 0;
}
/**
* Returns true if the map can shift up, false if not
* @param {number} speed
* @return {boolean}
*/
function canShiftMapUp(speed) {
return gamestage.canvas.height - this.container.y + speed < this.getMapHeight();
}
/**
*Function to handle moving objects from startxy to movingToCellTarget
* @public
*/
this.movementTickActions = function() {
var startxy = collisionSystem.getCollisionCoordinateFromCell(this.movingObject.x, this.movingObject.y);
if (!this.movementSearchResult[0] || (startxy.x === this.movementSearchResult[0].x && startxy.y === this.movementSearchResult[0].y)) {
// this section basically means we are looking to move to a new cell
var start = this.movementGraph.grid[startxy.x][startxy.y];
var end = this.movementGraph.grid[this.movingToCellTarget.x][this.movingToCellTarget.y];
this.movementSearchResult = astar.search(this.movementGraph, start, end);
// update our fog of war
if (this.movingObject.constructor === Player) {
this.fogOfWarContainer.uncache();
updateFogOfWar(this.movingObject);
this.beginCaching(this.fogOfWarContainer);
}
}
if ((this.movingToCellTarget.y === startxy.y && this.movingToCellTarget.x === startxy.x) || !this.movementSearchResult || !this.movementSearchResult[0]) {
cleanUpMovement.call(this);
return;
}
var deltax = this.movementSearchResult[0].x - startxy.x;
var deltay = this.movementSearchResult[0].y - startxy.y;
this.movingObject.updateMovementAnimation(deltax, deltay);
this.movingObject.animations.x += deltax * this.movingObject.moveSpeed;
this.movingObject.animations.y += deltay * this.movingObject.moveSpeed;
this.movingObject.x += deltax * this.movingObject.moveSpeed;
this.movingObject.y += deltay * this.movingObject.moveSpeed;
tryCentering.call(this, this.movingObject, this.movingObject.moveSpeed);
};
/**
* Returns map width
* @public
* @return {number}
*/
this.getMapWidth = function() {
return this.mapData.tilewidth * (this.mapData.width);
};
/**
* Returns map width
* @public
* @return {number}
*/
this.getMapHeight = function() {
return this.mapData.tileheight * (this.mapData.height);
};
/**
* Sets renderer's imageData
* @param {Image} image
*/
this.setImage = function(image) {
this.imageData = image;
}
}