I spent some time working on server side pathfinding with Phaser 3. I’m not an expert, but this is how I did it. I wanted to share how I did it because I spent a long time on it and want to save others some time and energy. Hopefully it helps you with your project. I would love to hear any suggestions on how to improve the code. E-mail me at matt@drennansoftware.com or comment below!
In Dead Haven, I wanted the zombies in the game to detect nearby players and then start moving towards the players location. I needed this all to be server side.
Firstly, I found a pathfinding library which works with node.js. You can find it here. You will need to download and set it up on your node.js server.
Next I had to add some code to my server JavaScript file. Here is the pathfinding code for the server side:
function ServerGameLoop()
{
// Zombies
for(var i = 0; i < zombie_array.length; i++)
{
// Fixes a bug where it would cause game to crash and repeat inside the dist if check
if(zombie_array[i] != undefined)
{
var zombie = zombie_array[i];
var followPlayerX = 0;
var followPlayerY = 0;
var getPlayerX = 0;
var getPlayerY = 0;
var playerDetected = false;
var myMap = zombie_array[i].myMap;
var myMapCoords = zombie_array[i].myMapCoords;
// Check if this bullet is close enough to hit any player
for(var playerId in players)
{
// And your own bullet shouldn't kill you
var dx = players[playerId].x - zombie_array[i].x;
var dy = players[playerId].y - zombie_array[i].y;
var dist = Math.sqrt(dx * dx + dy * dy);
// Determine distance
if(dist < 160000 && zombie_array[i].path.length == 0)
{
followPlayerX = players[playerId].x;
followPlayerY = players[playerId].y;
var zombieCoords = [(zombie_array[i].x - 320), (zombie_array[i].x + 320), (zombie_array[i].y - 320), (zombie_array[i].y + 320)];
getPlayerX = Math.floor((followPlayerX - zombieCoords[0])/32);
getPlayerY = Math.floor((followPlayerY - zombieCoords[2])/32);
if(getPlayerX < 0 || getPlayerX > 20 || getPlayerY < 0 || getPlayerY > 20)
{
zombie_array[i].isFollowingPlayer = false;
}
else
{
zombie_array[i].isFollowingPlayer = players[playerId];
playerDetected = true;
}
}
}
// Is following a player, lets do something about that
if(zombie_array[i].isFollowingPlayer != false)
{
// No path defined, so lets figure this out
if(zombie_array[i].path.length == 0)
{
// For looping though tiles around zombie
var loopC = 0;
// Clear variables
zombie_array[i].myMap = [];
zombie_array[i].myMapCoords = [];
// Start loop through tiles around zombie and put them in an array
for(y = -10; y < 10; y++)
{
zombie_array[i].myMap[loopC] = new Array();
for(c = Math.floor((zombie_array[i].x/32) * 32) - 320; c < Math.floor((zombie_array[i].x/32) * 32) + 320; c += 32)
{
var yOffset = Math.floor(zombie_array[i].y/32) + (y);
var getValue = (yOffset * 100) + ((Math.floor(c/32)));
if(map[getValue] == undefined)
{
zombie_array[i].myMap[loopC].push(1);
}
else
{
zombie_array[i].myMap[loopC].push(map[getValue]);
}
// Fixes a bug by doing it this way - calculate the y value
var calcBefore = parseInt(zombie_array[i].y) + (y * 32);
zombie_array[i].myMapCoords.push(c + "," + calcBefore);
}
loopC++;
}
// Let's find a path for the zombie
var grid = new PF.Grid(20, 20, zombie_array[i].myMap);
var finder = new PF.AStarFinder({
allowDiagonal: true
});
// Just in case there is an exception
try
{
if(playerDetected)
{
zombie_array[i].path = finder.findPath(10, 10, parseInt(getPlayerX), parseInt(getPlayerY), grid);
}
}
catch(error)
{
// This failed, put some code here later.
}
}
if(zombie_array[i].path.length > 0)
{
// Zombie path NPM cords
var zArray = zombie_array[i].path[zombie_array[i].pathI];
try
{
// Calculate which tile to travel to based on zombie path position
var calcBeforeCoords = (zArray[0]) + ((zArray[1]) * 20);
// Translate
var getCoords = myMapCoords[calcBeforeCoords].split(',');
var gX = parseInt(getCoords[0]);
var gY = parseInt(getCoords[1]);
// Prevent zombie from going out of bounds
if(getCoords[0] < 0)
{
gX = 32;
}
if(getCoords[1] < 0)
{
gY = 32;
}
// End of Prevent zombie from going out of bounds
// Let's check and see if the zombie moved
var moved = 0;
// Move zombie
if(zombie_array[i].x < gX)
{
zombie_array[i].x++;
moved++;
}
if(zombie_array[i].x > gX)
{
zombie_array[i].x--;
moved++;
}
if(zombie_array[i].y > gY)
{
zombie_array[i].y--;
moved++;
}
if(zombie_array[i].y < gY)
{
zombie_array[i].y++;
moved++;
}
// Did not move, let's go to the next index
if(moved == 0)
{
zombie_array[i].pathI++;
}
if(zombie_array[i].pathI >= zombie_array[i].path.length)
{
zombie_array[i].path = [];
zombie_array[i].isFollowingPlayer = false;
zombie_array[i].pathI = 0;
}
}
catch(error)
{
// Found player
//zombie_array[i].path = [];
//zombie_array[i].isFollowingPlayer = false;
//zombie_array[i].pathI = 0;
}
}
}
}
}
// Tell everyone where all the zombies are by sending the whole array
io.emit("zombie", zombie_array);
}
The code above detects if a player is nearby. If a player is nearby and the zombie is not following a player, it gets the player’s coordinate on the pathfinding grid system. It also calculates the coordinates of all the tiles on the pathfinding grid system (myMap – an array of all the tiles in the grid system) to the Phaser 3 grid system, and put it into the array myMapCoords. The pathfinding library then gets a path to the player, but the pathfinding library gets a path based on the pathfinding library grid system. We then convert the pathfinding library path array to the Phaser 3 coordinates. The server then send data to the zombie to move towards those Phaser 3 coordinates.
Next here is the code in my client side JavaScript file which utilizes Phaser 3:
this.socket.on('zombie', function(server_zombie_array)
{
for(var i = 0; i < server_zombie_array.length; i++)
{
if(zombie_array[i] == undefined)
{
zombie_array[i] = self.add.sprite(server_zombie_array[i].x, server_zombie_array[i].y, 'zombie').setOrigin(0, 0);
zombie_array[i].spriteid = server_zombie_array[i].spriteid;
}
else
{
// Otherwise, just update it!
zombie_array[i].x = server_zombie_array[i].x;
zombie_array[i].y = server_zombie_array[i].y;
}
}
});
The code above creates a sprite if one does not exist or updates the sprites location if it exists.
I did this in a rush, and I know I didn’t post a ton of info, but I will update this later. Please feel free to send me questions.