From 98ae8009e09b293e35cfc145bfd129f2da739d3f Mon Sep 17 00:00:00 2001 From: Michael Hadley Date: Wed, 10 Jan 2018 20:47:25 -0600 Subject: [PATCH] Arcade rect vs tilemap layer collision handling --- .../arcade/inc/CollideSpriteVsTilemapLayer.js | 43 +++++++++ .../inc/tilemap/ProcessTileCallbacks.js | 20 ++++ .../inc/tilemap/ProcessTileSeparationX.js | 32 +++++++ .../inc/tilemap/ProcessTileSeparationY.js | 32 +++++++ .../arcade/inc/tilemap/SeparateTile.js | 94 +++++++++++++++++++ src/physics/arcade/inc/tilemap/TileCheckX.js | 63 +++++++++++++ src/physics/arcade/inc/tilemap/TileCheckY.js | 63 +++++++++++++ .../arcade/inc/tilemap/TileIntersectsBody.js | 18 ++++ 8 files changed, 365 insertions(+) create mode 100644 src/physics/arcade/inc/tilemap/ProcessTileCallbacks.js create mode 100644 src/physics/arcade/inc/tilemap/ProcessTileSeparationX.js create mode 100644 src/physics/arcade/inc/tilemap/ProcessTileSeparationY.js create mode 100644 src/physics/arcade/inc/tilemap/SeparateTile.js create mode 100644 src/physics/arcade/inc/tilemap/TileCheckX.js create mode 100644 src/physics/arcade/inc/tilemap/TileCheckY.js create mode 100644 src/physics/arcade/inc/tilemap/TileIntersectsBody.js diff --git a/src/physics/arcade/inc/CollideSpriteVsTilemapLayer.js b/src/physics/arcade/inc/CollideSpriteVsTilemapLayer.js index 75368a8a2..6ed2c800a 100644 --- a/src/physics/arcade/inc/CollideSpriteVsTilemapLayer.js +++ b/src/physics/arcade/inc/CollideSpriteVsTilemapLayer.js @@ -1,5 +1,48 @@ +var SeparateTile = require('./tilemap/SeparateTile'); +var TileIntersectsBody = require('./tilemap/TileIntersectsBody'); +var ProcessTileCallbacks = require('./tilemap/ProcessTileCallbacks'); + var CollideSpriteVsTilemapLayer = function (sprite, tilemapLayer, collideCallback, processCallback, callbackContext, overlapOnly) { + if (!sprite.body.enable) + { + return false; + } + + var mapData = tilemapLayer.getTilesWithinWorldXY( + sprite.body.position.x, sprite.body.position.y, + sprite.body.width, sprite.body.height + ); + + if (mapData.length === 0) + { + return false; + } + + var tile; + var tileWorldRect = { left: 0, right: 0, top: 0, bottom: 0 }; + + for (var i = 0; i < mapData.length; i++) + { + tile = mapData[i]; + tileWorldRect.left = tilemapLayer.tileToWorldX(tile.x); + tileWorldRect.top = tilemapLayer.tileToWorldY(tile.y); + tileWorldRect.right = tileWorldRect.left + tile.width * tilemapLayer.scaleX; + tileWorldRect.bottom = tileWorldRect.top + tile.height * tilemapLayer.scaleY; + + if (TileIntersectsBody(tileWorldRect, sprite.body) + && (!processCallback || processCallback.call(callbackContext, sprite, tile)) + && ProcessTileCallbacks(tile) + && (overlapOnly || SeparateTile(i, sprite.body, tile, tileWorldRect, tilemapLayer))) + { + this._total++; + + if (collideCallback) + { + collideCallback.call(callbackContext, sprite, tile); + } + } + } }; module.exports = CollideSpriteVsTilemapLayer; diff --git a/src/physics/arcade/inc/tilemap/ProcessTileCallbacks.js b/src/physics/arcade/inc/tilemap/ProcessTileCallbacks.js new file mode 100644 index 000000000..7bd983bfa --- /dev/null +++ b/src/physics/arcade/inc/tilemap/ProcessTileCallbacks.js @@ -0,0 +1,20 @@ +var ProcessTileCallbacks = function (tile) +{ + return true; + + // TODO: port v2 + // // Tilemap & tile callbacks take priority + // // A local callback always takes priority over a layer level callback + // if (tile.collisionCallback && !tile.collisionCallback.call(tile.collisionCallbackContext, body.sprite, tile)) + // { + // // If it returns true then we can carry on, otherwise we should abort. + // return false; + // } + // else if (typeof tile.layer.callbacks !== 'undefined' && tile.layer.callbacks[tile.index] && !tile.layer.callbacks[tile.index].callback.call(tile.layer.callbacks[tile.index].callbackContext, body.sprite, tile)) + // { + // // If it returns true then we can carry on, otherwise we should abort. + // return false; + // } +}; + +module.exports = ProcessTileCallbacks; diff --git a/src/physics/arcade/inc/tilemap/ProcessTileSeparationX.js b/src/physics/arcade/inc/tilemap/ProcessTileSeparationX.js new file mode 100644 index 000000000..b7fc4a6c9 --- /dev/null +++ b/src/physics/arcade/inc/tilemap/ProcessTileSeparationX.js @@ -0,0 +1,32 @@ +/** +* Internal function to process the separation of a physics body from a tile. +* +* @private +* @method Phaser.Physics.Arcade#processTileSeparationX +* @param {Phaser.Physics.Arcade.Body} body - The Body object to separate. +* @param {number} x - The x separation amount. +*/ +var ProcessTileSeparationX = function (body, x) +{ + if (x < 0) + { + body.blocked.left = true; + } + else if (x > 0) + { + body.blocked.right = true; + } + + body.position.x -= x; + + if (body.bounce.x === 0) + { + body.velocity.x = 0; + } + else + { + body.velocity.x = -body.velocity.x * body.bounce.x; + } +}; + +module.exports = ProcessTileSeparationX; diff --git a/src/physics/arcade/inc/tilemap/ProcessTileSeparationY.js b/src/physics/arcade/inc/tilemap/ProcessTileSeparationY.js new file mode 100644 index 000000000..05777c2b0 --- /dev/null +++ b/src/physics/arcade/inc/tilemap/ProcessTileSeparationY.js @@ -0,0 +1,32 @@ +/** +* Internal function to process the separation of a physics body from a tile. +* +* @private +* @method Phaser.Physics.Arcade#processTileSeparationY +* @param {Phaser.Physics.Arcade.Body} body - The Body object to separate. +* @param {number} y - The y separation amount. +*/ +var ProcessTileSeparationY = function (body, y) +{ + if (y < 0) + { + body.blocked.up = true; + } + else if (y > 0) + { + body.blocked.down = true; + } + + body.position.y -= y; + + if (body.bounce.y === 0) + { + body.velocity.y = 0; + } + else + { + body.velocity.y = -body.velocity.y * body.bounce.y; + } +}; + +module.exports = ProcessTileSeparationY; diff --git a/src/physics/arcade/inc/tilemap/SeparateTile.js b/src/physics/arcade/inc/tilemap/SeparateTile.js new file mode 100644 index 000000000..7c7c4799d --- /dev/null +++ b/src/physics/arcade/inc/tilemap/SeparateTile.js @@ -0,0 +1,94 @@ +var TileCheckX = require('./TileCheckX'); +var TileCheckY = require('./TileCheckY'); +var TileIntersectsBody = require('./TileIntersectsBody'); + +/** + * The core separation function to separate a physics body and a tile. + * + * @param {Phaser.Physics.Arcade.Body} body - The Body object to separate. + * @param {Phaser.Tile} tile - The tile to collide against. + * @param {Phaser.TilemapLayer} tilemapLayer - The tilemapLayer to collide against. + * @return {boolean} Returns true if the body was separated, otherwise false. + */ +var SeparateTile = function (i, body, tile, tileWorldRect, tilemapLayer) +{ + var tileLeft = tileWorldRect.left; + var tileTop = tileWorldRect.top; + var tileRight = tileWorldRect.right; + var tileBottom = tileWorldRect.bottom; + var faceHorizontal = tile.faceLeft || tile.faceRight; + var faceVertical = tile.faceTop || tile.faceBottom; + + // We don't need to go any further if this tile doesn't actually have any colliding faces. This + // could happen if the tile was meant to be collided with re: a callback, but otherwise isn't + // needed for separation. + if (!faceHorizontal && !faceVertical) + { + return false; + } + + var ox = 0; + var oy = 0; + var minX = 0; + var minY = 1; + + if (body.deltaAbsX() > body.deltaAbsY()) + { + // Moving faster horizontally, check X axis first + minX = -1; + } + else if (body.deltaAbsX() < body.deltaAbsY()) + { + // Moving faster vertically, check Y axis first + minY = -1; + } + + if (body.deltaX() !== 0 && body.deltaY() !== 0 && faceHorizontal && faceVertical) + { + // We only need do this if both axes have colliding faces AND we're moving in both + // directions + minX = Math.min(Math.abs(body.position.x - tileRight), Math.abs(body.right - tileLeft)); + minY = Math.min(Math.abs(body.position.y - tileBottom), Math.abs(body.bottom - tileTop)); + } + + if (minX < minY) + { + if (faceHorizontal) + { + ox = TileCheckX(body, tile, tilemapLayer); + + // That's horizontal done, check if we still intersects? If not then we can return now + if (ox !== 0 && !TileIntersectsBody(tileWorldRect, body)) + { + return true; + } + } + + if (faceVertical) + { + oy = TileCheckY(body, tile, tilemapLayer); + } + } + else + { + if (faceVertical) + { + oy = TileCheckY(body, tile, tilemapLayer); + + // That's vertical done, check if we still intersects? If not then we can return now + if (oy !== 0 && !TileIntersectsBody(tileWorldRect, body)) + { + return true; + } + } + + if (faceHorizontal) + { + ox = TileCheckX(body, tile, tilemapLayer); + } + } + + return (ox !== 0 || oy !== 0); +}; + +module.exports = SeparateTile; diff --git a/src/physics/arcade/inc/tilemap/TileCheckX.js b/src/physics/arcade/inc/tilemap/TileCheckX.js new file mode 100644 index 000000000..6ab65c52a --- /dev/null +++ b/src/physics/arcade/inc/tilemap/TileCheckX.js @@ -0,0 +1,63 @@ +var TILE_BIAS = 16; +var ProcessTileSeparationX = require('./ProcessTileSeparationX'); + +/** +* Check the body against the given tile on the X axis. +* +* @private +* @method Phaser.Physics.Arcade#tileCheckX +* @param {Phaser.Physics.Arcade.Body} body - The Body object to separate. +* @param {Phaser.Tile} tile - The tile to check. +* @param {Phaser.TilemapLayer} tilemapLayer - The tilemapLayer to collide against. +* @return {number} The amount of separation that occurred. +*/ +var TileCheckX = function (body, tile, tilemapLayer) +{ + var ox = 0; + var tileLeft = tilemapLayer.tileToWorldX(tile.x); + var tileWidth = tile.width * tilemapLayer.scaleX; + var tileRight = tileLeft + tileWidth; + + if (body.deltaX() < 0 && !body.blocked.left && tile.collideRight && body.checkCollision.left) + { + // Body is moving LEFT + if (tile.faceRight && body.x < tileRight) + { + ox = body.x - tileRight; + + if (ox < -TILE_BIAS) + { + ox = 0; + } + } + } + else if (body.deltaX() > 0 && !body.blocked.right && tile.collideLeft && body.checkCollision.right) + { + // Body is moving RIGHT + if (tile.faceLeft && body.right > tileLeft) + { + ox = body.right - tileLeft; + + if (ox > TILE_BIAS) + { + ox = 0; + } + } + } + + if (ox !== 0) + { + if (body.customSeparateX) + { + body.overlapX = ox; + } + else + { + ProcessTileSeparationX(body, ox); + } + } + + return ox; +}; + +module.exports = TileCheckX; diff --git a/src/physics/arcade/inc/tilemap/TileCheckY.js b/src/physics/arcade/inc/tilemap/TileCheckY.js new file mode 100644 index 000000000..a431ff086 --- /dev/null +++ b/src/physics/arcade/inc/tilemap/TileCheckY.js @@ -0,0 +1,63 @@ +var TILE_BIAS = 16; +var ProcessTileSeparationY = require('./ProcessTileSeparationY'); + +/** +* Check the body against the given tile on the Y axis. +* +* @private +* @method Phaser.Physics.Arcade#tileCheckY +* @param {Phaser.Physics.Arcade.Body} body - The Body object to separate. +* @param {Phaser.Tile} tile - The tile to check. +* @param {Phaser.TilemapLayer} tilemapLayer - The tilemapLayer to collide against. +* @return {number} The amount of separation that occurred. +*/ +var TileCheckY = function (body, tile, tilemapLayer) +{ + var oy = 0; + var tileTop = tilemapLayer.tileToWorldX(tile.y); + var tileHeight = tile.height * tilemapLayer.scaleY; + var tileBottom = tileTop + tileHeight; + + if (body.deltaY() < 0 && !body.blocked.up && tile.collideDown && body.checkCollision.up) + { + // Body is moving UP + if (tile.faceBottom && body.y < tileBottom) + { + oy = body.y - tileBottom; + + if (oy < -TILE_BIAS) + { + oy = 0; + } + } + } + else if (body.deltaY() > 0 && !body.blocked.down && tile.collideUp && body.checkCollision.down) + { + // Body is moving DOWN + if (tile.faceTop && body.bottom > tileTop) + { + oy = body.bottom - tileTop; + + if (oy > TILE_BIAS) + { + oy = 0; + } + } + } + + if (oy !== 0) + { + if (body.customSeparateY) + { + body.overlapY = oy; + } + else + { + ProcessTileSeparationY(body, oy); + } + } + + return oy; +}; + +module.exports = TileCheckY; diff --git a/src/physics/arcade/inc/tilemap/TileIntersectsBody.js b/src/physics/arcade/inc/tilemap/TileIntersectsBody.js new file mode 100644 index 000000000..9c3cdda7c --- /dev/null +++ b/src/physics/arcade/inc/tilemap/TileIntersectsBody.js @@ -0,0 +1,18 @@ +var TileIntersectsBody = function (tileWorldRect, body) +{ + if (body.isCircle) + { + return false; + } + else + { + return !( + body.right <= tileWorldRect.left || + body.bottom <= tileWorldRect.top || + body.position.x >= tileWorldRect.right || + body.position.y >= tileWorldRect.bottom + ); + } +}; + +module.exports = TileIntersectsBody;