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
JavaScript
/**
* @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