Search code examples
javascriptphaser-frameworkpath-finding

Best solution for Phaser 3 pathfinding?


I have a game and I want to be able to have enemies pathfind to the player across a tilemap, is there a way to do this without programming my own a* algorithm? I've seen a couple supposedly still updated libraries for this, but neither of which have worked, they have both been broken with npm and not worked when added as a script.
The libraries I've tried are Easystar and Navmesh.
Any suggestions would be great, and while Phaser support would be useful, pathfinding over a 2D array would still be a good solution for me. Thanks!


Solution

  • I never tried it myself (until now), but https://github.com/mikewesthad/navmesh is a great plugin and is very extensive.

    ... A JS plugin for fast pathfinding using navigation meshes, with optional wrappers for the Phaser v2 and Phaser v3 game engines. ...

    p.s.: on this page you can find a list of available plugins for phaser https://phaserplugins.com/

    Update:

    After testing in on an simple an example it worked fine, just be careful not to miss any steps.

    Here the steps needed:

    1. get the latest plugin file phaser-navmesh-plugin.js (currently from https://github.com/mikewesthad/navmesh/releases/tag/2.1.0)

    2. load it from the html file <script src="phaser-navmesh-plugin.js"></script>

    3. configure the plugin, in the config (there are other ways but this is the more convinient)

       var config = {
           ...
           plugins: {
               scene: [
                   {
                       key: "PhaserNavMeshPlugin", // Key to store the plugin class under in cache
                       plugin: PhaserNavMeshPlugin, // Class that constructs plugins
                       mapping: "navMeshPlugin", // Property mapping to use for the scene, e.g. this.navMeshPlugin
                       start: true
                   }
               ]
           },
           scene: {
              ...
           }
       };
      
    4. setup the map and the layer which should collide:

           var map = this.make.tilemap({ data: [[0,1,0],[0,1,0],[0,1,0],[0,0,0]], tileWidth: 8, tileHeight: 8 });
           var tiles = map.addTilesetImage('tile01');
           var layer = map.createLayer(0, tiles, 0, 0);
      
           // this is important, can also be multiple indices
           layer.setCollision(1);
      
    5. create navMesh, pass the map and the created layer with should have to collisions.

           const navMesh = this.navMeshPlugin.buildMeshFromTilemap("mesh", map, [layer]);
      
    6. when needed execute pathfinding (x/y positions are in pixels, not tilesId):

           const path = navMesh.findPath({ x: 4, y: 4 }, { x: 17, y: 4 });
      

    You can load the plugin in also in the preload function like this

            this.load.scenePlugin({
                key: 'PhaserNavMeshPlugin',
                url: PhaserNavMeshPlugin,
                sceneKey: 'navMeshPlugin'
            });
    

    Here the demo code with a mini path trace when done:

        var config = {
            scene: {
                preload,
                create,
            }
        };
    
        var game = new Phaser.Game(config);
    
        function preload() {
            this.load.image('tile01', 'tile01.png');
            this.load.scenePlugin({
                key: 'PhaserNavMeshPlugin',
                url: PhaserNavMeshPlugin,
                sceneKey: 'navMeshPlugin'
            });
        }
    
        function create() {
            var map = this.make.tilemap({ data: [[0,1,0],[0,1,0],[0,1,0],[0,0,0]], tileWidth: 8, tileHeight: 8 });
            var tiles = map.addTilesetImage('tile01');
            var layer = map.createLayer(0, tiles, 0, 0);
    
            layer.setCollision(1);
    
            const navMesh = this.navMeshPlugin.buildMeshFromTilemap("mesh", map, [layer]);
            const path = navMesh.findPath({ x: 4, y: 4 }, { x: 17, y: 4 });
    
            if (!path) {
                return;
            }
    
            let graphics = this.add.graphics();
    
            graphics.lineStyle(2, 0xff0000, 1);
            graphics.beginPath();
            graphics.moveTo(path[0].x, path[0].y);
    
            for (let idx = 1; idx < path.length; idx++) {
                graphics.lineTo(path[idx].x, path[idx].y);
            }
    
            graphics.strokePath();
        }