| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639 |
- /**
- * The MIT License (MIT)
- * Copyright (c) 2014 Raphaël Roux
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in
- * all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- * THE SOFTWARE.
- *
- *
- *
- */
- /**
- * @author Raphaël Roux
- * @copyright 2014 Raphaël Roux
- * @license {@link http://opensource.org/licenses/MIT}
- */
- /**
- * AStar is a phaser pathfinding plugin based on an A* kind of algorythm
- * It works with the Phaser.Tilemap
- *
- * @class Phaser.Plugin.AStar
- * @constructor
- * @param {Any} parent - The object that owns this plugin, usually Phaser.PluginManager.
- */
- Phaser.Plugin.AStar = function (parent)
- {
- /**
- * @property {Any} parent - The parent of this plugin. If added to the PluginManager the parent will be set to that, otherwise it will be null.
- */
- this.parent = parent;
- /**
- * @property {Phaser.Tilemap} _tilemap - A reference to the tilemap used to store astar nodes according to the Phaser.Tilemap structure.
- */
- this._tilemap;
- /**
- * @property {number} _layerIndex - The layer index of the tilemap that is used to store astar nodes.
- */
- this._layerIndex;
- /**
- * @property {number} _tilesetIndex - The tileset index of the tileset that handle tiles properties.
- */
- this._tilesetIndex;
-
- /**
- * @property {array} _open - An array that references nodes to be considered by the search path algorythm.
- */
- this._open;
- /**
- * @property {array} _closed - An array that references nodes not to consider anymore.
- */
- this._closed;
-
- /**
- * @property {array} _visited - Internal array of visited tiles, use for debug pupose.
- */
- this._visited;
- /**
- * @property {boolean} _useDiagonal - Does the astar algorythm can use tile diagonal?
- * @default true
- */
- this._useDiagonal = true;
- /**
- * @property {boolean} _findClosest - Does the findPath algorythm must calc the closest result if destination is unreachable. If not findPath will return an empty array
- * @default true
- */
- this._findClosest = true;
- /**
- * @property {string} _walkablePropName - Wich name have the walkable propertiy in your tileset.
- * @default 'walkable'
- */
- this._walkablePropName = 'walkable';
- /**
- * @property {function} _distanceFunction - The function used to calc distance.
- */
- this._distanceFunction = Phaser.Plugin.AStar.DISTANCE_EUCLIDIAN;
- /**
- * @property {Phaser.Plugin.AStar.AStarPath} _lastPath - The last path calculated by astar.
- */
- this._lastPath = null;
- /**
- * @property {boolean} _debug - Boolean to debug mode, stores visited nodes, and have a cost. Disable in production.
- * @default false
- */
- this._debug = true;
- };
- Phaser.Plugin.AStar.prototype = Object.create(Phaser.Plugin.prototype);
- Phaser.Plugin.AStar.prototype.constructor = Phaser.Plugin.AStar;
- Phaser.Plugin.AStar.VERSION = '0.0.101';
- Phaser.Plugin.AStar.COST_ORTHOGONAL = 1;
- Phaser.Plugin.AStar.COST_DIAGONAL = Phaser.Plugin.AStar.COST_ORTHOGONAL*Math.sqrt(2);
- Phaser.Plugin.AStar.DISTANCE_MANHATTAN = 'distManhattan';
- Phaser.Plugin.AStar.DISTANCE_EUCLIDIAN = 'distEuclidian';
- /**
- * Sets the Phaser.Tilemap used to searchPath into.
- * @method Phaser.Plugin.AStar#setAStarMap
- * @public
- * @param {Phaser.Tilemap} map - the Phaser.Tilemap used to searchPath into. It must have a tileset with tile porperties to know if tiles are walkable or not.
- * @param {string} layerName - The name of the layer that handle tiles.
- * @param {string} tilesetName - The name of the tileset that have walkable properties.
- * @return {Phaser.Plugin.AStar} The Phaser.Plugin.AStar itself.
- */
- Phaser.Plugin.AStar.prototype.setAStarMap = function(map, layerName, tilesetName)
- {
- this._tilemap = map;
- this._layerIndex = this._tilemap.getLayerIndex(layerName);;
- this._tilesetIndex = this._tilemap.getTilesetIndex(tilesetName);
- this.updateMap();
- return this;
- };
- /**
- * Sets the Phaser.Tilemap used to searchPath into.
- * @method Phaser.Plugin.AStar-setAStarMap
- * @private
- * @return {void} The Phaser.Plugin.AStar itself.
- */
- Phaser.Plugin.AStar.prototype.updateMap = function()
- {
- var tile;
- var walkable;
- //for each tile, add a default AStarNode with x, y and walkable properties according to the tilemap/tileset datas
- for(var y=0; y < this._tilemap.height; y++)
- {
- for(var x=0; x < this._tilemap.width; x++)
- {
- tile = this._tilemap.layers[this._layerIndex].data[y][x];
- walkable = this._tilemap.tilesets[this._tilesetIndex].tileProperties[tile.index - 1][this._walkablePropName] !== "false" ? true : false;
- tile.properties.astarNode = new Phaser.Plugin.AStar.AStarNode(x, y, walkable);
- }
- }
- };
- /**
- * Find a path between to tiles coordinates
- * @method Phaser.Plugin.AStar#findPath
- * @public
- * @param {Phaser.Point} startPoint - The start point x, y in tiles coordinates to search a path.
- * @param {Phaser.Point} goalPoint - The goal point x, y in tiles coordinates that you trying to reach.
- * @return {Phaser.Plugin.AStar.AStarPath} The Phaser.Plugin.AStar.AStarPath that results
- */
- Phaser.Plugin.AStar.prototype.findPath = function(startPoint, goalPoint)
- {
- var path = new Phaser.Plugin.AStar.AStarPath();
- var start = this._tilemap.layers[this._layerIndex].data[startPoint.y][startPoint.x].properties.astarNode; //:AStarNode;
- var goal = this._tilemap.layers[this._layerIndex].data[goalPoint.y][goalPoint.x].properties.astarNode
- path.start = start;
- path.goal = goal;
- this._open = [];
- this._closed = [];
- this._visited = [];
-
- this._open.push(start);
-
- start.g = 0;
- start.h = this[this._distanceFunction](start, goal);
- start.f = start.h;
- start.parent = null;
-
- //Loop until there are no more nodes to search
- while(this._open.length > 0)
- {
- //Find lowest f in this._open
- var f = Infinity;
- var x;
- for (var i=0; i<this._open.length; i++)
- {
- if (this._open[i].f < f)
- {
- x = this._open[i];
- f = x.f;
- }
- }
-
- //Solution found, return solution
- if (x == goal)
- {
- path.nodes = this.reconstructPath(goal);
- this._lastPath = path;
- if(this._debug === true) path.visited = this._visited;
- return path;
- }
-
- //Close current node
- this._open.splice(this._open.indexOf(x), 1);
- this._closed.push(x);
-
- //Then get its neighbors
- var n = this.neighbors(x);
- for(var yIndex=0; yIndex < n.length; yIndex++)
- {
- var y = n[yIndex];
-
- if (-1 != this._closed.indexOf(y))
- continue;
-
- var g = x.g + y.travelCost;
- var better = false;
-
- //Add the node for being considered next loop.
- if (-1 == this._open.indexOf(y))
- {
- this._open.push(y);
- better = true;
- if(this._debug === true) this.visit(y);
- }
- else if (g < y.g)
- {
- better = true;
- }
- if (better) {
- y.parent = x;
- y.g = g;
- y.h = this[this._distanceFunction](y, goal);
- y.f = y.g + y.h;
- }
-
- }
-
- }
- //If no solution found, does A* try to return the closest result?
- if(this._findClosest === true)
- {
- var min = Infinity;
- var closestGoal, node, dist;
- for(var i=0, ii=this._closed.length; i<ii; i++)
- {
- node = this._closed[i];
- var dist = this[this._distanceFunction](goal, node);
- if (dist < min)
- {
- min = dist;
- closestGoal = node;
- }
- }
- //Reconstruct a path a path from the closestGoal
- path.nodes = this.reconstructPath(closestGoal);
- if(this._debug === true) path.visited = this._visited;
- }
- this._lastPath = path;
- return path;
- };
- /**
- * Reconstruct the result path backwards from the goal point, crawling its parents. Internal method.
- * @method Phaser.Plugin.AStar-reconstructPath
- * @private
- * @param {Phaser.Plugin.AStar.AStarNode} n - The astar node from wich you want to rebuild the path.
- * @return {array} An array of Phaser.Plugin.AStar.AStarNode
- */
- Phaser.Plugin.AStar.prototype.reconstructPath = function(n)
- {
- var solution = [];
- var nn = n;
- while(nn.parent) {
- solution.push({x: nn.x, y: nn.y});
- nn = nn.parent;
- }
- return solution;
- };
-
- /**
- * Add a node into visited if it is not already in. Debug only.
- * @method Phaser.Plugin.AStar-visit
- * @private
- * @param {Phaser.Plugin.AStar.AStarNode} node - The astar node you want to register as visited
- * @return {void}
- */
- Phaser.Plugin.AStar.prototype.visit = function(node)
- {
- for(var i in this._visited)
- {
- if (this._visited[i] == node) return;
- }
- this._visited.push(node);
- };
-
- /**
- * Add a node into visited if it is not already in. Debug only.
- * @method Phaser.Plugin.AStar-neighbors
- * @private
- * @param {Phaser.Plugin.AStar.AStarNode} n - The astar node you want to register as visited
- * @return {void}
- */
- Phaser.Plugin.AStar.prototype.neighbors = function(node)
- {
- var x = node.x;
- var y = node.y;
- var n = null;
- var neighbors = [];
-
- var map = this._tilemap.layers[this._layerIndex].data;
- //West
- if (x > 0) {
-
- n = map[y][x-1].properties.astarNode;
- if (n.walkable) {
- n.travelCost = Phaser.Plugin.AStar.COST_ORTHOGONAL;
- neighbors.push(n);
- }
- }
- //East
- if (x < this._tilemap.width-1) {
- n = map[y][x+1].properties.astarNode;
- if (n.walkable) {
- n.travelCost = Phaser.Plugin.AStar.COST_ORTHOGONAL;
- neighbors.push(n);
- }
- }
- //North
- if (y > 0) {
- n = map[y-1][x].properties.astarNode;
- if (n.walkable) {
- n.travelCost = Phaser.Plugin.AStar.COST_ORTHOGONAL;
- neighbors.push(n);
- }
- }
- //South
- if (y < this._tilemap.height-1) {
- n = map[y+1][x].properties.astarNode;
- if (n.walkable) {
- n.travelCost = Phaser.Plugin.AStar.COST_ORTHOGONAL;
- neighbors.push(n);
- }
- }
-
- //If diagonals aren't used do not search for other neighbors and return orthogonal search result
- if(this._useDiagonal === false)
- return neighbors;
-
- //NorthWest
- if (x > 0 && y > 0) {
- n = map[y-1][x-1].properties.astarNode;
- if (n.walkable
- && map[y][x-1].properties.astarNode.walkable
- && map[y-1][x].properties.astarNode.walkable
- ) {
- n.travelCost = Phaser.Plugin.AStar.COST_DIAGONAL;
- neighbors.push(n);
- }
- }
- //NorthEast
- if (x < this._tilemap.width-1 && y > 0) {
- n = map[y-1][x+1].properties.astarNode;
- if (n.walkable
- && map[y][x+1].properties.astarNode.walkable
- && map[y-1][x].properties.astarNode.walkable
- ) {
- n.travelCost = Phaser.Plugin.AStar.COST_DIAGONAL;
- neighbors.push(n);
- }
- }
- //SouthWest
- if (x > 0 && y < this._tilemap.height-1) {
- n = map[y+1][x-1].properties.astarNode;
- if (n.walkable
- && map[y][x-1].properties.astarNode.walkable
- && map[y+1][x].properties.astarNode.walkable
- ) {
- n.travelCost = Phaser.Plugin.AStar.COST_DIAGONAL;
- neighbors.push(n);
- }
- }
- //SouthEast
- if (x < this._tilemap.width-1 && y < this._tilemap.height-1) {
- n = map[y+1][x+1].properties.astarNode;
- if (n.walkable
- && map[y][x+1].properties.astarNode.walkable
- && map[y+1][x].properties.astarNode.walkable
- ) {
- n.travelCost = Phaser.Plugin.AStar.COST_DIAGONAL;
- neighbors.push(n);
- }
- }
-
- return neighbors;
- };
- /**
- * Calculate a distance between tow astar nodes coordinates according to the Manhattan method
- * @method Phaser.Plugin.AStar-distManhattan
- * @private
- * @param {Phaser.Plugin.AStar.AStarNode} nodeA - The A node.
- * @param {Phaser.Plugin.AStar.AStarNode} nodeB - The B node.
- * @return {number} The distance between nodeA and nodeB
- */
- Phaser.Plugin.AStar.prototype.distManhattan = function (nodeA, nodeB)
- {
- return Math.abs(nodeA.x - nodeB.x) + Math.abs(nodeA.y - nodeB.y);
- };
- /**
- * Calculate a distance between tow astar nodes coordinates according to the Euclidian method. More accurate
- * @method Phaser.Plugin.AStar-distEuclidian
- * @private
- * @param {Phaser.Plugin.AStar.AStarNode} nodeA - The A node.
- * @param {Phaser.Plugin.AStar.AStarNode} nodeB - The B node.
- * @return {number} The distance between nodeA and nodeB
- */
- Phaser.Plugin.AStar.prototype.distEuclidian = function(nodeA, nodeB)
- {
- return Math.sqrt(Math.pow((nodeA.x - nodeB.x), 2) + Math.pow((nodeA.y -nodeB.y), 2));
- };
- /**
- * Tells if a tile is walkable from its tilemap coordinates
- * @method Phaser.Plugin.AStar-isWalkable
- * @public
- * @param {number} x - The x coordiante of the tile in tilemap's coordinate.
- * @param {number} y - The y coordinate of the tile in tilemap's coordinate.
- * @return {boolean} The distance between nodeA and nodeB
- */
- Phaser.Plugin.AStar.prototype.isWalkable = function(x, y)
- {
- return this._tilemap.layers[this._layerIndex].data[y][x].properties.astarNode.walkable;
- };
- /**
- * @properties {string} version - The version number of Phaser.Plugin.AStar read only
- */
- Object.defineProperty(Phaser.Plugin.AStar.prototype, "version", {
-
- get: function () {
- return Phaser.Plugin.AStar.VERSION;
- }
- });
-
- /**
- * AStarNode is an object that stores AStar value. Each tile have an AStarNode in their properties
- * @class Phaser.Plugin.AStar.AStarNode
- * @constructor
- * @param {number} x - The x coordinate of the tile.
- * @param {number} y - The y coordinate of the tile.
- * @param {boolean} isWalkable - Is this tile is walkable?
- */
- Phaser.Plugin.AStar.AStarNode = function(x, y, isWalkable)
- {
- /**
- * @property {number} x - The x coordinate of the tile.
- */
- this.x = x;
-
- /**
- * @property {number} y - The y coordinate of the tile.
- */
- this.y = y;
- /**
- * @property {number} g - The total travel cost from the start point. Sum of COST_ORTHOGONAL and COST_DIAGONAL
- */
- this.g = 0;
- /**
- * @property {number} h - The remaing distance as the crow flies between this node and the goal.
- */
- this.h = 0;
- /**
- * @property {number} f - The weight. Sum of g + h.
- */
- this.f = 0;
- /**
- * @property {Phaser.Plugin.AStar.AStarNode} parent - Where do we come from? It's an AStarNode reference needed to reconstruct a path backwards (from goal to start point)
- */
- this.parent;
- /**
- * @property {boolean} walkable - Is this node is walkable?
- */
- this.walkable = isWalkable;
- /**
- * @property {number} travelCost - The cost to travel to this node, COST_ORTHOGONAL or COST_DIAGONAL
- */
- this.travelCost;
- };
- /**
- * AStarPath is an object that stores a searchPath result.
- * @class Phaser.Plugin.AStar.AStarPath
- * @constructor
- * @param {array} nodes - An array of nodes coordinates sorted backward from goal to start point.
- * @param {Phaser.Plugin.AStarNode} start - The start AStarNode used for the searchPath.
- * @param {Phaser.Plugin.AStarNode} goal - The goal AStarNode used for the searchPath.
- */
- Phaser.Plugin.AStar.AStarPath = function(nodes, start, goal)
- {
- /**
- * @property {array} nodes - Array of AstarNodes x, y coordiantes that are the path solution from goal to start point.
- */
- this.nodes = nodes || [];
- /**
- * @property {Phaser.Plugin.Astar.AStarNode} start - Reference to the start point used by findPath.
- */
- this.start = start || null;
- /**
- * @property {Phaser.Plugin.Astar.AStarNode} goal - Reference to the goal point used by findPath.
- */
- this.goal = goal || null;
- /**
- * @property {array} visited - Array of AStarNodes that the findPath algorythm has visited. Used for debug only.
- */
- this.visited = [];
- };
- /**
- * Debug method to draw the last calculated path by AStar
- * @method Phaser.Utils.Debug.AStar
- * @param {Phaser.Plugin.AStar} astar- The AStar plugin that you want to debug.
- * @param {number} x - X position on camera for debug display.
- * @param {number} y - Y position on camera for debug display.
- * @param {string} color - Color to stroke the path line.
- * @return {void}
- */
- Phaser.Utils.Debug.prototype.AStar = function(astar, x, y, color, showVisited)
- {
- if (this.context == null)
- {
- return;
- }
-
- var pathLength = 0;
- if(astar._lastPath !== null)
- {
- pathLength = astar._lastPath.nodes.length;
- }
- color = color || 'rgb(255,255,255)';
- game.debug.start(x, y, color);
- if(pathLength > 0)
- {
- var node = astar._lastPath.nodes[0];
- this.context.strokeStyle = color;
- this.context.beginPath();
- this.context.moveTo((node.x * astar._tilemap.tileWidth) + (astar._tilemap.tileWidth/2) - game.camera.view.x, (node.y * astar._tilemap.tileHeight) + (astar._tilemap.tileHeight/2) - game.camera.view.y);
- for(var i=0; i<pathLength; i++)
- {
- node = astar._lastPath.nodes[i];
- this.context.lineTo((node.x * astar._tilemap.tileWidth) + (astar._tilemap.tileWidth/2) - game.camera.view.x, (node.y * astar._tilemap.tileHeight) + (astar._tilemap.tileHeight/2) - game.camera.view.y);
- }
- this.context.lineTo((astar._lastPath.start.x * astar._tilemap.tileWidth) + (astar._tilemap.tileWidth/2) - game.camera.view.x, (astar._lastPath.start.y * astar._tilemap.tileHeight) + (astar._tilemap.tileHeight/2) - game.camera.view.y);
- this.context.stroke();
- //Draw circles on visited nodes
- if(showVisited !== false)
- {
- var visitedNode;
- for(var j=0; j < astar._lastPath.visited.length; j++)
- {
- visitedNode = astar._lastPath.visited[j];
- this.context.beginPath();
- this.context.arc((visitedNode.x * astar._tilemap.tileWidth) + (astar._tilemap.tileWidth/2) - game.camera.view.x, (visitedNode.y * astar._tilemap.tileHeight) + (astar._tilemap.tileHeight/2) - game.camera.view.y, 2, 0, Math.PI*2, true);
- this.context.stroke();
- }
- }
- }
- this.line('Path length: ' + pathLength);
- this.line('Distance func: ' + astar._distanceFunction);
- this.line('Use diagonal: ' + astar._useDiagonal);
- this.line('Find Closest: ' + astar._findClosest);
- game.debug.stop();
- };
|