Major Optimization for Tilemap Collision Indexing

This update optimizes an existing feature (Tilemap Collision Indexing).

This optimization will apply to [nearly] everyone using procedural generation tilemaps, infinite tilemaps, multiplayer tilemaps, particularly large tilemaps (especially those dyanmic in nature) or who otherwise intend to index collisions before the tiles are loaded.

Benchmarking:

Using the standard approach, indexing collisions for a 500x450 tileset took 2530ms. By pre-building the collision index array in its entirety, I was able to reduce that to 259ms. However, this implementation reduces it to 8 ms. Larger tilemaps would be exponentially affected.

There are some considerations to make here since there are better implementations, but it would require deprecating some existing code. The advantage would be that the standard approach itself would be optimized, (thus avoiding the sort of 2500+ms costs here), rather than fairly obscured as it is with this implementation.
This commit is contained in:
Skysteed 2018-07-19 10:59:35 -05:00
parent 106e32a4f5
commit 5cd60b3378
5 changed files with 50 additions and 25 deletions

View file

@ -1660,16 +1660,18 @@ var Tilemap = new Class({
* @param {boolean} [recalculateFaces=true] - Whether or not to recalculate the tile faces after the
* update.
* @param {Phaser.Tilemaps.LayerData} [layer] - [description]
* @param {boolean} [updateLayer=true] - If true, updates the current tiles on the layer. Set to
* false if no tiles have been placed for significant performance boost.
*
* @return {?Phaser.Tilemaps.Tilemap} Return this Tilemap object, or null if the layer given was invalid.
*/
setCollision: function (indexes, collides, recalculateFaces, layer)
setCollision: function (indexes, collides, recalculateFaces, layer, updateLayer)
{
layer = this.getLayer(layer);
if (layer === null) { return this; }
TilemapComponents.SetCollision(indexes, collides, recalculateFaces, layer);
TilemapComponents.SetCollision(indexes, collides, recalculateFaces, layer, updateLayer);
return this;
},

View file

@ -23,33 +23,39 @@ var SetLayerCollisionIndex = require('./SetLayerCollisionIndex');
* @param {boolean} [recalculateFaces=true] - Whether or not to recalculate the tile faces after the
* update.
* @param {Phaser.Tilemaps.LayerData} layer - The Tilemap Layer to act upon.
* @param {boolean} [updateLayer=true] - If true, updates the current tiles on the layer. Set to
* false if no tiles have been placed for significant performance boost.
*/
var SetCollision = function (indexes, collides, recalculateFaces, layer)
var SetCollision = function (indexes, collides, recalculateFaces, layer, updateLayer)
{
if (collides === undefined) { collides = true; }
if (recalculateFaces === undefined) { recalculateFaces = true; }
if (!Array.isArray(indexes)) { indexes = [ indexes ]; }
if (updateLayer === undefined) { updateLayer = true; }
// Update the array of colliding indexes
for (var i = 0; i < indexes.length; i++)
{
SetLayerCollisionIndex(indexes[i], collides, layer);
}
// Update the tiles
for (var ty = 0; ty < layer.height; ty++)
if(updateLayer)
{
for (var tx = 0; tx < layer.width; tx++)
for (var ty = 0; ty < layer.height; ty++)
{
var tile = layer.data[ty][tx];
if (tile && indexes.indexOf(tile.index) !== -1)
for (var tx = 0; tx < layer.width; tx++)
{
SetTileCollision(tile, collides);
var tile = layer.data[ty][tx];
if (tile && indexes.indexOf(tile.index) !== -1)
{
SetTileCollision(tile, collides);
}
}
}
}
if (recalculateFaces) { CalculateFacesWithin(0, 0, layer.width, layer.height, layer); }
};

View file

@ -25,11 +25,14 @@ var SetLayerCollisionIndex = require('./SetLayerCollisionIndex');
* @param {boolean} [recalculateFaces=true] - Whether or not to recalculate the tile faces after the
* update.
* @param {Phaser.Tilemaps.LayerData} layer - The Tilemap Layer to act upon.
* @param {boolean} [updateLayer=true] - If true, updates the current tiles on the layer. Set to
* false if no tiles have been placed for significant performance boost.
*/
var SetCollisionBetween = function (start, stop, collides, recalculateFaces, layer)
var SetCollisionBetween = function (start, stop, collides, recalculateFaces, layer, updateLayer)
{
if (collides === undefined) { collides = true; }
if (recalculateFaces === undefined) { recalculateFaces = true; }
if (updateLayer === undefined) { updateLayer = true; }
if (start > stop) { return; }
@ -40,21 +43,24 @@ var SetCollisionBetween = function (start, stop, collides, recalculateFaces, lay
}
// Update the tiles
for (var ty = 0; ty < layer.height; ty++)
if(updateLayer)
{
for (var tx = 0; tx < layer.width; tx++)
for (var ty = 0; ty < layer.height; ty++)
{
var tile = layer.data[ty][tx];
if (tile)
for (var tx = 0; tx < layer.width; tx++)
{
if (tile.index >= start && tile.index <= stop)
var tile = layer.data[ty][tx];
if (tile)
{
SetTileCollision(tile, collides);
if (tile.index >= start && tile.index <= stop)
{
SetTileCollision(tile, collides);
}
}
}
}
}
if (recalculateFaces) { CalculateFacesWithin(0, 0, layer.width, layer.height, layer); }
};

View file

@ -872,7 +872,9 @@ var DynamicTilemapLayer = new Class({
* 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).
*
* @method Phaser.Tilemaps.DynamicTilemapLayer#setCollision
* If no layer specified, the map's current layer is used.
*
* @method Phaser.Tilemaps.Tilemap#setCollision
* @since 3.0.0
*
* @param {(integer|array)} indexes - Either a single tile index, or an array of tile indexes.
@ -880,12 +882,19 @@ var DynamicTilemapLayer = new Class({
* collision.
* @param {boolean} [recalculateFaces=true] - Whether or not to recalculate the tile faces after the
* update.
* @param {Phaser.Tilemaps.LayerData} [layer] - [description]
* @param {boolean} [updateLayer=true] - If true, updates the current tiles on the layer. Set to
* false if no tiles have been placed for significant performance boost.
*
* @return {Phaser.Tilemaps.DynamicTilemapLayer} This Tilemap Layer object.
* @return {?Phaser.Tilemaps.Tilemap} Return this Tilemap object, or null if the layer given was invalid.
*/
setCollision: function (indexes, collides, recalculateFaces)
setCollision: function (indexes, collides, recalculateFaces, layer, updateLayer)
{
TilemapComponents.SetCollision(indexes, collides, recalculateFaces, this.layer);
layer = this.getLayer(layer);
if (layer === null) { return this; }
TilemapComponents.SetCollision(indexes, collides, recalculateFaces, layer, updateLayer);
return this;
},

View file

@ -784,12 +784,14 @@ var StaticTilemapLayer = new Class({
* collision.
* @param {boolean} [recalculateFaces=true] - Whether or not to recalculate the tile faces after the
* update.
* @param {boolean} [updateLayer=true] - If true, updates the current tiles on the layer. Set to
* false if no tiles have been placed for significant performance boost.
*
* @return {Phaser.Tilemaps.StaticTilemapLayer} This Tilemap Layer object.
*/
setCollision: function (indexes, collides, recalculateFaces)
setCollision: function (indexes, collides, recalculateFaces, updateLayer)
{
TilemapComponents.SetCollision(indexes, collides, recalculateFaces, this.layer);
TilemapComponents.SetCollision(indexes, collides, recalculateFaces, this.layer, updateLayer);
return this;
},