├── LICENSE ├── README.md ├── assets ├── gridtiles.png ├── map.json ├── map.tmx └── phaserguy.png ├── index.html └── js ├── easystar.js ├── game.js ├── main.js └── phaser.js /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Jerome Renaux 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # pathfinding_tutorial 2 | 3 | Demo for a tutorial showing how to perform pathfinding and move a sprite accordingly with Phaser 3. 4 | 5 | The demo [can be found here](https://jerenaux.github.io/pathfinding_tutorial/). 6 | 7 | The tutorial is available [here](http://www.dynetisgames.com/2018/03/06/pathfinding-easystar-phaser-3/). 8 | 9 | ## Running the demo ## 10 | 11 | Place all the files on your local web server and navigate to index.html. 12 | 13 | You can move around by clicking on the map. The colored areas are tiles that have a cost attached to them, so the pathfinding algorithm witll try to avoid them if possible (unless all alternative paths are much longer). 14 | -------------------------------------------------------------------------------- /assets/gridtiles.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jerenaux/pathfinding_tutorial/1cefa2ef883c19b7335c0e4eb2729cc97c49b8f1/assets/gridtiles.png -------------------------------------------------------------------------------- /assets/map.json: -------------------------------------------------------------------------------- 1 | { "height":20, 2 | "layers":[ 3 | { 4 | "data":[20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 1, 20, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 20, 20, 1, 20, 1, 20, 20, 20, 20, 20, 20, 1, 62, 62, 62, 62, 1, 1, 20, 1, 20, 20, 1, 20, 1, 20, 1, 1, 1, 1, 1, 1, 58, 58, 58, 62, 1, 1, 20, 1, 20, 20, 1, 20, 1, 90, 1, 20, 20, 20, 20, 20, 20, 20, 58, 90, 1, 1, 20, 1, 20, 20, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 20, 1, 20, 20, 1, 1, 20, 20, 1, 20, 20, 20, 20, 20, 20, 20, 1, 1, 20, 1, 20, 1, 20, 20, 1, 20, 20, 20, 1, 20, 1, 20, 1, 1, 20, 1, 1, 1, 20, 1, 20, 1, 20, 20, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 20, 1, 20, 1, 20, 20, 1, 1, 20, 20, 20, 20, 20, 1, 1, 1, 20, 20, 20, 1, 20, 20, 20, 1, 20, 20, 1, 1, 20, 20, 20, 20, 20, 1, 20, 1, 1, 1, 20, 1, 20, 1, 20, 1, 20, 20, 1, 72, 72, 72, 72, 72, 1, 1, 20, 1, 20, 1, 20, 1, 20, 1, 20, 1, 20, 20, 1, 72, 62, 72, 62, 72, 1, 1, 20, 1, 1, 1, 20, 1, 20, 1, 20, 1, 20, 20, 1, 20, 20, 72, 20, 1, 1, 1, 20, 1, 1, 1, 1, 1, 1, 1, 1, 1, 20, 20, 1, 1, 20, 20, 20, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 20, 20, 1, 1, 1, 1, 1, 1, 1, 20, 20, 20, 20, 20, 20, 20, 1, 20, 1, 1, 20, 20, 1, 1, 20, 1, 1, 20, 1, 48, 47, 1, 1, 1, 1, 1, 1, 20, 1, 1, 20, 20, 1, 20, 20, 20, 20, 20, 1, 34, 34, 34, 30, 34, 34, 34, 34, 34, 1, 1, 20, 20, 1, 1, 1, 1, 1, 1, 1, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20], 5 | "height":20, 6 | "name":"ground", 7 | "opacity":1, 8 | "type":"tilelayer", 9 | "visible":true, 10 | "width":20, 11 | "x":0, 12 | "y":0 13 | }], 14 | "nextobjectid":1, 15 | "orientation":"orthogonal", 16 | "renderorder":"right-down", 17 | "tiledversion":"1.0.3", 18 | "tileheight":32, 19 | "tilesets":[ 20 | { 21 | "columns":14, 22 | "firstgid":1, 23 | "image":"gridtiles.png", 24 | "imageheight":320, 25 | "imagewidth":448, 26 | "margin":0, 27 | "name":"tiles", 28 | "spacing":0, 29 | "tilecount":140, 30 | "tileheight":32, 31 | "tileproperties": 32 | { 33 | "103": 34 | { 35 | "collide":true 36 | }, 37 | "19": 38 | { 39 | "collide":true 40 | }, 41 | "29": 42 | { 43 | "cost":2 44 | }, 45 | "33": 46 | { 47 | "collide":true 48 | }, 49 | "47": 50 | { 51 | "collide":true 52 | }, 53 | "57": 54 | { 55 | "cost":4 56 | }, 57 | "61": 58 | { 59 | "collide":true 60 | }, 61 | "71": 62 | { 63 | "cost":3 64 | }, 65 | "89": 66 | { 67 | "collide":true 68 | } 69 | }, 70 | "tilepropertytypes": 71 | { 72 | "103": 73 | { 74 | "collide":"bool" 75 | }, 76 | "19": 77 | { 78 | "collide":"bool" 79 | }, 80 | "29": 81 | { 82 | "cost":"int" 83 | }, 84 | "33": 85 | { 86 | "collide":"bool" 87 | }, 88 | "47": 89 | { 90 | "collide":"bool" 91 | }, 92 | "57": 93 | { 94 | "cost":"int" 95 | }, 96 | "61": 97 | { 98 | "collide":"bool" 99 | }, 100 | "71": 101 | { 102 | "cost":"int" 103 | }, 104 | "89": 105 | { 106 | "collide":"bool" 107 | } 108 | }, 109 | "tilewidth":32 110 | }], 111 | "tilewidth":32, 112 | "type":"map", 113 | "version":1, 114 | "width":20 115 | } -------------------------------------------------------------------------------- /assets/map.tmx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20, 54 | 20,1,20,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,20, 55 | 20,1,20,1,20,20,20,20,20,20,1,62,62,62,62,1,1,20,1,20, 56 | 20,1,20,1,20,1,1,1,1,1,1,58,58,58,62,1,1,20,1,20, 57 | 20,1,20,1,90,1,20,20,20,20,20,20,20,58,90,1,1,20,1,20, 58 | 20,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,20,1,20, 59 | 20,1,1,20,20,1,20,20,20,20,20,20,20,1,1,20,1,20,1,20, 60 | 20,1,20,20,20,1,20,1,20,1,1,20,1,1,1,20,1,20,1,20, 61 | 20,1,1,1,1,1,1,1,1,1,1,1,1,1,1,20,1,20,1,20, 62 | 20,1,1,20,20,20,20,20,1,1,1,20,20,20,1,20,20,20,1,20, 63 | 20,1,1,20,20,20,20,20,1,20,1,1,1,20,1,20,1,20,1,20, 64 | 20,1,72,72,72,72,72,1,1,20,1,20,1,20,1,20,1,20,1,20, 65 | 20,1,72,62,72,62,72,1,1,20,1,1,1,20,1,20,1,20,1,20, 66 | 20,1,20,20,72,20,1,1,1,20,1,1,1,1,1,1,1,1,1,20, 67 | 20,1,1,20,20,20,1,1,1,1,1,1,1,1,1,1,1,1,1,20, 68 | 20,1,1,1,1,1,1,1,20,20,20,20,20,20,20,1,20,1,1,20, 69 | 20,1,1,20,1,1,20,1,48,47,1,1,1,1,1,1,20,1,1,20, 70 | 20,1,20,20,20,20,20,1,34,34,34,30,34,34,34,34,34,1,1,20, 71 | 20,1,1,1,1,1,1,1,30,30,30,30,30,30,30,30,30,30,30,20, 72 | 20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20 73 | 74 | 75 | 76 | -------------------------------------------------------------------------------- /assets/phaserguy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jerenaux/pathfinding_tutorial/1cefa2ef883c19b7335c0e4eb2729cc97c49b8f1/assets/phaserguy.png -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Pathfinding tutorial 6 | 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /js/easystar.js: -------------------------------------------------------------------------------- 1 | var EasyStar = 2 | /******/ (function(modules) { // webpackBootstrap 3 | /******/ // The module cache 4 | /******/ var installedModules = {}; 5 | 6 | /******/ // The require function 7 | /******/ function __webpack_require__(moduleId) { 8 | 9 | /******/ // Check if module is in cache 10 | /******/ if(installedModules[moduleId]) 11 | /******/ return installedModules[moduleId].exports; 12 | 13 | /******/ // Create a new module (and put it into the cache) 14 | /******/ var module = installedModules[moduleId] = { 15 | /******/ exports: {}, 16 | /******/ id: moduleId, 17 | /******/ loaded: false 18 | /******/ }; 19 | 20 | /******/ // Execute the module function 21 | /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); 22 | 23 | /******/ // Flag the module as loaded 24 | /******/ module.loaded = true; 25 | 26 | /******/ // Return the exports of the module 27 | /******/ return module.exports; 28 | /******/ } 29 | 30 | 31 | /******/ // expose the modules object (__webpack_modules__) 32 | /******/ __webpack_require__.m = modules; 33 | 34 | /******/ // expose the module cache 35 | /******/ __webpack_require__.c = installedModules; 36 | 37 | /******/ // __webpack_public_path__ 38 | /******/ __webpack_require__.p = ""; 39 | 40 | /******/ // Load entry module and return exports 41 | /******/ return __webpack_require__(0); 42 | /******/ }) 43 | /************************************************************************/ 44 | /******/ ([ 45 | /* 0 */ 46 | /***/ (function(module, exports, __webpack_require__) { 47 | 48 | /** 49 | * EasyStar.js 50 | * github.com/prettymuchbryce/EasyStarJS 51 | * Licensed under the MIT license. 52 | * 53 | * Implementation By Bryce Neal (@prettymuchbryce) 54 | **/ 55 | 56 | var EasyStar = {}; 57 | var Instance = __webpack_require__(1); 58 | var Node = __webpack_require__(2); 59 | var Heap = __webpack_require__(3); 60 | 61 | const CLOSED_LIST = 0; 62 | const OPEN_LIST = 1; 63 | 64 | module.exports = EasyStar; 65 | 66 | var nextInstanceId = 1; 67 | 68 | EasyStar.js = function () { 69 | var STRAIGHT_COST = 1.0; 70 | var DIAGONAL_COST = 1.4; 71 | var syncEnabled = false; 72 | var pointsToAvoid = {}; 73 | var collisionGrid; 74 | var costMap = {}; 75 | var pointsToCost = {}; 76 | var directionalConditions = {}; 77 | var allowCornerCutting = true; 78 | var iterationsSoFar; 79 | var instances = {}; 80 | var instanceQueue = []; 81 | var iterationsPerCalculation = Number.MAX_VALUE; 82 | var acceptableTiles; 83 | var diagonalsEnabled = false; 84 | 85 | /** 86 | * Sets the collision grid that EasyStar uses. 87 | * 88 | * @param {Array|Number} tiles An array of numbers that represent 89 | * which tiles in your grid should be considered 90 | * acceptable, or "walkable". 91 | **/ 92 | this.setAcceptableTiles = function (tiles) { 93 | if (tiles instanceof Array) { 94 | // Array 95 | acceptableTiles = tiles; 96 | } else if (!isNaN(parseFloat(tiles)) && isFinite(tiles)) { 97 | // Number 98 | acceptableTiles = [tiles]; 99 | } 100 | }; 101 | 102 | /** 103 | * Enables sync mode for this EasyStar instance.. 104 | * if you're into that sort of thing. 105 | **/ 106 | this.enableSync = function () { 107 | syncEnabled = true; 108 | }; 109 | 110 | /** 111 | * Disables sync mode for this EasyStar instance. 112 | **/ 113 | this.disableSync = function () { 114 | syncEnabled = false; 115 | }; 116 | 117 | /** 118 | * Enable diagonal pathfinding. 119 | */ 120 | this.enableDiagonals = function () { 121 | diagonalsEnabled = true; 122 | }; 123 | 124 | /** 125 | * Disable diagonal pathfinding. 126 | */ 127 | this.disableDiagonals = function () { 128 | diagonalsEnabled = false; 129 | }; 130 | 131 | /** 132 | * Sets the collision grid that EasyStar uses. 133 | * 134 | * @param {Array} grid The collision grid that this EasyStar instance will read from. 135 | * This should be a 2D Array of Numbers. 136 | **/ 137 | this.setGrid = function (grid) { 138 | collisionGrid = grid; 139 | 140 | //Setup cost map 141 | for (var y = 0; y < collisionGrid.length; y++) { 142 | for (var x = 0; x < collisionGrid[0].length; x++) { 143 | if (!costMap[collisionGrid[y][x]]) { 144 | costMap[collisionGrid[y][x]] = 1; 145 | } 146 | } 147 | } 148 | }; 149 | 150 | /** 151 | * Sets the tile cost for a particular tile type. 152 | * 153 | * @param {Number} The tile type to set the cost for. 154 | * @param {Number} The multiplicative cost associated with the given tile. 155 | **/ 156 | this.setTileCost = function (tileType, cost) { 157 | costMap[tileType] = cost; 158 | }; 159 | 160 | /** 161 | * Sets the an additional cost for a particular point. 162 | * Overrides the cost from setTileCost. 163 | * 164 | * @param {Number} x The x value of the point to cost. 165 | * @param {Number} y The y value of the point to cost. 166 | * @param {Number} The multiplicative cost associated with the given point. 167 | **/ 168 | this.setAdditionalPointCost = function (x, y, cost) { 169 | if (pointsToCost[y] === undefined) { 170 | pointsToCost[y] = {}; 171 | } 172 | pointsToCost[y][x] = cost; 173 | }; 174 | 175 | /** 176 | * Remove the additional cost for a particular point. 177 | * 178 | * @param {Number} x The x value of the point to stop costing. 179 | * @param {Number} y The y value of the point to stop costing. 180 | **/ 181 | this.removeAdditionalPointCost = function (x, y) { 182 | if (pointsToCost[y] !== undefined) { 183 | delete pointsToCost[y][x]; 184 | } 185 | }; 186 | 187 | /** 188 | * Remove all additional point costs. 189 | **/ 190 | this.removeAllAdditionalPointCosts = function () { 191 | pointsToCost = {}; 192 | }; 193 | 194 | /** 195 | * Sets a directional condition on a tile 196 | * 197 | * @param {Number} x The x value of the point. 198 | * @param {Number} y The y value of the point. 199 | * @param {Array.} allowedDirections A list of all the allowed directions that can access 200 | * the tile. 201 | **/ 202 | this.setDirectionalCondition = function (x, y, allowedDirections) { 203 | if (directionalConditions[y] === undefined) { 204 | directionalConditions[y] = {}; 205 | } 206 | directionalConditions[y][x] = allowedDirections; 207 | }; 208 | 209 | /** 210 | * Remove all directional conditions 211 | **/ 212 | this.removeAllDirectionalConditions = function () { 213 | directionalConditions = {}; 214 | }; 215 | 216 | /** 217 | * Sets the number of search iterations per calculation. 218 | * A lower number provides a slower result, but more practical if you 219 | * have a large tile-map and don't want to block your thread while 220 | * finding a path. 221 | * 222 | * @param {Number} iterations The number of searches to prefrom per calculate() call. 223 | **/ 224 | this.setIterationsPerCalculation = function (iterations) { 225 | iterationsPerCalculation = iterations; 226 | }; 227 | 228 | /** 229 | * Avoid a particular point on the grid, 230 | * regardless of whether or not it is an acceptable tile. 231 | * 232 | * @param {Number} x The x value of the point to avoid. 233 | * @param {Number} y The y value of the point to avoid. 234 | **/ 235 | this.avoidAdditionalPoint = function (x, y) { 236 | if (pointsToAvoid[y] === undefined) { 237 | pointsToAvoid[y] = {}; 238 | } 239 | pointsToAvoid[y][x] = 1; 240 | }; 241 | 242 | /** 243 | * Stop avoiding a particular point on the grid. 244 | * 245 | * @param {Number} x The x value of the point to stop avoiding. 246 | * @param {Number} y The y value of the point to stop avoiding. 247 | **/ 248 | this.stopAvoidingAdditionalPoint = function (x, y) { 249 | if (pointsToAvoid[y] !== undefined) { 250 | delete pointsToAvoid[y][x]; 251 | } 252 | }; 253 | 254 | /** 255 | * Enables corner cutting in diagonal movement. 256 | **/ 257 | this.enableCornerCutting = function () { 258 | allowCornerCutting = true; 259 | }; 260 | 261 | /** 262 | * Disables corner cutting in diagonal movement. 263 | **/ 264 | this.disableCornerCutting = function () { 265 | allowCornerCutting = false; 266 | }; 267 | 268 | /** 269 | * Stop avoiding all additional points on the grid. 270 | **/ 271 | this.stopAvoidingAllAdditionalPoints = function () { 272 | pointsToAvoid = {}; 273 | }; 274 | 275 | /** 276 | * Find a path. 277 | * 278 | * @param {Number} startX The X position of the starting point. 279 | * @param {Number} startY The Y position of the starting point. 280 | * @param {Number} endX The X position of the ending point. 281 | * @param {Number} endY The Y position of the ending point. 282 | * @param {Function} callback A function that is called when your path 283 | * is found, or no path is found. 284 | * @return {Number} A numeric, non-zero value which identifies the created instance. This value can be passed to cancelPath to cancel the path calculation. 285 | * 286 | **/ 287 | this.findPath = function (startX, startY, endX, endY, callback) { 288 | // Wraps the callback for sync vs async logic 289 | var callbackWrapper = function (result) { 290 | if (syncEnabled) { 291 | callback(result); 292 | } else { 293 | setTimeout(function () { 294 | callback(result); 295 | }); 296 | } 297 | }; 298 | 299 | // No acceptable tiles were set 300 | if (acceptableTiles === undefined) { 301 | throw new Error("You can't set a path without first calling setAcceptableTiles() on EasyStar."); 302 | } 303 | // No grid was set 304 | if (collisionGrid === undefined) { 305 | throw new Error("You can't set a path without first calling setGrid() on EasyStar."); 306 | } 307 | 308 | // Start or endpoint outside of scope. 309 | if (startX < 0 || startY < 0 || endX < 0 || endY < 0 || startX > collisionGrid[0].length - 1 || startY > collisionGrid.length - 1 || endX > collisionGrid[0].length - 1 || endY > collisionGrid.length - 1) { 310 | throw new Error("Your start or end point is outside the scope of your grid."); 311 | } 312 | 313 | // Start and end are the same tile. 314 | if (startX === endX && startY === endY) { 315 | callbackWrapper([]); 316 | return; 317 | } 318 | 319 | // End point is not an acceptable tile. 320 | var endTile = collisionGrid[endY][endX]; 321 | var isAcceptable = false; 322 | for (var i = 0; i < acceptableTiles.length; i++) { 323 | if (endTile === acceptableTiles[i]) { 324 | isAcceptable = true; 325 | break; 326 | } 327 | } 328 | 329 | if (isAcceptable === false) { 330 | callbackWrapper(null); 331 | return; 332 | } 333 | 334 | // Create the instance 335 | var instance = new Instance(); 336 | instance.openList = new Heap(function (nodeA, nodeB) { 337 | return nodeA.bestGuessDistance() - nodeB.bestGuessDistance(); 338 | }); 339 | instance.isDoneCalculating = false; 340 | instance.nodeHash = {}; 341 | instance.startX = startX; 342 | instance.startY = startY; 343 | instance.endX = endX; 344 | instance.endY = endY; 345 | instance.callback = callbackWrapper; 346 | 347 | instance.openList.push(coordinateToNode(instance, instance.startX, instance.startY, null, STRAIGHT_COST)); 348 | 349 | var instanceId = nextInstanceId++; 350 | instances[instanceId] = instance; 351 | instanceQueue.push(instanceId); 352 | return instanceId; 353 | }; 354 | 355 | /** 356 | * Cancel a path calculation. 357 | * 358 | * @param {Number} instanceId The instance ID of the path being calculated 359 | * @return {Boolean} True if an instance was found and cancelled. 360 | * 361 | **/ 362 | this.cancelPath = function (instanceId) { 363 | if (instanceId in instances) { 364 | delete instances[instanceId]; 365 | // No need to remove it from instanceQueue 366 | return true; 367 | } 368 | return false; 369 | }; 370 | 371 | /** 372 | * This method steps through the A* Algorithm in an attempt to 373 | * find your path(s). It will search 4-8 tiles (depending on diagonals) for every calculation. 374 | * You can change the number of calculations done in a call by using 375 | * easystar.setIteratonsPerCalculation(). 376 | **/ 377 | this.calculate = function () { 378 | if (instanceQueue.length === 0 || collisionGrid === undefined || acceptableTiles === undefined) { 379 | return; 380 | } 381 | for (iterationsSoFar = 0; iterationsSoFar < iterationsPerCalculation; iterationsSoFar++) { 382 | if (instanceQueue.length === 0) { 383 | return; 384 | } 385 | 386 | if (syncEnabled) { 387 | // If this is a sync instance, we want to make sure that it calculates synchronously. 388 | iterationsSoFar = 0; 389 | } 390 | 391 | var instanceId = instanceQueue[0]; 392 | var instance = instances[instanceId]; 393 | if (typeof instance == 'undefined') { 394 | // This instance was cancelled 395 | instanceQueue.shift(); 396 | continue; 397 | } 398 | 399 | // Couldn't find a path. 400 | if (instance.openList.size() === 0) { 401 | instance.callback(null); 402 | delete instances[instanceId]; 403 | instanceQueue.shift(); 404 | continue; 405 | } 406 | 407 | var searchNode = instance.openList.pop(); 408 | 409 | // Handles the case where we have found the destination 410 | if (instance.endX === searchNode.x && instance.endY === searchNode.y) { 411 | var path = []; 412 | path.push({ x: searchNode.x, y: searchNode.y }); 413 | var parent = searchNode.parent; 414 | while (parent != null) { 415 | path.push({ x: parent.x, y: parent.y }); 416 | parent = parent.parent; 417 | } 418 | path.reverse(); 419 | var ip = path; 420 | instance.callback(ip); 421 | delete instances[instanceId]; 422 | instanceQueue.shift(); 423 | continue; 424 | } 425 | 426 | searchNode.list = CLOSED_LIST; 427 | 428 | if (searchNode.y > 0) { 429 | checkAdjacentNode(instance, searchNode, 0, -1, STRAIGHT_COST * getTileCost(searchNode.x, searchNode.y - 1)); 430 | } 431 | if (searchNode.x < collisionGrid[0].length - 1) { 432 | checkAdjacentNode(instance, searchNode, 1, 0, STRAIGHT_COST * getTileCost(searchNode.x + 1, searchNode.y)); 433 | } 434 | if (searchNode.y < collisionGrid.length - 1) { 435 | checkAdjacentNode(instance, searchNode, 0, 1, STRAIGHT_COST * getTileCost(searchNode.x, searchNode.y + 1)); 436 | } 437 | if (searchNode.x > 0) { 438 | checkAdjacentNode(instance, searchNode, -1, 0, STRAIGHT_COST * getTileCost(searchNode.x - 1, searchNode.y)); 439 | } 440 | if (diagonalsEnabled) { 441 | if (searchNode.x > 0 && searchNode.y > 0) { 442 | 443 | if (allowCornerCutting || isTileWalkable(collisionGrid, acceptableTiles, searchNode.x, searchNode.y - 1, searchNode) && isTileWalkable(collisionGrid, acceptableTiles, searchNode.x - 1, searchNode.y, searchNode)) { 444 | 445 | checkAdjacentNode(instance, searchNode, -1, -1, DIAGONAL_COST * getTileCost(searchNode.x - 1, searchNode.y - 1)); 446 | } 447 | } 448 | if (searchNode.x < collisionGrid[0].length - 1 && searchNode.y < collisionGrid.length - 1) { 449 | 450 | if (allowCornerCutting || isTileWalkable(collisionGrid, acceptableTiles, searchNode.x, searchNode.y + 1, searchNode) && isTileWalkable(collisionGrid, acceptableTiles, searchNode.x + 1, searchNode.y, searchNode)) { 451 | 452 | checkAdjacentNode(instance, searchNode, 1, 1, DIAGONAL_COST * getTileCost(searchNode.x + 1, searchNode.y + 1)); 453 | } 454 | } 455 | if (searchNode.x < collisionGrid[0].length - 1 && searchNode.y > 0) { 456 | 457 | if (allowCornerCutting || isTileWalkable(collisionGrid, acceptableTiles, searchNode.x, searchNode.y - 1, searchNode) && isTileWalkable(collisionGrid, acceptableTiles, searchNode.x + 1, searchNode.y, searchNode)) { 458 | 459 | checkAdjacentNode(instance, searchNode, 1, -1, DIAGONAL_COST * getTileCost(searchNode.x + 1, searchNode.y - 1)); 460 | } 461 | } 462 | if (searchNode.x > 0 && searchNode.y < collisionGrid.length - 1) { 463 | 464 | if (allowCornerCutting || isTileWalkable(collisionGrid, acceptableTiles, searchNode.x, searchNode.y + 1, searchNode) && isTileWalkable(collisionGrid, acceptableTiles, searchNode.x - 1, searchNode.y, searchNode)) { 465 | 466 | checkAdjacentNode(instance, searchNode, -1, 1, DIAGONAL_COST * getTileCost(searchNode.x - 1, searchNode.y + 1)); 467 | } 468 | } 469 | } 470 | } 471 | }; 472 | 473 | // Private methods follow 474 | var checkAdjacentNode = function (instance, searchNode, x, y, cost) { 475 | var adjacentCoordinateX = searchNode.x + x; 476 | var adjacentCoordinateY = searchNode.y + y; 477 | 478 | if ((pointsToAvoid[adjacentCoordinateY] === undefined || pointsToAvoid[adjacentCoordinateY][adjacentCoordinateX] === undefined) && isTileWalkable(collisionGrid, acceptableTiles, adjacentCoordinateX, adjacentCoordinateY, searchNode)) { 479 | var node = coordinateToNode(instance, adjacentCoordinateX, adjacentCoordinateY, searchNode, cost); 480 | 481 | if (node.list === undefined) { 482 | node.list = OPEN_LIST; 483 | instance.openList.push(node); 484 | } else if (searchNode.costSoFar + cost < node.costSoFar) { 485 | node.costSoFar = searchNode.costSoFar + cost; 486 | node.parent = searchNode; 487 | instance.openList.updateItem(node); 488 | } 489 | } 490 | }; 491 | 492 | // Helpers 493 | var isTileWalkable = function (collisionGrid, acceptableTiles, x, y, sourceNode) { 494 | var directionalCondition = directionalConditions[y] && directionalConditions[y][x]; 495 | if (directionalCondition) { 496 | var direction = calculateDirection(sourceNode.x - x, sourceNode.y - y); 497 | var directionIncluded = function () { 498 | for (var i = 0; i < directionalCondition.length; i++) { 499 | if (directionalCondition[i] === direction) return true; 500 | } 501 | return false; 502 | }; 503 | if (!directionIncluded()) return false; 504 | } 505 | for (var i = 0; i < acceptableTiles.length; i++) { 506 | if (collisionGrid[y][x] === acceptableTiles[i]) { 507 | return true; 508 | } 509 | } 510 | 511 | return false; 512 | }; 513 | 514 | /** 515 | * -1, -1 | 0, -1 | 1, -1 516 | * -1, 0 | SOURCE | 1, 0 517 | * -1, 1 | 0, 1 | 1, 1 518 | */ 519 | var calculateDirection = function (diffX, diffY) { 520 | if (diffX === 0 && diffY === -1) return EasyStar.TOP;else if (diffX === 1 && diffY === -1) return EasyStar.TOP_RIGHT;else if (diffX === 1 && diffY === 0) return EasyStar.RIGHT;else if (diffX === 1 && diffY === 1) return EasyStar.BOTTOM_RIGHT;else if (diffX === 0 && diffY === 1) return EasyStar.BOTTOM;else if (diffX === -1 && diffY === 1) return EasyStar.BOTTOM_LEFT;else if (diffX === -1 && diffY === 0) return EasyStar.LEFT;else if (diffX === -1 && diffY === -1) return EasyStar.TOP_LEFT; 521 | throw new Error('These differences are not valid: ' + diffX + ', ' + diffY); 522 | }; 523 | 524 | var getTileCost = function (x, y) { 525 | return pointsToCost[y] && pointsToCost[y][x] || costMap[collisionGrid[y][x]]; 526 | }; 527 | 528 | var coordinateToNode = function (instance, x, y, parent, cost) { 529 | if (instance.nodeHash[y] !== undefined) { 530 | if (instance.nodeHash[y][x] !== undefined) { 531 | return instance.nodeHash[y][x]; 532 | } 533 | } else { 534 | instance.nodeHash[y] = {}; 535 | } 536 | var simpleDistanceToTarget = getDistance(x, y, instance.endX, instance.endY); 537 | if (parent !== null) { 538 | var costSoFar = parent.costSoFar + cost; 539 | } else { 540 | costSoFar = 0; 541 | } 542 | var node = new Node(parent, x, y, costSoFar, simpleDistanceToTarget); 543 | instance.nodeHash[y][x] = node; 544 | return node; 545 | }; 546 | 547 | var getDistance = function (x1, y1, x2, y2) { 548 | if (diagonalsEnabled) { 549 | // Octile distance 550 | var dx = Math.abs(x1 - x2); 551 | var dy = Math.abs(y1 - y2); 552 | if (dx < dy) { 553 | return DIAGONAL_COST * dx + dy; 554 | } else { 555 | return DIAGONAL_COST * dy + dx; 556 | } 557 | } else { 558 | // Manhattan distance 559 | var dx = Math.abs(x1 - x2); 560 | var dy = Math.abs(y1 - y2); 561 | return dx + dy; 562 | } 563 | }; 564 | }; 565 | 566 | EasyStar.TOP = 'TOP'; 567 | EasyStar.TOP_RIGHT = 'TOP_RIGHT'; 568 | EasyStar.RIGHT = 'RIGHT'; 569 | EasyStar.BOTTOM_RIGHT = 'BOTTOM_RIGHT'; 570 | EasyStar.BOTTOM = 'BOTTOM'; 571 | EasyStar.BOTTOM_LEFT = 'BOTTOM_LEFT'; 572 | EasyStar.LEFT = 'LEFT'; 573 | EasyStar.TOP_LEFT = 'TOP_LEFT'; 574 | 575 | /***/ }), 576 | /* 1 */ 577 | /***/ (function(module, exports) { 578 | 579 | /** 580 | * Represents a single instance of EasyStar. 581 | * A path that is in the queue to eventually be found. 582 | */ 583 | module.exports = function () { 584 | this.pointsToAvoid = {}; 585 | this.startX; 586 | this.callback; 587 | this.startY; 588 | this.endX; 589 | this.endY; 590 | this.nodeHash = {}; 591 | this.openList; 592 | }; 593 | 594 | /***/ }), 595 | /* 2 */ 596 | /***/ (function(module, exports) { 597 | 598 | /** 599 | * A simple Node that represents a single tile on the grid. 600 | * @param {Object} parent The parent node. 601 | * @param {Number} x The x position on the grid. 602 | * @param {Number} y The y position on the grid. 603 | * @param {Number} costSoFar How far this node is in moves*cost from the start. 604 | * @param {Number} simpleDistanceToTarget Manhatten distance to the end point. 605 | **/ 606 | module.exports = function (parent, x, y, costSoFar, simpleDistanceToTarget) { 607 | this.parent = parent; 608 | this.x = x; 609 | this.y = y; 610 | this.costSoFar = costSoFar; 611 | this.simpleDistanceToTarget = simpleDistanceToTarget; 612 | 613 | /** 614 | * @return {Number} Best guess distance of a cost using this node. 615 | **/ 616 | this.bestGuessDistance = function () { 617 | return this.costSoFar + this.simpleDistanceToTarget; 618 | }; 619 | }; 620 | 621 | /***/ }), 622 | /* 3 */ 623 | /***/ (function(module, exports, __webpack_require__) { 624 | 625 | module.exports = __webpack_require__(4); 626 | 627 | /***/ }), 628 | /* 4 */ 629 | /***/ (function(module, exports, __webpack_require__) { 630 | 631 | var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;// Generated by CoffeeScript 1.8.0 632 | (function () { 633 | var Heap, defaultCmp, floor, heapify, heappop, heappush, heappushpop, heapreplace, insort, min, nlargest, nsmallest, updateItem, _siftdown, _siftup; 634 | 635 | floor = Math.floor, min = Math.min; 636 | 637 | /* 638 | Default comparison function to be used 639 | */ 640 | 641 | defaultCmp = function (x, y) { 642 | if (x < y) { 643 | return -1; 644 | } 645 | if (x > y) { 646 | return 1; 647 | } 648 | return 0; 649 | }; 650 | 651 | /* 652 | Insert item x in list a, and keep it sorted assuming a is sorted. 653 | 654 | If x is already in a, insert it to the right of the rightmost x. 655 | 656 | Optional args lo (default 0) and hi (default a.length) bound the slice 657 | of a to be searched. 658 | */ 659 | 660 | insort = function (a, x, lo, hi, cmp) { 661 | var mid; 662 | if (lo == null) { 663 | lo = 0; 664 | } 665 | if (cmp == null) { 666 | cmp = defaultCmp; 667 | } 668 | if (lo < 0) { 669 | throw new Error('lo must be non-negative'); 670 | } 671 | if (hi == null) { 672 | hi = a.length; 673 | } 674 | while (lo < hi) { 675 | mid = floor((lo + hi) / 2); 676 | if (cmp(x, a[mid]) < 0) { 677 | hi = mid; 678 | } else { 679 | lo = mid + 1; 680 | } 681 | } 682 | return [].splice.apply(a, [lo, lo - lo].concat(x)), x; 683 | }; 684 | 685 | /* 686 | Push item onto heap, maintaining the heap invariant. 687 | */ 688 | 689 | heappush = function (array, item, cmp) { 690 | if (cmp == null) { 691 | cmp = defaultCmp; 692 | } 693 | array.push(item); 694 | return _siftdown(array, 0, array.length - 1, cmp); 695 | }; 696 | 697 | /* 698 | Pop the smallest item off the heap, maintaining the heap invariant. 699 | */ 700 | 701 | heappop = function (array, cmp) { 702 | var lastelt, returnitem; 703 | if (cmp == null) { 704 | cmp = defaultCmp; 705 | } 706 | lastelt = array.pop(); 707 | if (array.length) { 708 | returnitem = array[0]; 709 | array[0] = lastelt; 710 | _siftup(array, 0, cmp); 711 | } else { 712 | returnitem = lastelt; 713 | } 714 | return returnitem; 715 | }; 716 | 717 | /* 718 | Pop and return the current smallest value, and add the new item. 719 | 720 | This is more efficient than heappop() followed by heappush(), and can be 721 | more appropriate when using a fixed size heap. Note that the value 722 | returned may be larger than item! That constrains reasonable use of 723 | this routine unless written as part of a conditional replacement: 724 | if item > array[0] 725 | item = heapreplace(array, item) 726 | */ 727 | 728 | heapreplace = function (array, item, cmp) { 729 | var returnitem; 730 | if (cmp == null) { 731 | cmp = defaultCmp; 732 | } 733 | returnitem = array[0]; 734 | array[0] = item; 735 | _siftup(array, 0, cmp); 736 | return returnitem; 737 | }; 738 | 739 | /* 740 | Fast version of a heappush followed by a heappop. 741 | */ 742 | 743 | heappushpop = function (array, item, cmp) { 744 | var _ref; 745 | if (cmp == null) { 746 | cmp = defaultCmp; 747 | } 748 | if (array.length && cmp(array[0], item) < 0) { 749 | _ref = [array[0], item], item = _ref[0], array[0] = _ref[1]; 750 | _siftup(array, 0, cmp); 751 | } 752 | return item; 753 | }; 754 | 755 | /* 756 | Transform list into a heap, in-place, in O(array.length) time. 757 | */ 758 | 759 | heapify = function (array, cmp) { 760 | var i, _i, _j, _len, _ref, _ref1, _results, _results1; 761 | if (cmp == null) { 762 | cmp = defaultCmp; 763 | } 764 | _ref1 = function () { 765 | _results1 = []; 766 | for (var _j = 0, _ref = floor(array.length / 2); 0 <= _ref ? _j < _ref : _j > _ref; 0 <= _ref ? _j++ : _j--) { 767 | _results1.push(_j); 768 | } 769 | return _results1; 770 | }.apply(this).reverse(); 771 | _results = []; 772 | for (_i = 0, _len = _ref1.length; _i < _len; _i++) { 773 | i = _ref1[_i]; 774 | _results.push(_siftup(array, i, cmp)); 775 | } 776 | return _results; 777 | }; 778 | 779 | /* 780 | Update the position of the given item in the heap. 781 | This function should be called every time the item is being modified. 782 | */ 783 | 784 | updateItem = function (array, item, cmp) { 785 | var pos; 786 | if (cmp == null) { 787 | cmp = defaultCmp; 788 | } 789 | pos = array.indexOf(item); 790 | if (pos === -1) { 791 | return; 792 | } 793 | _siftdown(array, 0, pos, cmp); 794 | return _siftup(array, pos, cmp); 795 | }; 796 | 797 | /* 798 | Find the n largest elements in a dataset. 799 | */ 800 | 801 | nlargest = function (array, n, cmp) { 802 | var elem, result, _i, _len, _ref; 803 | if (cmp == null) { 804 | cmp = defaultCmp; 805 | } 806 | result = array.slice(0, n); 807 | if (!result.length) { 808 | return result; 809 | } 810 | heapify(result, cmp); 811 | _ref = array.slice(n); 812 | for (_i = 0, _len = _ref.length; _i < _len; _i++) { 813 | elem = _ref[_i]; 814 | heappushpop(result, elem, cmp); 815 | } 816 | return result.sort(cmp).reverse(); 817 | }; 818 | 819 | /* 820 | Find the n smallest elements in a dataset. 821 | */ 822 | 823 | nsmallest = function (array, n, cmp) { 824 | var elem, i, los, result, _i, _j, _len, _ref, _ref1, _results; 825 | if (cmp == null) { 826 | cmp = defaultCmp; 827 | } 828 | if (n * 10 <= array.length) { 829 | result = array.slice(0, n).sort(cmp); 830 | if (!result.length) { 831 | return result; 832 | } 833 | los = result[result.length - 1]; 834 | _ref = array.slice(n); 835 | for (_i = 0, _len = _ref.length; _i < _len; _i++) { 836 | elem = _ref[_i]; 837 | if (cmp(elem, los) < 0) { 838 | insort(result, elem, 0, null, cmp); 839 | result.pop(); 840 | los = result[result.length - 1]; 841 | } 842 | } 843 | return result; 844 | } 845 | heapify(array, cmp); 846 | _results = []; 847 | for (i = _j = 0, _ref1 = min(n, array.length); 0 <= _ref1 ? _j < _ref1 : _j > _ref1; i = 0 <= _ref1 ? ++_j : --_j) { 848 | _results.push(heappop(array, cmp)); 849 | } 850 | return _results; 851 | }; 852 | 853 | _siftdown = function (array, startpos, pos, cmp) { 854 | var newitem, parent, parentpos; 855 | if (cmp == null) { 856 | cmp = defaultCmp; 857 | } 858 | newitem = array[pos]; 859 | while (pos > startpos) { 860 | parentpos = pos - 1 >> 1; 861 | parent = array[parentpos]; 862 | if (cmp(newitem, parent) < 0) { 863 | array[pos] = parent; 864 | pos = parentpos; 865 | continue; 866 | } 867 | break; 868 | } 869 | return array[pos] = newitem; 870 | }; 871 | 872 | _siftup = function (array, pos, cmp) { 873 | var childpos, endpos, newitem, rightpos, startpos; 874 | if (cmp == null) { 875 | cmp = defaultCmp; 876 | } 877 | endpos = array.length; 878 | startpos = pos; 879 | newitem = array[pos]; 880 | childpos = 2 * pos + 1; 881 | while (childpos < endpos) { 882 | rightpos = childpos + 1; 883 | if (rightpos < endpos && !(cmp(array[childpos], array[rightpos]) < 0)) { 884 | childpos = rightpos; 885 | } 886 | array[pos] = array[childpos]; 887 | pos = childpos; 888 | childpos = 2 * pos + 1; 889 | } 890 | array[pos] = newitem; 891 | return _siftdown(array, startpos, pos, cmp); 892 | }; 893 | 894 | Heap = function () { 895 | Heap.push = heappush; 896 | 897 | Heap.pop = heappop; 898 | 899 | Heap.replace = heapreplace; 900 | 901 | Heap.pushpop = heappushpop; 902 | 903 | Heap.heapify = heapify; 904 | 905 | Heap.updateItem = updateItem; 906 | 907 | Heap.nlargest = nlargest; 908 | 909 | Heap.nsmallest = nsmallest; 910 | 911 | function Heap(cmp) { 912 | this.cmp = cmp != null ? cmp : defaultCmp; 913 | this.nodes = []; 914 | } 915 | 916 | Heap.prototype.push = function (x) { 917 | return heappush(this.nodes, x, this.cmp); 918 | }; 919 | 920 | Heap.prototype.pop = function () { 921 | return heappop(this.nodes, this.cmp); 922 | }; 923 | 924 | Heap.prototype.peek = function () { 925 | return this.nodes[0]; 926 | }; 927 | 928 | Heap.prototype.contains = function (x) { 929 | return this.nodes.indexOf(x) !== -1; 930 | }; 931 | 932 | Heap.prototype.replace = function (x) { 933 | return heapreplace(this.nodes, x, this.cmp); 934 | }; 935 | 936 | Heap.prototype.pushpop = function (x) { 937 | return heappushpop(this.nodes, x, this.cmp); 938 | }; 939 | 940 | Heap.prototype.heapify = function () { 941 | return heapify(this.nodes, this.cmp); 942 | }; 943 | 944 | Heap.prototype.updateItem = function (x) { 945 | return updateItem(this.nodes, x, this.cmp); 946 | }; 947 | 948 | Heap.prototype.clear = function () { 949 | return this.nodes = []; 950 | }; 951 | 952 | Heap.prototype.empty = function () { 953 | return this.nodes.length === 0; 954 | }; 955 | 956 | Heap.prototype.size = function () { 957 | return this.nodes.length; 958 | }; 959 | 960 | Heap.prototype.clone = function () { 961 | var heap; 962 | heap = new Heap(); 963 | heap.nodes = this.nodes.slice(0); 964 | return heap; 965 | }; 966 | 967 | Heap.prototype.toArray = function () { 968 | return this.nodes.slice(0); 969 | }; 970 | 971 | Heap.prototype.insert = Heap.prototype.push; 972 | 973 | Heap.prototype.top = Heap.prototype.peek; 974 | 975 | Heap.prototype.front = Heap.prototype.peek; 976 | 977 | Heap.prototype.has = Heap.prototype.contains; 978 | 979 | Heap.prototype.copy = Heap.prototype.clone; 980 | 981 | return Heap; 982 | }(); 983 | 984 | (function (root, factory) { 985 | if (true) { 986 | return !(__WEBPACK_AMD_DEFINE_ARRAY__ = [], __WEBPACK_AMD_DEFINE_FACTORY__ = (factory), __WEBPACK_AMD_DEFINE_RESULT__ = (typeof __WEBPACK_AMD_DEFINE_FACTORY__ === 'function' ? (__WEBPACK_AMD_DEFINE_FACTORY__.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__)) : __WEBPACK_AMD_DEFINE_FACTORY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__)); 987 | } else if (typeof exports === 'object') { 988 | return module.exports = factory(); 989 | } else { 990 | return root.Heap = factory(); 991 | } 992 | })(this, function () { 993 | return Heap; 994 | }); 995 | }).call(this); 996 | 997 | /***/ }) 998 | /******/ ]); -------------------------------------------------------------------------------- /js/game.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Jerome Renaux (jerome.renaux@gmail.com) on 25-02-18. 3 | */ 4 | var Game = {}; 5 | 6 | Game.preload = function(){ 7 | Game.scene = this; // Handy reference to the scene (alternative to `this` binding) 8 | this.load.image('tileset', 'assets/gridtiles.png'); 9 | this.load.tilemapTiledJSON('map', 'assets/map.json'); 10 | this.load.image('phaserguy', 'assets/phaserguy.png'); 11 | }; 12 | 13 | Game.create = function(){ 14 | // Handles the clicks on the map to make the character move 15 | this.input.on('pointerup',Game.handleClick); 16 | 17 | Game.camera = this.cameras.main; 18 | Game.camera.setBounds(0, 0, 20*32, 20*32); 19 | 20 | var phaserGuy = this.add.image(32,32,'phaserguy'); 21 | phaserGuy.setDepth(1); 22 | phaserGuy.setOrigin(0,0.5); 23 | Game.camera.startFollow(phaserGuy); 24 | Game.player = phaserGuy; 25 | 26 | // Display map 27 | Game.map = Game.scene.make.tilemap({ key: 'map'}); 28 | // The first parameter is the name of the tileset in Tiled and the second parameter is the key 29 | // of the tileset image used when loading the file in preload. 30 | var tiles = Game.map.addTilesetImage('tiles', 'tileset'); 31 | Game.map.createStaticLayer(0, tiles, 0,0); 32 | 33 | // Marker that will follow the mouse 34 | Game.marker = this.add.graphics(); 35 | Game.marker.lineStyle(3, 0xffffff, 1); 36 | Game.marker.strokeRect(0, 0, Game.map.tileWidth, Game.map.tileHeight); 37 | 38 | // ### Pathfinding stuff ### 39 | // Initializing the pathfinder 40 | Game.finder = new EasyStar.js(); 41 | 42 | // We create the 2D array representing all the tiles of our map 43 | var grid = []; 44 | for(var y = 0; y < Game.map.height; y++){ 45 | var col = []; 46 | for(var x = 0; x < Game.map.width; x++){ 47 | // In each cell we store the ID of the tile, which corresponds 48 | // to its index in the tileset of the map ("ID" field in Tiled) 49 | col.push(Game.getTileID(x,y)); 50 | } 51 | grid.push(col); 52 | } 53 | Game.finder.setGrid(grid); 54 | 55 | var tileset = Game.map.tilesets[0]; 56 | var properties = tileset.tileProperties; 57 | var acceptableTiles = []; 58 | 59 | // We need to list all the tile IDs that can be walked on. Let's iterate over all of them 60 | // and see what properties have been entered in Tiled. 61 | for(var i = tileset.firstgid-1; i < tiles.total; i++){ // firstgid and total are fields from Tiled that indicate the range of IDs that the tiles can take in that tileset 62 | if(!properties.hasOwnProperty(i)) { 63 | // If there is no property indicated at all, it means it's a walkable tile 64 | acceptableTiles.push(i+1); 65 | continue; 66 | } 67 | if(!properties[i].collide) acceptableTiles.push(i+1); 68 | if(properties[i].cost) Game.finder.setTileCost(i+1, properties[i].cost); // If there is a cost attached to the tile, let's register it 69 | } 70 | Game.finder.setAcceptableTiles(acceptableTiles); 71 | }; 72 | 73 | Game.update = function(){ 74 | var worldPoint = this.input.activePointer.positionToCamera(this.cameras.main); 75 | 76 | // Rounds down to nearest tile 77 | var pointerTileX = Game.map.worldToTileX(worldPoint.x); 78 | var pointerTileY = Game.map.worldToTileY(worldPoint.y); 79 | Game.marker.x = Game.map.tileToWorldX(pointerTileX); 80 | Game.marker.y = Game.map.tileToWorldY(pointerTileY); 81 | Game.marker.setVisible(!Game.checkCollision(pointerTileX,pointerTileY)); 82 | }; 83 | 84 | Game.checkCollision = function(x,y){ 85 | var tile = Game.map.getTileAt(x, y); 86 | return tile.properties.collide == true; 87 | }; 88 | 89 | Game.getTileID = function(x,y){ 90 | var tile = Game.map.getTileAt(x, y); 91 | return tile.index; 92 | }; 93 | 94 | Game.handleClick = function(pointer){ 95 | var x = Game.camera.scrollX + pointer.x; 96 | var y = Game.camera.scrollY + pointer.y; 97 | var toX = Math.floor(x/32); 98 | var toY = Math.floor(y/32); 99 | var fromX = Math.floor(Game.player.x/32); 100 | var fromY = Math.floor(Game.player.y/32); 101 | console.log('going from ('+fromX+','+fromY+') to ('+toX+','+toY+')'); 102 | 103 | Game.finder.findPath(fromX, fromY, toX, toY, function( path ) { 104 | if (path === null) { 105 | console.warn("Path was not found."); 106 | } else { 107 | console.log(path); 108 | Game.moveCharacter(path); 109 | } 110 | }); 111 | Game.finder.calculate(); // don't forget, otherwise nothing happens 112 | }; 113 | 114 | Game.moveCharacter = function(path){ 115 | // Sets up a list of tweens, one for each tile to walk, that will be chained by the timeline 116 | var tweens = []; 117 | for(var i = 0; i < path.length-1; i++){ 118 | var ex = path[i+1].x; 119 | var ey = path[i+1].y; 120 | tweens.push({ 121 | targets: Game.player, 122 | x: {value: ex*Game.map.tileWidth, duration: 200}, 123 | y: {value: ey*Game.map.tileHeight, duration: 200} 124 | }); 125 | } 126 | 127 | Game.scene.tweens.timeline({ 128 | tweens: tweens 129 | }); 130 | }; -------------------------------------------------------------------------------- /js/main.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Jerome Renaux (jerome.renaux@gmail.com) on 25-02-18. 3 | */ 4 | var config = { 5 | type: Phaser.AUTO, 6 | width: 20*32, 7 | height: 20*32, 8 | parent: 'game', 9 | scene: [Game] 10 | }; 11 | 12 | var game = new Phaser.Game(config); 13 | --------------------------------------------------------------------------------