import SpriteBase from "../sprite/base";
import {Sprite} from "../sprite";
/**
* TileMap element.
*
* @class
* @memberof Pixel
* @augments Pixel.EXPORTS.SpriteBase
*/
export default class Map extends SpriteBase {
/**
* Initiates new Tilemap.
*
* @class
* @param {Pixel.SpriteSheet#sheet|Pixel.SpriteSheet#generateSheet} sheet - The spritesheet used for the tilemap.
* @param {object} data - The data object for the tilemap.
* @param {number} data.width - The width of the spritesheet (in tile numbers).
* @param {number} data.height - The height of the spritesheet (in tile numbers).
* @param {number} data.tileHeight - Height of a tile.
* @param {number} data.tileWidth - Width of a tile.
*/
constructor(sheet, data) {
super();
/**
* Stores the sheet.
*
* @private
* @name Pixel.Map#sheet
* @type {Pixel.SpriteSheet#sheet|Pixel.SpriteSheet#generateSheet}
*/
this.sheet = sheet;
/**
* Stores the data.
*
* @private
* @name Pixel.Map#data
* @type {object}
*/
this.data = data;
/**
* Tilemap array.
*
* @name Pixel.Map#tiles
* @type {number[][]}
*/
this.tiles = [];
/**
* Tiles excluded from collisions.
*
* @private
* @name Pixel.Map#exclude
* @type {number[]}
*/
this.exclude;
/**
* Tile IDS that can collide.
*
* @private
* @name Pixel.Map#colliders
* @type {number[]}
*/
this.colliders = [];
}
/**
* Checks if tile should be added to collide list.
*
* @private
* @function Pixel.Map#_includeCollider
* @param {number} id - ID of tile.
* @param {HTMLCanvasElement} sprite - Canvas element (sprite) to be either added or excluded.
*/
_includeCollider(id, sprite) {
if (this.exclude) {
let shouldInclude = this.exclude.indexOf(id) > -1 ? false : true;
if (shouldInclude) {this.colliders.push(sprite);}
}
}
/**
* Applies a pre-determined tileset to the tilemap.
*
* @function Pixel.Map#applyTileset
* @param {number[][]} tiles - Array of tiles.
*/
applyTileset(tiles) {
let layers = this.data.height;
let cols = this.data.width;
let self = this;
for (var lay = 0; lay < layers; lay++) {
for (var col = 0; col < cols; col++) {
let tile = tiles[lay][col];
let sprite = this.sheet[tile] ? new Sprite({image: self.sheet[tile], renderable: true}) : false;
this.tiles.push(sprite);
if (sprite) {this._includeCollider(tile, sprite);}
}
col = 0;
}
}
/**
* Generates a blank tilemap.
*
* @function Pixel.Map#generateBlankMap
*/
generateBlankMap() {
for (var lay = 0; lay < this.data.height; lay++) {
for (var col = 0; col < this.data.width; col++) {
this.tiles.push(false);
}
}
}
/**
* Sets collisions by excluding x ids.
*
* @function Pixel.Map#collideByExclusion
* @param {number[]} ids - IDs of every tile that cannot collide.
*/
collideByExclusion(ids) {
this.exclude = ids;
}
/**
* Checks collisions with a sprite.
*
* @function Pixel.Map#checkCollisions
* @param {Pixel.Sprite} rect2 - Sprite that tilemap checks to see if it is colliding with.
*/
checkCollisions(rect2) {
let ret = {body: false, top: false, bottom: false, left: false, right: false};
for (let i = 0; i < this.colliders.length; i++) {
let rect1 = this.colliders[i];
let col = rect1.checkCollisions(rect2);
ret.body = col.body === true || ret.body === true ? true : false;
ret.left = col.left === true || ret.left === true ? true : false;
ret.right = col.right === true || ret.right === true ? true : false;
ret.bottom = col.bottom === true || ret.bottom === true ? true : false;
ret.top = col.top === true || ret.top === true ? true : false;
}
return ret;
}
/**
* Gives random item in array based on each weight.
*
* @function Pixel.Map#_match
* @private
* @param {number} max - The total weight.
* @param {object[]} array - Every item.
* @returns {object[]}
*/
_match(max, array) {
let rand = Math.random() * max;
var sum = 0;
var randomIndex = -1;
for (var j = 0; j < array.length; j++) {
sum += array[j].weight;
if (rand <= sum) {
var chosen = array[j].index;
randomIndex = Array.isArray(chosen) ? chosen[Math.floor(Math.random() * chosen.length)] : chosen;
break;
}
}
return randomIndex;
}
/**
* Randomly places tiles based on weight.
*
* @function Pixel.Map#weightedRandomize
* @param {number} gx - Starting tile x.
* @param {number} gy - Starting tile y.
* @param {number} w - Tile width (amount going across).
* @param {number} h - Tile height (amount going down).
* @param {object[]} index - All indexes + weights of ids to place.
*
* @example
* // Example of weightedRandomize use, where map is a Pixel.Map
* map.weightedRandomize(10, 10, 10, 10, [{index: 4, weight: 3}, {index: 10, weight: 0.5}]);
*/
weightedRandomize(gx, gy, w, h, index) {
let bias = index.map(a => {
return a.weight;
});
let max = 0;
bias.forEach(r => {
max += r;
});
let self = this;
for (let y = gy; y <= gy + h; y++) {
for (let x = gx; x <= gx + w; x++) {
let til = this._match(max, index);
this.tiles[x + y * this.data.height] = new Sprite({image: self.sheet[til], renderable: true});
this._includeCollider(til, this.tiles[x + y * this.data.height]);
}
}
}
/**
* Places single tile at TileX and TileY.
*
* @function Pixel.Map#placeTile
* @param {number} id - ID of tile.
* @param {number} x - TileX of tile.
* @param {number} y - TileY of tile.
*/
placeTile(id, x, y) {
let self = this;
this.tiles[x + y * this.data.height] = new Sprite({image: self.sheet[id], renderable: true});
this._includeCollider(id, this.tiles[x + y * this.data.height]);
}
/**
* Converts tile to world x.
*
* @function Pixel.Map#tileToWorldX
* @param {number} x - TileX.
*/
tileToWorldX(x) {
return (((this.data.width * this.data.tileWidth) / 2) * -1) + x * this.data.tileWidth;
}
/**
* Converts tile to world y.
*
* @function Pixel.Map#tileToWorldY
* @param {number} y - TileY.
*/
tileToWorldY(y) {
return (((this.data.height * this.data.tileHeight) / 2) * -1) + y * this.data.tileHeight;
}
/**
* Places multiple tiles horizontally + vertically based on array.
*
* @function Pixel.Map#placeTiles
* @param {number[]|number[][]} tilesArray - Array of tiles to be placed.
* @param {number} x - X position of first tile.
* @param {number} y - Y position of first tile.
*
* @example
* // Create new map
* var map = new Pixel.Map(Pixel.SpriteSheet.generateSheet(), { // Use an existing sprite sheet to pass to the map
* width: 48,
* height: 48,
* tileWidth: 48,
* tileHeight: 48
* });
*
* // Generate a blank map
* map.generateBlankMap();
*
* // Place tiles on the map
* map.placeTiles([ [10], [50], [32] ], 5, 5); // Is vertically, starting at tile index 5, 5
* map.placeTiles([ 10, 50, 32 ], 5, 5); // Is horizontally, starting at tile index 5, 5
*/
placeTiles(tilesArray, x, y) {
if (!Array.isArray(tilesArray[0])) {
tilesArray = [tilesArray];
}
var height = tilesArray.length;
var width = tilesArray[0].length;
for (var ty = 0; ty < height; ty++) {
for (var tx = 0; tx < width; tx++) {
var tile = tilesArray[ty][tx];
this.placeTile(tile, x + tx, y + ty);
}
}
}
/**
* Fills area with 1 tile.
*
* @function Pixel.Map#fill
* @param {number} id - ID of tile.
* @param {number} gx - Start x of fill.
* @param {number} gy - Start y of fill.
* @param {number} w - Width of fill.
* @param {number} h - Height of fill.
*/
fill(id, gx, gy, w, h) {
for (let y = gy; y < gy + h; y++) {
for (let x = gx; x < gx + w; x++) {
this.placeTile(id, x, y);
}
}
}
/**
* Renders the tilemap.
*
* @function Pixel.Map#render
* @param {CanvasRenderingContext2d} ctx - The Canvas to print to.
*/
render(ctx) {
let x = ((this.data.width * this.data.tileWidth) / 2) * -1;
let y = ((this.data.height * this.data.tileHeight) / 2) * -1;
for (var til in this.tiles) {
let tile = this.tiles[til];
if (tile) {
tile.x = x + this.x;
tile.y = y + this.y;
tile.render(ctx);
}
x += this.data.tileWidth;
if (x > this.data.tileWidth * this.data.width / 2 - this.data.tileWidth) {
x = ((this.data.width * this.data.tileWidth) / 2) * -1;
y += this.data.tileHeight;
}
}
}
}