UNPKG

phaser-ce

Version:

Phaser CE (Community Edition) is a fast, free and fun HTML5 Game Framework for Desktop and Mobile web browsers.

1,385 lines (1,183 loc) 74.8 kB
/** * @author Richard Davey <rich@photonstorm.com> * @copyright 2016 Photon Storm Ltd. * @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License} */ /** * Creates a new Phaser.Tilemap object. The map can either be populated with data from a Tiled JSON file or from a CSV file. * * Tiled is a free software package specifically for creating tile maps, and is available from http://www.mapeditor.org * * To do this pass the Cache key as the first parameter. When using Tiled data you need only provide the key. * When using CSV data you must provide the key and the tileWidth and tileHeight parameters. * If creating a blank tilemap to be populated later, you can either specify no parameters at all and then use `Tilemap.create` or pass the map and tile dimensions here. * Note that all Tilemaps use a base tile size to calculate dimensions from, but that a TilemapLayer may have its own unique tile size that overrides it. * A Tile map is rendered to the display using a TilemapLayer. It is not added to the display list directly itself. * A map may have multiple layers. You can perform operations on the map data such as copying, pasting, filling and shuffling the tiles around. * * @class Phaser.Tilemap * @constructor * @param {Phaser.Game} game - Game reference to the currently running game. * @param {string} [key] - The key of the tilemap data as stored in the Cache. If you're creating a blank map either leave this parameter out or pass `null`. * @param {number} [tileWidth=32] - The pixel width of a single map tile. If using CSV data you must specify this. Not required if using Tiled map data. * @param {number} [tileHeight=32] - The pixel height of a single map tile. If using CSV data you must specify this. Not required if using Tiled map data. * @param {number} [width=10] - The width of the map in tiles. If this map is created from Tiled or CSV data you don't need to specify this. * @param {number} [height=10] - The height of the map in tiles. If this map is created from Tiled or CSV data you don't need to specify this. */ Phaser.Tilemap = function (game, key, tileWidth, tileHeight, width, height) { /** * @property {Phaser.Game} game - A reference to the currently running Game. */ this.game = game; /** * @property {string} key - The key of this map data in the Phaser.Cache. */ this.key = key; var data = Phaser.TilemapParser.parse(this.game, key, tileWidth, tileHeight, width, height); if (data === null) { return; } /** * @property {number} width - The width of the map (in tiles). */ this.width = data.width; /** * @property {number} height - The height of the map (in tiles). */ this.height = data.height; /** * @property {number} tileWidth - The base width of the tiles in the map (in pixels). */ this.tileWidth = data.tileWidth; /** * @property {number} tileHeight - The base height of the tiles in the map (in pixels). */ this.tileHeight = data.tileHeight; /** * @property {string} orientation - The orientation of the map data (as specified in Tiled), usually 'orthogonal'. */ this.orientation = data.orientation; /** * @property {number} format - The format of the map data, either Phaser.Tilemap.CSV or Phaser.Tilemap.TILED_JSON. */ this.format = data.format; /** * @property {number} version - The version of the map data (as specified in Tiled, usually 1). */ this.version = data.version; /** * @property {object} properties - Map specific properties as specified in Tiled. */ this.properties = data.properties; /** * @property {number} widthInPixels - The width of the map in pixels based on width * tileWidth. */ this.widthInPixels = data.widthInPixels; /** * @property {number} heightInPixels - The height of the map in pixels based on height * tileHeight. */ this.heightInPixels = data.heightInPixels; /** * @property {array} layers - An array of Tilemap layer data. */ this.layers = data.layers; /** * @property {array} tilesets - An array of Tilesets. */ this.tilesets = data.tilesets; /** * @property {array} imagecollections - An array of Image Collections. */ this.imagecollections = data.imagecollections; /** * @property {array} tiles - The super array of Tiles. */ this.tiles = data.tiles; /** * @property {object} objects - Tiled Object Layers, by layer name. */ this.objects = data.objects; /** * @property {object} objectsMap - Tiled objects indexed by `id`. */ this.objectsMap = data.objectsMap; /** * @property {array} collideIndexes - An array of tile indexes that collide. */ this.collideIndexes = []; /** * @property {array} collision - An array of collision data (polylines, etc). */ this.collision = data.collision; /** * @property {array} images - An array of Tiled Image Layers. */ this.images = data.images; /** * @property {boolean} enableDebug - If set then console.log is used to dump out useful layer creation debug data. */ this.enableDebug = false; /** * @property {number} currentLayer - The current layer. */ this.currentLayer = 0; /** * @property {array} debugMap - Map data used for debug values only. */ this.debugMap = []; /** * @property {array} _results - Internal var. * @private */ this._results = []; /** * @property {number} _tempA - Internal var. * @private */ this._tempA = 0; /** * @property {number} _tempB - Internal var. * @private */ this._tempB = 0; }; /** * @constant * @type {number} */ Phaser.Tilemap.CSV = 0; /** * @constant * @type {number} */ Phaser.Tilemap.TILED_JSON = 1; /** * @constant * @type {number} */ Phaser.Tilemap.NORTH = 0; /** * @constant * @type {number} */ Phaser.Tilemap.EAST = 1; /** * @constant * @type {number} */ Phaser.Tilemap.SOUTH = 2; /** * @constant * @type {number} */ Phaser.Tilemap.WEST = 3; Phaser.Tilemap.prototype = { /** * Creates an empty map of the given dimensions and one blank layer. If layers already exist they are erased. * * @method Phaser.Tilemap#create * @param {string} name - The name of the default layer of the map. * @param {number} width - The width of the map in tiles. * @param {number} height - The height of the map in tiles. * @param {number} tileWidth - The width of the tiles the map uses for calculations. * @param {number} tileHeight - The height of the tiles the map uses for calculations. * @param {Phaser.Group} [group] - Optional Group to add the layer to. If not specified it will be added to the World group. * @return {Phaser.TilemapLayer} The TilemapLayer object. This is an extension of Phaser.Image and can be moved around the display list accordingly. */ create: function (name, width, height, tileWidth, tileHeight, group) { if (group === undefined) { group = this.game.world; } this.width = width; this.height = height; this.setTileSize(tileWidth, tileHeight); this.layers.length = 0; return this.createBlankLayer(name, width, height, tileWidth, tileHeight, group); }, /** * Sets the base tile size for the map. * * @method Phaser.Tilemap#setTileSize * @param {number} tileWidth - The width of the tiles the map uses for calculations. * @param {number} tileHeight - The height of the tiles the map uses for calculations. */ setTileSize: function (tileWidth, tileHeight) { this.tileWidth = tileWidth; this.tileHeight = tileHeight; this.widthInPixels = this.width * tileWidth; this.heightInPixels = this.height * tileHeight; }, /** * Adds an image to the map to be used as a tileset. A single map may use multiple tilesets. * Note that the tileset name can be found in the JSON file exported from Tiled, or in the Tiled editor. * * @method Phaser.Tilemap#addTilesetImage * @param {string} tileset - The name of the tileset as specified in the map data. * @param {string|Phaser.BitmapData} [key] - The key of the Phaser.Cache image used for this tileset. * If `undefined` or `null` it will look for an image with a key matching the tileset parameter. * You can also pass in a BitmapData which can be used instead of an Image. * @param {number} [tileWidth=32] - The width of the tiles in the Tileset Image. If not given it will default to the map.tileWidth value, if that isn't set then 32. * @param {number} [tileHeight=32] - The height of the tiles in the Tileset Image. If not given it will default to the map.tileHeight value, if that isn't set then 32. * @param {number} [tileMargin=0] - The width of the tiles in the Tileset Image. * @param {number} [tileSpacing=0] - The height of the tiles in the Tileset Image. * @param {number} [gid=0] - If adding multiple tilesets to a blank/dynamic map, specify the starting GID the set will use here. * @return {Phaser.Tileset} Returns the Tileset object that was created or updated, or null if it failed. */ addTilesetImage: function (tileset, key, tileWidth, tileHeight, tileMargin, tileSpacing, gid) { if (tileset === undefined) { return null; } if (tileWidth === undefined) { tileWidth = this.tileWidth; } if (tileHeight === undefined) { tileHeight = this.tileHeight; } if (tileMargin === undefined) { tileMargin = 0; } if (tileSpacing === undefined) { tileSpacing = 0; } if (gid === undefined) { gid = 0; } // In-case we're working from a blank map if (tileWidth === 0) { tileWidth = 32; } if (tileHeight === 0) { tileHeight = 32; } var img = null; if (key === undefined || key === null) { key = tileset; } if (Phaser.BitmapData && key instanceof Phaser.BitmapData) { img = key.canvas; } else { if (!this.game.cache.checkImageKey(key)) { console.warn('Phaser.Tilemap.addTilesetImage: Invalid image key given: "' + key + '"'); return null; } img = this.game.cache.getImage(key); } var idx = this.getTilesetIndex(tileset); if (idx === null && this.format === Phaser.Tilemap.TILED_JSON) { console.warn('Phaser.Tilemap.addTilesetImage: No data found in the JSON matching the tileset name: "' + tileset + '"'); console.log('Tilesets: ', this.tilesets); return null; } if (this.tilesets[idx]) { this.tilesets[idx].setImage(img); return this.tilesets[idx]; } else { var newSet = new Phaser.Tileset(tileset, gid, tileWidth, tileHeight, tileMargin, tileSpacing, {}); newSet.setImage(img); this.tilesets.push(newSet); var i = this.tilesets.length - 1; var x = tileMargin; var y = tileMargin; var count = 0; var countX = 0; var countY = 0; for (var t = gid; t < gid + newSet.total; t++) { this.tiles[t] = [ x, y, i ]; x += tileWidth + tileSpacing; count++; if (count === newSet.total) { break; } countX++; if (countX === newSet.columns) { x = tileMargin; y += tileHeight + tileSpacing; countX = 0; countY++; if (countY === newSet.rows) { break; } } } return newSet; } }, /** * Creates a Sprite for every {@link http://doc.mapeditor.org/reference/tmx-map-format/#object object} matching the `search` argument. * * - When `search` is a number, it matches the object's tile ID (`gid`). * - When `search` is a string, it matches the object's `name`. * - When `search` is an array like `['type', 'enemy']` it matches that property name and value on the object. * - When `search` is `null`, it matches every object. * * You can optionally specify the group that the Sprite will be created in. * If `undefined` is given it will be created in the World. * If `null` is given it won't be added to any group. * * All properties from the object are copied to the Sprite, so you can use this as an easy way to * configure Sprite properties from within the map editor. * For example giving an object a property of `alpha: 0.5` in the map editor will duplicate that when the * Sprite is created. You could also give it a value like: `body.velocity.x: 100` to set it moving automatically. * * @method Phaser.Tilemap#createFromObjects * @param {string} layer - The name of the Object Group (Object Layer) to create Sprites from. * @param {number|string|array|null} search - The search value (see above). * @param {string} key - The Game.cache key of the image that this Sprite will use. * @param {number|string} [frame] - If the Sprite image contains multiple frames you can specify which one to use here. * @param {boolean} [exists=true] - The default exists state of the Sprite. * @param {boolean} [autoCull=false] - The default autoCull state of the Sprite. Sprites that are autoCulled are culled from the camera if out of its range. * @param {Phaser.Group|null} [group=this.game.world] - Group to add the Sprite to, or `null` for no group. If `undefined` it will be added to the World group. * @param {object} [CustomClass=Phaser.Sprite] - If you wish to create your own class, rather than Phaser.Sprite, pass the class here. Your class must extend Phaser.Sprite and have the same constructor parameters. * @param {boolean} [adjustY=true] - By default the Tiled map editor uses a bottom-left coordinate system. Phaser uses top-left. So most objects will appear too low down. This parameter moves them up by their height. * @param {boolean} [adjustSize=true] - By default the width and height of the objects are transferred to the sprite. This parameter controls that behavior. * * @return {Phaser.Sprite[]} - The created Sprites. */ createFromObjects: function (layer, search, key, frame, exists, autoCull, group, CustomClass, adjustY, adjustSize) { if (exists === undefined) { exists = true; } if (autoCull === undefined) { autoCull = false; } if (group === undefined) { group = this.game.world; } if (CustomClass === undefined) { CustomClass = Phaser.Sprite; } if (adjustY === undefined) { adjustY = true; } if (adjustSize === undefined) { adjustSize = true; } var objects = this.objects[layer]; if (!objects) { console.warn('Tilemap.createFromObjects: Invalid object layer name given: ' + layer); console.log('Object layers: ', this.objects); return; } var foundObjects = []; var created = []; var searchType = typeof search; if (searchType === 'number') { this.getObjects(layer, 'gid', search, foundObjects); } else if (searchType === 'string') { this.getObjects(layer, 'name', search, foundObjects); } else if (Array.isArray(search)) { this.getObjects(layer, search[0], search[1], foundObjects); } else if (search === null) { foundObjects = objects; } for (var i = 0; i < foundObjects.length; i++) { var obj = foundObjects[i]; var sprite = new CustomClass(this.game, parseFloat(obj.x), parseFloat(obj.y), key, frame); sprite.name = obj.name; sprite.autoCull = autoCull; sprite.exists = exists; sprite.visible = obj.visible; if (adjustSize) { if (obj.width) { sprite.width = obj.width; } if (obj.height) { sprite.height = obj.height; } } if (obj.rotation) { sprite.angle = obj.rotation; } // Tile objects have origin (0, 1), all others (0, 0) <https://github.com/mapeditor/tiled/issues/91> if (adjustY && obj.gid) { sprite.y -= sprite.height; } if (group !== null) { group.add(sprite); } // Set properties directly on the sprite var properties = obj.properties; for (var propertyName in properties) { Phaser.Utils.setProperty(sprite, propertyName, properties[propertyName]); } created.push(sprite); } return created; }, /** * Creates a Sprite for every object matching the given tile indexes in the map data. * You can specify the group that the Sprite will be created in. If none is given it will be created in the World. * You can optional specify if the tile will be replaced with another after the Sprite is created. This is useful if you want to lay down special * tiles in a level that are converted to Sprites, but want to replace the tile itself with a floor tile or similar once converted. * * @method Phaser.Tilemap#createFromTiles * @param {integer|Array} tiles - The tile index, or array of indexes, to create Sprites from. * @param {integer|Array} replacements - The tile index, or array of indexes, to change a converted tile to. Set to -1 to remove the tile. Set to `null` to make no change (leave the tile as is). * @param {string} key - The Game.cache key of the image that this Sprite will use. * @param {number|string|Phaser.TilemapLayer} [layer] - The layer to operate on. * @param {Phaser.Group} [group=Phaser.World] - Group to add the Sprite to. If not specified it will be added to the World group. * @param {object} [properties] - An object that contains the default properties for your newly created Sprite. This object will be iterated and any matching Sprite property will be set. * @return {integer} The number of Sprites that were created. */ createFromTiles: function (tiles, replacements, key, layer, group, properties) { if (typeof tiles === 'number') { tiles = [ tiles ]; } if (replacements === undefined || replacements === null) { replacements = []; } else if (typeof replacements === 'number') { replacements = [ replacements ]; } layer = this.getLayer(layer); if (group === undefined) { group = this.game.world; } if (properties === undefined) { properties = {}; } if (properties.customClass === undefined) { properties.customClass = Phaser.Sprite; } if (properties.adjustY === undefined) { properties.adjustY = true; } var lw = this.layers[layer].width; var lh = this.layers[layer].height; this.copy(0, 0, lw, lh, layer); if (this._results.length < 2) { return 0; } var total = 0; var sprite; for (var i = 1, len = this._results.length; i < len; i++) { if (tiles.indexOf(this._results[i].index) !== -1) { sprite = new properties.customClass(this.game, this._results[i].worldX, this._results[i].worldY, key); for (var property in properties) { sprite[property] = properties[property]; } group.add(sprite); total++; } } if (replacements.length === 1) { // Assume 1 replacement for all types of tile given for (i = 0; i < tiles.length; i++) { this.replace(tiles[i], replacements[0], 0, 0, lw, lh, layer); } } else if (replacements.length > 1) { // Assume 1 for 1 mapping for (i = 0; i < tiles.length; i++) { this.replace(tiles[i], replacements[i], 0, 0, lw, lh, layer); } } return total; }, /** * Creates a new TilemapLayer object. By default TilemapLayers are fixed to the camera. * The `layer` parameter is important. If you've created your map in Tiled then you can get this by looking in Tiled and looking at the Layer name. * Or you can open the JSON file it exports and look at the layers[].name value. Either way it must match. * If you wish to create a blank layer to put your own tiles on then see Tilemap.createBlankLayer. * * @method Phaser.Tilemap#createLayer * @param {number|string} layer - The layer array index value, or if a string is given the layer name, within the map data that this TilemapLayer represents. * @param {number} [width] - The rendered width of the layer, should never be wider than Game.width. If not given it will be set to Game.width. * @param {number} [height] - The rendered height of the layer, should never be wider than Game.height. If not given it will be set to Game.height. * @param {Phaser.Group} [group] - Optional Group to add the object to. If not specified it will be added to the World group. * @return {Phaser.TilemapLayer} The TilemapLayer object. This is an extension of Phaser.Sprite and can be moved around the display list accordingly. */ createLayer: function (layer, width, height, group) { // Add Buffer support for the left of the canvas if (width === undefined) { width = this.game.width; } if (height === undefined) { height = this.game.height; } if (group === undefined) { group = this.game.world; } var index = layer; if (typeof layer === 'string') { index = this.getLayerIndex(layer); } if (index === null || index > this.layers.length) { console.warn('Tilemap.createLayer: Invalid layer ID given: "' + layer + '"'); console.log('Layers: ', this.layers); return; } // Sort out the display dimensions, so they never render too much, or too little. if (width === undefined || width <= 0) { width = Math.min(this.game.width, this.layers[index].widthInPixels); } else if (width > this.game.width) { width = this.game.width; } if (height === undefined || height <= 0) { height = Math.min(this.game.height, this.layers[index].heightInPixels); } else if (height > this.game.height) { height = this.game.height; } if (this.enableDebug) { console.group('Tilemap.createLayer'); console.log('Name:', this.layers[index].name); console.log('Size:', width, 'x', height); console.log('Tileset:', this.tilesets[0].name, 'index:', index); } var rootLayer = group.add(new Phaser.TilemapLayer(this.game, this, index, width, height)); if (this.enableDebug) { console.groupEnd(); } return rootLayer; }, /** * Creates a new and empty layer on this Tilemap. By default TilemapLayers are fixed to the camera. * * @method Phaser.Tilemap#createBlankLayer * @param {string} name - The name of this layer. Must be unique within the map. * @param {number} width - The width of the layer in tiles. * @param {number} height - The height of the layer in tiles. * @param {number} tileWidth - The width of the tiles the layer uses for calculations. * @param {number} tileHeight - The height of the tiles the layer uses for calculations. * @param {Phaser.Group} [group] - Optional Group to add the layer to. If not specified it will be added to the World group. * @return {Phaser.TilemapLayer} The TilemapLayer object. This is an extension of Phaser.Image and can be moved around the display list accordingly. */ createBlankLayer: function (name, width, height, tileWidth, tileHeight, group) { if (group === undefined) { group = this.game.world; } if (this.getLayerIndex(name) !== null) { console.warn('Tilemap.createBlankLayer: Layer with matching name already exists: ' + name); return; } var layer = { name: name, x: 0, y: 0, width: width, height: height, widthInPixels: width * tileWidth, heightInPixels: height * tileHeight, alpha: 1, visible: true, properties: {}, indexes: [], callbacks: [], bodies: [], data: null }; var row; var output = []; for (var y = 0; y < height; y++) { row = []; for (var x = 0; x < width; x++) { row.push(new Phaser.Tile(layer, -1, x, y, tileWidth, tileHeight)); } output.push(row); } layer.data = output; this.layers.push(layer); this.currentLayer = this.layers.length - 1; var w = layer.widthInPixels; var h = layer.heightInPixels; if (w > this.game.width) { w = this.game.width; } if (h > this.game.height) { h = this.game.height; } var output = new Phaser.TilemapLayer(this.game, this, this.layers.length - 1, w, h); output.name = name; return group.add(output); }, /** * Gets the layer index based on the layers name. * * @method Phaser.Tilemap#getIndex * @protected * @param {array} location - The local array to search. * @param {string} name - The name of the array element to get. * @return {number} The index of the element in the array, or null if not found. */ getIndex: function (location, name) { for (var i = 0; i < location.length; i++) { if (location[i].name === name) { return i; } } return null; }, /** * Gets the layer index based on its name. * * @method Phaser.Tilemap#getLayerIndex * @param {string} name - The name of the layer to get. * @return {number} The index of the layer in this tilemap, or null if not found. */ getLayerIndex: function (name) { return this.getIndex(this.layers, name); }, /** * Gets the object with the given `id`, from any Object Layer. * * @param {number} id - The `id` of the object. * * @return {?TilemapObject} The object, or null if not found. */ getObject: function (id) { return this.objectsMap[id] || null; }, /** * Gets objects matching the given property name and value from an Object Layer. * * @param {string} layer - The name of the Object Layer. * @param {string} propName - The name of the object property to match. * @param {any} propValue - The property value to match. * @param {array} [output] - An array to append matching objects to. * * @return {TilemapObject[]} - The matching objects. */ getObjects: function (layer, propName, propValue, output) { var objects = this.objects[layer]; var len = objects.length; if (output === undefined) { output = []; } for (var i = 0; i < len; i++) { var obj = objects[i]; if (obj[propName] === propValue) { output.push(obj); } } return output; }, /** * Gets the tileset index based on its name. * * @method Phaser.Tilemap#getTilesetIndex * @param {string} name - The name of the tileset to get. * @return {number} The index of the tileset in this tilemap, or null if not found. */ getTilesetIndex: function (name) { return this.getIndex(this.tilesets, name); }, /** * Gets the image index based on its name. * * @method Phaser.Tilemap#getImageIndex * @param {string} name - The name of the image to get. * @return {number} The index of the image in this tilemap, or null if not found. */ getImageIndex: function (name) { return this.getIndex(this.images, name); }, /** * Sets a global collision callback for the given tile index within the layer. This will affect all tiles on this layer that have the same index. * If a callback is already set for the tile index it will be replaced. Set the callback to null to remove it. * If you want to set a callback for a tile at a specific location on the map then see setTileLocationCallback. * * Return `true` from the callback to continue separating the tile and colliding object, or `false` to cancel the collision for the current tile (see {@link Phaser.Physics.Arcade#separateTile}). * * @method Phaser.Tilemap#setTileIndexCallback * @param {number|array} indexes - Either a single tile index, or an array of tile indexes to have a collision callback set for. * @param {function} callback - The callback that will be invoked when the tile is collided with (via {@link Phaser.Physics.Arcade#collide}). * @param {object} callbackContext - The context under which the callback is called. * @param {number|string|Phaser.TilemapLayer} [layer] - The layer to operate on. If not given will default to this.currentLayer. */ setTileIndexCallback: function (indexes, callback, callbackContext, layer) { layer = this.getLayer(layer); if (typeof indexes === 'number') { if (callback === null) { delete this.layers[layer].callbacks[indexes]; } else { /* * This may seem a bit wasteful, because it will cause empty array elements to be created, but the look-up cost is much * less than having to iterate through the callbacks array hunting down tile indexes each frame, so I'll take the small memory hit. */ this.layers[layer].callbacks[indexes] = { callback: callback, callbackContext: callbackContext }; } } else { for (var i = 0, len = indexes.length; i < len; i++) { if (callback === null) { delete this.layers[layer].callbacks[indexes[i]]; } else { this.layers[layer].callbacks[indexes[i]] = { callback: callback, callbackContext: callbackContext }; } } } }, /** * Sets a global collision callback for the given map location within the layer. This will affect all tiles on this layer found in the given area. * If a callback is already set for the tile index it will be replaced. Set the callback to null to remove it. * If you want to set a callback for a tile at a specific location on the map then see setTileLocationCallback. * * Return `true` from the callback to continue separating the tile and colliding object, or `false` to cancel the collision for the current tile (see {@link Phaser.Physics.Arcade#separateTile}). * * @method Phaser.Tilemap#setTileLocationCallback * @param {number} x - X position of the top left of the area to copy (given in tiles, not pixels) * @param {number} y - Y position of the top left of the area to copy (given in tiles, not pixels) * @param {number} width - The width of the area to copy (given in tiles, not pixels) * @param {number} height - The height of the area to copy (given in tiles, not pixels) * @param {function} callback - The callback that will be invoked when the tile is collided with (via {@link Phaser.Physics.Arcade#collide}). * @param {object} callbackContext - The context under which the callback is called. * @param {number|string|Phaser.TilemapLayer} [layer] - The layer to operate on. If not given will default to this.currentLayer. */ setTileLocationCallback: function (x, y, width, height, callback, callbackContext, layer) { layer = this.getLayer(layer); this.copy(x, y, width, height, layer); if (this._results.length < 2) { return; } for (var i = 1; i < this._results.length; i++) { this._results[i].setCollisionCallback(callback, callbackContext); } }, /** * Sets collision on the given tile or tiles. You can pass in either a single numeric index or an array of indexes: [2, 3, 15, 20]. * The `collides` parameter controls if collision will be enabled (true) or disabled (false). * * Collision-enabled tiles can be collided against Sprites using {@link Phaser.Physics.Arcade#collide}. * * You can verify the collision faces by enabling {@link Phaser.TilemapLayer#debug}. * * @method Phaser.Tilemap#setCollision * @param {number|array} indexes - Either a single tile index, or an array of tile IDs to be checked for collision. * @param {boolean} [collides=true] - If true it will enable collision. If false it will clear collision. * @param {number|string|Phaser.TilemapLayer} [layer] - The layer to operate on. If not given will default to this.currentLayer. * @param {boolean} [recalculate=true] - Recalculates the tile faces after the update. */ setCollision: function (indexes, collides, layer, recalculate) { if (collides === undefined) { collides = true; } if (recalculate === undefined) { recalculate = true; } layer = this.getLayer(layer); if (typeof indexes === 'number') { return this.setCollisionByIndex(indexes, collides, layer, true); } else if (Array.isArray(indexes)) { // Collide all of the IDs given in the indexes array for (var i = 0; i < indexes.length; i++) { this.setCollisionByIndex(indexes[i], collides, layer, false); } if (recalculate) { // Now re-calculate interesting faces this.calculateFaces(layer); } } }, /** * Sets collision on a range of tiles where the tile IDs increment sequentially. * Calling this with a start value of 10 and a stop value of 14 would set collision for tiles 10, 11, 12, 13 and 14. * The `collides` parameter controls if collision will be enabled (true) or disabled (false). * * @method Phaser.Tilemap#setCollisionBetween * @param {number} start - The first index of the tile to be set for collision. * @param {number} stop - The last index of the tile to be set for collision. * @param {boolean} [collides=true] - If true it will enable collision. If false it will clear collision. * @param {number|string|Phaser.TilemapLayer} [layer] - The layer to operate on. If not given will default to this.currentLayer. * @param {boolean} [recalculate=true] - Recalculates the tile faces after the update. */ setCollisionBetween: function (start, stop, collides, layer, recalculate) { if (collides === undefined) { collides = true; } if (recalculate === undefined) { recalculate = true; } layer = this.getLayer(layer); if (start > stop) { return; } for (var index = start; index <= stop; index++) { this.setCollisionByIndex(index, collides, layer, false); } if (recalculate) { // Now re-calculate interesting faces this.calculateFaces(layer); } }, /** * Sets collision on all tiles in the given layer, except for the IDs of those in the given array. * The `collides` parameter controls if collision will be enabled (true) or disabled (false). * * @method Phaser.Tilemap#setCollisionByExclusion * @param {array} indexes - An array of the tile IDs to not be counted for collision. * @param {boolean} [collides=true] - If true it will enable collision. If false it will clear collision. * @param {number|string|Phaser.TilemapLayer} [layer] - The layer to operate on. If not given will default to this.currentLayer. * @param {boolean} [recalculate=true] - Recalculates the tile faces after the update. */ setCollisionByExclusion: function (indexes, collides, layer, recalculate) { if (collides === undefined) { collides = true; } if (recalculate === undefined) { recalculate = true; } layer = this.getLayer(layer); // Collide everything, except the IDs given in the indexes array for (var i = 0, len = this.tiles.length; i < len; i++) { if (indexes.indexOf(i) === -1) { this.setCollisionByIndex(i, collides, layer, false); } } if (recalculate) { // Now re-calculate interesting faces this.calculateFaces(layer); } }, /** * Sets collision values on a tile in the set. * You shouldn't usually call this method directly, instead use setCollision, setCollisionBetween or setCollisionByExclusion. * * @method Phaser.Tilemap#setCollisionByIndex * @protected * @param {number} index - The index of the tile on the layer. * @param {boolean} [collides=true] - If true it will enable collision on the tile. If false it will clear collision values from the tile. * @param {number} [layer] - The layer to operate on. If not given will default to this.currentLayer. * @param {boolean} [recalculate=true] - Recalculates the tile faces after the update. */ setCollisionByIndex: function (index, collides, layer, recalculate) { if (collides === undefined) { collides = true; } if (layer === undefined) { layer = this.currentLayer; } if (recalculate === undefined) { recalculate = true; } if (collides) { this.collideIndexes.push(index); } else { var i = this.collideIndexes.indexOf(index); if (i > -1) { this.collideIndexes.splice(i, 1); } } for (var y = 0; y < this.layers[layer].height; y++) { for (var x = 0; x < this.layers[layer].width; x++) { var tile = this.layers[layer].data[y][x]; if (tile && tile.index === index) { if (collides) { tile.setCollision(true, true, true, true); } else { tile.resetCollision(); } tile.faceTop = collides; tile.faceBottom = collides; tile.faceLeft = collides; tile.faceRight = collides; } } } if (recalculate) { // Now re-calculate interesting faces this.calculateFaces(layer); } return layer; }, /** * Gets the TilemapLayer index as used in the setCollision calls. * * @method Phaser.Tilemap#getLayer * @protected * @param {number|string|Phaser.TilemapLayer} layer - The layer to operate on. If not given will default to this.currentLayer. * @return {number} The TilemapLayer index. */ getLayer: function (layer) { if (layer === undefined) { layer = this.currentLayer; } else if (typeof layer === 'string') { var layerArg = layer; layer = this.getLayerIndex(layer); if (layer === null) { console.warn('No such layer name: ' + layerArg); } } else if (layer instanceof Phaser.TilemapLayer) { layer = layer.index; } return layer; }, /** * Turn off/on the recalculation of faces for tile or collision updates. * `setPreventRecalculate(true)` puts recalculation on hold while `setPreventRecalculate(false)` recalculates all the changed layers. * * @method Phaser.Tilemap#setPreventRecalculate * @param {boolean} value - If true it will put the recalculation on hold. */ setPreventRecalculate: function (value) { if (value === true && this.preventingRecalculate !== true) { this.preventingRecalculate = true; this.needToRecalculate = {}; } if (value === false && this.preventingRecalculate === true) { this.preventingRecalculate = false; for (var i in this.needToRecalculate) { this.calculateFaces(i); } this.needToRecalculate = false; } }, /** * Internal function. * * @method Phaser.Tilemap#calculateFaces * @protected * @param {number} layer - The index of the TilemapLayer to operate on. */ calculateFaces: function (layer) { if (this.preventingRecalculate) { this.needToRecalculate[layer] = true; return; } var above = null; var below = null; var left = null; var right = null; for (var y = 0, h = this.layers[layer].height; y < h; y++) { for (var x = 0, w = this.layers[layer].width; x < w; x++) { var tile = this.layers[layer].data[y][x]; if (tile) { above = this.getTileAbove(layer, x, y); below = this.getTileBelow(layer, x, y); left = this.getTileLeft(layer, x, y); right = this.getTileRight(layer, x, y); if (tile.collides) { tile.faceTop = true; tile.faceBottom = true; tile.faceLeft = true; tile.faceRight = true; } if (above && above.collides) { // There is a tile above this one that also collides, so the top of this tile is no longer interesting tile.faceTop = false; } if (below && below.collides) { // There is a tile below this one that also collides, so the bottom of this tile is no longer interesting tile.faceBottom = false; } if (left && left.collides) { // There is a tile left this one that also collides, so the left of this tile is no longer interesting tile.faceLeft = false; } if (right && right.collides) { // There is a tile right this one that also collides, so the right of this tile is no longer interesting tile.faceRight = false; } } } } }, /** * Gets the tile above the tile coordinates given. * Mostly used as an internal function by calculateFaces. * * @method Phaser.Tilemap#getTileAbove * @param {number} layer - The local layer index to get the tile from. Can be determined by Tilemap.getLayer(). * @param {number} x - The x coordinate to get the tile from. In tiles, not pixels. * @param {number} y - The y coordinate to get the tile from. In tiles, not pixels. */ getTileAbove: function (layer, x, y) { if (y > 0) { return this.layers[layer].data[y - 1][x]; } return null; }, /** * Gets the tile below the tile coordinates given. * Mostly used as an internal function by calculateFaces. * * @method Phaser.Tilemap#getTileBelow * @param {number} layer - The local layer index to get the tile from. Can be determined by Tilemap.getLayer(). * @param {number} x - The x coordinate to get the tile from. In tiles, not pixels. * @param {number} y - The y coordinate to get the tile from. In tiles, not pixels. */ getTileBelow: function (layer, x, y) { if (y < this.layers[layer].height - 1) { return this.layers[layer].data[y + 1][x]; } return null; }, /** * Gets the tile to the left of the tile coordinates given. * Mostly used as an internal function by calculateFaces. * * @method Phaser.Tilemap#getTileLeft * @param {number} layer - The local layer index to get the tile from. Can be determined by Tilemap.getLayer(). * @param {number} x - The x coordinate to get the tile from. In tiles, not pixels. * @param {number} y - The y coordinate to get the tile from. In tiles, not pixels. */ getTileLeft: function (layer, x, y) { if (x > 0) { return this.layers[layer].data[y][x - 1]; } return null; }, /** * Gets the tile to the right of the tile coordinates given. * Mostly used as an internal function by calculateFaces. * * @method Phaser.Tilemap#getTileRight * @param {number} layer - The local layer index to get the tile from. Can be determined by Tilemap.getLayer(). * @param {number} x - The x coordinate to get the tile from. In tiles, not pixels. * @param {number} y - The y coordinate to get the tile from. In tiles, not pixels. */ getTileRight: function (layer, x, y) { if (x < this.layers[layer].width - 1) { return this.layers[layer].data[y][x + 1]; } return null; }, /** * Sets the current layer to the given index. * * @method Phaser.Tilemap#setLayer * @param {number|string|Phaser.TilemapLayer} layer - The layer to set as current. */ setLayer: function (layer) { layer = this.getLayer(layer); if (this.layers[layer]) { this.currentLayer = layer; } }, /** * Checks if there is a tile at the given location. * * @method Phaser.Tilemap#hasTile * @param {number} x - X position to check if a tile exists at (given in tile units, not pixels) * @param {number} y - Y position to check if a tile exists at (given in tile units, not pixels) * @param {number|string|Phaser.TilemapLayer} layer - The layer to set as current. * @return {boolean} True if there is a tile at the given location, otherwise false. */ hasTile: function (x, y, layer) { layer = this.getLayer(layer); if (this.layers[layer].data[y] === undefined || this.layers[layer].data[y][x] === undefined) { return false; } return (this.layers[layer].data[y][x].index > -1); }, /** * Removes the tile located at the given coordinates and updates the collision data. * * @method Phaser.Tilemap#removeTile * @param {number} x - X position to place the tile (given in tile units, not pixels) * @param {number} y - Y position to place the tile (given in tile units, not pixels) * @param {number|string|Phaser.TilemapLayer} [layer] - The layer to modify. * @return {Phaser.Tile} The Tile object that was removed from this map. */ removeTile: function (x, y, layer) { layer = this.getLayer(layer); if (x >= 0 && x < this.layers[layer].width && y >= 0 && y < this.layers[layer].height) { if (this.hasTile(x, y, layer)) { var tile = this.layers[layer].data[y][x]; this.layers[layer].data[y][x] = new Phaser.Tile(this.layers[layer], -1, x, y, this.tileWidth, this.tileHeight); this.layers[layer].dirty = true; this.calculateFaces(layer); return tile; } } }, /** * Removes the tile located at the given coordinates and updates the collision data. The coordinates are given in pixel values. * * @method Phaser.Tilemap#removeTileWorldXY * @param {number} x - X position to insert the tile (given in pixels) * @param {number} y - Y position to insert the