diff --git a/README.md b/README.md index cee235128..5c2daaefe 100644 --- a/README.md +++ b/README.md @@ -153,6 +153,7 @@ New features: * ArcadePhysics.Body has a new gravityScale property, which is a modifier multiplied against the world gravity value on a Body. * Line.coordinatesOnLine will return all coordinates on the line using Bresenhams line algorithm. * Line now has x, y, width, height, top, bottom, left and right properties, so you can effectively get its bounds. +* TilemapLayer.getRayCastTiles will let you get all tiles that hit the given line for further processing. Updates: diff --git a/examples/wip/line points.js b/examples/wip/line points.js index f60c30f99..71050f8c1 100644 --- a/examples/wip/line points.js +++ b/examples/wip/line points.js @@ -6,7 +6,7 @@ function create() { var line = new Phaser.Line(100, 50, 10, 300); - var coords = line.coordinatesOnLine(); + var coords = line.coordinatesOnLine(8); var bmd = game.add.bitmapData(800, 600); bmd.context.fillStyle = '#ffffff'; diff --git a/examples/wip/tilemap raycast.js b/examples/wip/tilemap raycast.js new file mode 100644 index 000000000..c074a92cb --- /dev/null +++ b/examples/wip/tilemap raycast.js @@ -0,0 +1,127 @@ + +var game = new Phaser.Game(800, 600, Phaser.CANVAS, 'phaser-example', { preload: preload, create: create, update: update, render: render }); + +function preload() { + + game.load.tilemap('map', 'assets/tilemaps/maps/collision_test.json', null, Phaser.Tilemap.TILED_JSON); + game.load.image('ground_1x1', 'assets/tilemaps/tiles/ground_1x1.png'); + game.load.image('phaser', 'assets/sprites/phaser-dude.png'); + +} + +var map; +var layer; +var cursors; +var sprite; +var line; +var tileHits = []; +var plotting = false; + +function create() { + + line = new Phaser.Line(); + + map = game.add.tilemap('map'); + + map.addTilesetImage('ground_1x1'); + + layer = map.createLayer('Tile Layer 1'); + + layer.resizeWorld(); + + map.setCollisionBetween(1, 12); + + layer.debug = true; + + sprite = game.add.sprite(260, 70, 'phaser'); + + game.physics.enable(sprite); + + game.camera.follow(sprite); + + cursors = game.input.keyboard.createCursorKeys(); + + var help = game.add.text(10, 10, 'Arrows to move, click and drag to cast a ray', { font: '16px Arial', fill: '#ffffff' }); + help.fixedToCamera = true; + + game.input.onDown.add(startLine, this); + game.input.onUp.add(raycast, this); + +} + +function startLine(pointer) { + + if (tileHits.length > 0) + { + for (var i = 0; i < tileHits.length; i++) + { + tileHits[i].debug = false; + } + + layer.dirty = true; + } + + line.start.set(pointer.worldX, pointer.worldY); + + plotting = true; + +} + +function raycast(pointer) { + + line.end.set(pointer.worldX, pointer.worldY); + + tileHits = layer.getRayCastTiles(line, 4, false, false); + + if (tileHits.length > 0) + { + // Just so we can visually see the tiles + for (var i = 0; i < tileHits.length; i++) + { + tileHits[i].debug = true; + } + + layer.dirty = true; + } + + plotting = false; + +} + +function update() { + + if (plotting) + { + line.end.set(game.input.activePointer.worldX, game.input.activePointer.worldY); + } + + game.physics.arcade.collide(sprite, layer); + + sprite.body.velocity.x = 0; + sprite.body.velocity.y = 0; + + if (cursors.up.isDown) + { + sprite.body.velocity.y = -200; + } + else if (cursors.down.isDown) + { + sprite.body.velocity.y = 200; + } + + if (cursors.left.isDown) + { + sprite.body.velocity.x = -200; + } + else if (cursors.right.isDown) + { + sprite.body.velocity.x = 200; + } + +} + +function render() { + + game.debug.geom(line); + +} diff --git a/src/geom/Line.js b/src/geom/Line.js index 5b5b5340c..e9abfc5e5 100644 --- a/src/geom/Line.js +++ b/src/geom/Line.js @@ -131,11 +131,13 @@ Phaser.Line.prototype = { * The start and end points are rounded before this runs as the algorithm works on integers. * * @method Phaser.Line#coordinatesOnLine + * @param {number} [stepRate=1] - How many steps will we return? 1 = every coordinate on the line, 2 = every other coordinate, etc. * @param {array} [results] - The array to store the results in. If not provided a new one will be generated. * @return {array} An array of coordinates. */ - coordinatesOnLine: function (results) { + coordinatesOnLine: function (stepRate, results) { + if (typeof stepRate === 'undefined') { stepRate = 1; } if (typeof results === 'undefined') { results = []; } var x1 = Math.round(this.start.x); @@ -151,6 +153,8 @@ Phaser.Line.prototype = { results.push([x1, y1]); + var i = 1; + while (!((x1 == x2) && (y1 == y2))) { var e2 = err << 1; @@ -167,7 +171,13 @@ Phaser.Line.prototype = { y1 += sy; } - results.push([x1, y1]); + if (i % stepRate === 0) + { + results.push([x1, y1]); + } + + i++; + } return results; diff --git a/src/physics/arcade/World.js b/src/physics/arcade/World.js index f883384e2..8b4a37906 100644 --- a/src/physics/arcade/World.js +++ b/src/physics/arcade/World.js @@ -44,6 +44,11 @@ Phaser.Physics.Arcade = function (game) { */ this.OVERLAP_BIAS = 4; + /** + * @property {number} TILE_BIAS - A value added to the delta values during collision with tiles. Adjust this if you get tunnelling. + */ + this.TILE_BIAS = 16; + /** * @property {Phaser.QuadTree} quadTree - The world QuadTree. */ @@ -1052,7 +1057,7 @@ Phaser.Physics.Arcade.prototype = { { ox = body.x - tile.right; - if (ox < -this.OVERLAP_BIAS) + if (ox < -this.TILE_BIAS) { ox = 0; } @@ -1065,7 +1070,7 @@ Phaser.Physics.Arcade.prototype = { { ox = body.right - tile.left; - if (ox > this.OVERLAP_BIAS) + if (ox > this.TILE_BIAS) { ox = 0; } @@ -1101,7 +1106,7 @@ Phaser.Physics.Arcade.prototype = { { oy = body.y - tile.bottom; - if (oy < -this.OVERLAP_BIAS) + if (oy < -this.TILE_BIAS) { oy = 0; } @@ -1114,7 +1119,7 @@ Phaser.Physics.Arcade.prototype = { { oy = body.bottom - tile.top; - if (oy > this.OVERLAP_BIAS) + if (oy > this.TILE_BIAS) { oy = 0; } diff --git a/src/tilemap/Tile.js b/src/tilemap/Tile.js index e24e89eff..9e8c3f96c 100644 --- a/src/tilemap/Tile.js +++ b/src/tilemap/Tile.js @@ -144,6 +144,20 @@ Phaser.Tile = function (layer, index, x, y, width, height) { Phaser.Tile.prototype = { + /** + * Check if the given x and y world coordinates are within this Tile. + * + * @method Phaser.Tile#containsPoint + * @param {number} x - The x coordinate to test. + * @param {number} y - The y coordinate to test. + * @return {boolean} True if the coordinates are within this Tile, otherwise false. + */ + containsPoint: function (x, y) { + + return !(x < this.worldX || y < this.worldY || x > this.right || y > this.bottom); + + }, + /** * Check for intersection with this tile. * diff --git a/src/tilemap/TilemapLayer.js b/src/tilemap/TilemapLayer.js index 80f56e3b5..73e22915d 100644 --- a/src/tilemap/TilemapLayer.js +++ b/src/tilemap/TilemapLayer.js @@ -151,6 +151,12 @@ Phaser.TilemapLayer = function (game, tilemap, index, width, height) { */ this.dirty = true; + /** + * @property {number} rayStepRate - When ray-casting against tiles this is the number of steps it will jump. For larger tile sizes you can increase this to improve performance. + * @default + */ + this.rayStepRate = 4; + /** * @property {number} _cw - Local collision var. * @private @@ -457,17 +463,46 @@ Phaser.TilemapLayer.prototype.getTileXY = function (x, y, point) { /** * Gets all tiles that intersect with the given line. * -* @method Phaser.TilemapLayer#getIntersectingTiles +* @method Phaser.TilemapLayer#getRayCastTiles * @memberof Phaser.TilemapLayer * @param {Phaser.Line} line - The line used to determine which tiles to return. +* @param {number} [stepRate] - How many steps through the ray will we check? If undefined or null it uses TilemapLayer.rayStepRate. +* @param {boolean} [collides=false] - If true only return tiles that collide on one or more faces. +* @param {boolean} [interestingFace=false] - If true only return tiles that have interesting faces. * @return {array} An array of Phaser.Tiles. */ -Phaser.TilemapLayer.prototype.getIntersectingTiles = function (line) { +Phaser.TilemapLayer.prototype.getRayCastTiles = function (line, stepRate, collides, interestingFace) { - var tiles = this.getTiles(x, y, width, height, false); + if (typeof stepRate === 'undefined' || stepRate === null) { stepRate = this.rayStepRate; } + if (typeof collides === 'undefined') { collides = false; } + if (typeof interestingFace === 'undefined') { interestingFace = false; } + // First get all tiles that touch the bounds of the line + var tiles = this.getTiles(line.x, line.y, line.width, line.height, collides, interestingFace); - return tiles; + if (tiles.length === 0) + { + return []; + } + + // Now we only want the tiles that intersect with the points on this line + var coords = line.coordinatesOnLine(stepRate); + var total = coords.length; + var results = []; + + for (var i = 0; i < tiles.length; i++) + { + for (var t = 0; t < total; t++) + { + if (tiles[i].containsPoint(coords[t][0], coords[t][1])) + { + results.push(tiles[i]); + break; + } + } + } + + return results; } @@ -589,8 +624,6 @@ Phaser.TilemapLayer.prototype.render = function () { var tile; var set; - // var ox = 0; - // var oy = 0; if (this.debug) { @@ -618,11 +651,11 @@ Phaser.TilemapLayer.prototype.render = function () { set.draw(this.context, Math.floor(this._tx), Math.floor(this._ty), tile.index); - // if (tile.debug) - // { - // this.context.fillStyle = 'rgba(0, 255, 0, 0.4)'; - // this.context.fillRect(Math.floor(this._tx), Math.floor(this._ty), this.map.tileWidth, this.map.tileHeight); - // } + if (tile.debug) + { + this.context.fillStyle = 'rgba(0, 255, 0, 0.4)'; + this.context.fillRect(Math.floor(this._tx), Math.floor(this._ty), this.map.tileWidth, this.map.tileHeight); + } } this._tx += this.map.tileWidth;