UNPKG

shaku

Version:

A simple and effective JavaScript game development framework that knows its place!

185 lines (172 loc) 6.06 kB
/** * Implement collision tilemap. * * |-- copyright and license --| * @module Shaku * @file shaku\src\collision\shapes\tilemap.js * @author Ronen Ness (ronenness@gmail.com | http://ronenness.com) * @copyright (c) 2021 Ronen Ness * @license MIT * |-- end copyright and license --| * */ 'use strict'; const CollisionShape = require("./shape"); const Rectangle = require("../../utils/rectangle"); const Vector2 = require("../../utils/vector2"); const gfx = require('./../../gfx'); const RectangleShape = require("./rectangle"); /** * Collision tilemap class. * A collision tilemap shape is a grid of equal-sized cells that can either block or not (+ have collision flags). * Its the most efficient (both memory and CPU) way to implement grid based / tilemap collision. */ class TilemapShape extends CollisionShape { /** * Create the collision tilemap. * @param {Vector2} offset Tilemap top-left corner. * @param {Vector2} gridSize Number of tiles on X and Y axis. * @param {Vector2} tileSize The size of a single tile. * @param {Number} borderThickness Set a border collider with this thickness. */ constructor(offset, gridSize, tileSize, borderThickness) { super(); borderThickness = borderThickness || 0; this._offset = offset.clone(); this._intBoundingRect = new Rectangle(offset.x, offset.y, gridSize.x * tileSize.x, gridSize.y * tileSize.y); this._boundingRect = this._intBoundingRect.resize(borderThickness * 2); this._center = this._boundingRect.getCenter(); this._radius = this._boundingRect.getBoundingCircle().radius; this._borderThickness = borderThickness; this._gridSize = gridSize.clone(); this._tileSize = tileSize.clone(); this._tiles = {}; } /** * @inheritdoc */ get shapeId() { return "tilemap"; } /** * Get tile key from vector index. * Also validate range. * @private * @param {Vector2} index Index to get key for. * @returns {String} tile key. */ _indexToKey(index) { if (index.x < 0 || index.y < 0 || index.x >= this._gridSize.x || index.y >= this._gridSize.y) { throw new Error(`Collision tile with index ${index.x},${index.y} is out of bounds!`); } return index.x +',' + index.y; } /** * Set the state of a tile. * @param {Vector2} index Tile index. * @param {Boolean} haveCollision Does this tile have collision? * @param {Number} collisionFlags Optional collision flag to set for this tile. */ setTile(index, haveCollision, collisionFlags) { let key = this._indexToKey(index); if (haveCollision) { let rect = this._tiles[key] || new RectangleShape( new Rectangle( this._offset.x + index.x * this._tileSize.x, this._offset.y + index.y * this._tileSize.y, this._tileSize.x, this._tileSize.y) ); if (collisionFlags !== undefined) { rect.collisionFlags = collisionFlags; } this._tiles[key] = rect; } else { delete this._tiles[key]; } } /** * Get the collision shape of a tile at a given position. * @param {Vector2} position Position to get tile at. * @returns {RectangleShape} Collision shape at this position, or null if not set. */ getTileAt(position) { let index = new Vector2(Math.floor(position.x / this._tileSize.x), Math.floor(position.y / this._tileSize.y)); let key = index.x + ',' + index.y; return this._tiles[key] || null; } /** * Iterate all tiles in given region, represented by a rectangle. * @param {Rectangle} region Rectangle to get tiles for. * @param {Function} callback Method to invoke for every tile. Return false to break iteration. */ iterateTilesAtRegion(region, callback) { let topLeft = region.getTopLeft(); let bottomRight = region.getBottomRight(); let startIndex = new Vector2(Math.floor(topLeft.x / this._tileSize.x), Math.floor(topLeft.y / this._tileSize.y)); let endIndex = new Vector2(Math.floor(bottomRight.x / this._tileSize.x), Math.floor(bottomRight.y / this._tileSize.y)); for (let i = startIndex.x; i <= endIndex.x; ++i) { for (let j = startIndex.y; j <= endIndex.y; ++j) { let key = i + ',' + j; let tile = this._tiles[key]; if (tile && (callback(tile) === false)) { return; } } } } /** * Get all tiles in given region, represented by a rectangle. * @param {Rectangle} region Rectangle to get tiles for. * @returns {Array<RectangleShape>} Array with rectangle shapes or empty if none found. */ getTilesAtRegion(region) { let ret = []; this.iterateTilesAtRegion(region, (tile) => { ret.push(tile); }); return ret; } /** * @inheritdoc */ _getRadius() { return this._radius; } /** * @inheritdoc */ _getBoundingBox() { return this._boundingRect; } /** * @inheritdoc */ getCenter() { return this._center.clone(); } /** * @inheritdoc */ debugDraw(opacity, shapesBatch) { if (opacity === undefined) { opacity = 1; } for (let key in this._tiles) { let tile = this._tiles[key]; tile.setDebugColor(this._forceDebugColor); tile.debugDraw(opacity, shapesBatch); } } } // export collision shape class module.exports = TilemapShape;