jdiperla

Need help to fix: Making an adventure game pathfinding room using easystar and phaser

Recommended Posts

Good afternoon,

I was hoping that the community could look over my code and see if something needs to be adjusted. I recently started learning Javascript and I just started learning Phaser and easystar yesterday so this might all look ridiculous. At the moment all I am trying to accomplish is pathfinding the way it is done in a point and click adventure game such as Monkey Island or Day of the Tentacle from Lucasarts. I am trying to achieve it much how AGS(Adventure Game Studio) allows the user to set it up. Basically, you import a room image and then you draw over the room with a chosen color(Sort of like drawing a mask) all the area's that a character can walk and find a path to and from. 

What I am trying to achieve in my code is the same principle above and I am trying to use easystar and Phaser to accomplish this. Let me explain my code:

I have three images: the 'background' image which will load up the image of the room the character will be in. 'walkablepath' will be the image that contains the image mask. Anything that is hot pink is where the character can walk. 'maincharacter' is the player character that will find the path when we click on a part of the screen we want the player to go to.

At start we will create a 'bmd' object, create the walkable grid and then destroy the bmd object. The 'bmd' object is what will hold the walkable mask information. It will match the same size as the room image. It will have complete transparency and will be overlaid over the background image, but not visible to the user. 'walkableGrid' will be the grid data that easystar will use to calculate the walkable paths. 'walkableRGB' will contain the RGB value of Hot Pink so that we can find the hot pink pixels. 'gridCollection' will collect the X and Y pixel data in the 'bmd' object and push it to the 'walkableGrid' as it goes through each pixel line from top to bottom. The code will do this by iterating through each X and Y pixel in a loop. After that is completed, the mask will be destroyed, easystar will have a setup to determine the acceptable tiles in the grid. 

Function calculateWalkPath() will be called each time the user clicks on the screen and the game will try and calculate the path for the user to walk and move him to his destination. Please see the code below:

//Set the initial game paramters - Will start with 800x600 resolution and will use WebGL as a renderer and default back to Canvas if WebGL is not present.
var game = new Phaser.Game(800,600, Phaser.AUTO, '', { preload: preload, create: create, update: update});
var easystar = new EasyStar.js(); //Lets get easy star in here. 
var bmd; //This will be the object that will take the pixel data of the scene.

//Assets that will be preloaded at start
function preload(){
	game.load.image('background', 'assets/room1.png'); //The game room background that will be loaded.
	game.load.image('walkablepath', 'assets/walkablepath.png'); //The room's walkable area.
	game.load.image('maincharacter', 'assets/character.png', 32, 48); //The main characters animated spritesheet who will be walking around the room.
	
	
}

//The first function called when we start our game
function create(){
	//We are going to obtain the width and height of the background room.
	var backWidth = game.cache.getImage("background").width;var backHeight = game.cache.getImage("background").height;
	bmd = game.make.bitmapData(backWidth, backHeight); //Getting ready to determine the room size and get the pixel data of the walkable path.
	
	game.add.sprite(0,0,'background'); // Will add the room background to the desktop. It will place the upper left part of the image to the upper left part of the screen.
	bmd.alpha = 0; //Let's make sure the image is completely invisible to the users eyes.
	bmd.draw('walkablepath', 0, 0); //Will overlay the transparent walkable path over the background image. 
	
	var walkableGrid = new Array(); //Lets make the grid that easy star will define as the walkable points. 
	var gridCollection;	 //This will collect the 2 dimensional array grids and push it to the walkableGrid.
	var walkableRGB = "255105180"; //This is the RGB value of the area's the user can walk on. - Hot Pink is the RGB Color
	
	//Following code will begin at the top left corner of the walkable area and check each pixel for the hot pink color. If it finds it, it will add a 0. If not, 1. 
	for (i = 0; i < backWidth; i++) { 
		
		gridCollection = "[";
		
		for (j = 0; j < backWidth; j++) {
			
		if (bmd.getPixelRGB(i, j) == "255105180"){
			
			gridCollection = gridCollection + "0";
			
		} else {
			
			gridCollection = gridCollection + "1";
			
		}
		
		//If there is still more width in the image, it will add a comma. Otherwise it won't and the array can be closed off.
		if (j != backWidth) {
			gridCollection = gridCollection + ",";
		}
		
			
	}
	//Close up and then Push the Array to the walkable grid
	    gridCollection = gridCollection + "]";
		walkableGrid.push(gridCollection);
		
		
      }
	bmd.kill(); //let's destroy the walkable area path we created from view - we need to find a better way to do this process.
	easystar.setGrid(walkableGrid);  //Let's add the 2 dimensional grid array we just created to Easy star's pathfinding grid.
	easystar.setAcceptableTiles([0]); //Let's also make sure that easy star is aware that the 0 tiles are the tiles that the player can walk on.
	
	
	
	
}

function update(){
	
}

function calculateWalkPath() {  //This function will be called every time the user clicks on a path to walk to. 
	
	//Now let's calculate the path and presumably use tweening to move the character from it's current x and y position to it's next calculated position
	easystar.findPath(xClickDest, yClickDest, playerXpos, playerYpos, function( path ) {
		
	if (path === null) {
		//Do something like a shrug animation from the character for not being able to find a path.
	} else {
		game.add.tween(maincharacter).to( { x: path[0].x }, 2000, Phaser.Easing.Linear.None, true);
		game.add.tween(maincharacter).to( { y: path[0].y }, 2000, Phaser.Easing.Linear.None, true);
	}
});

//Let's get this party started.
easystar.setIterationsPerCalculation(1000);
easystar.calculate();

}

I have to admit, I did not test this code yet. I rather have a fresh pair of eyes on this as I spent a good half hour trying to figure this out today and feel rather brain dead. Now, my questions are these: Will this code operate correctly? Did I use Phaser and Easystar correctly? What about memory management and speed and what is a better way to manage this? How would you improve it? Also, can I set more than one acceptable tile for easystar and how?

Thanks for looking and for your assistance.

Share this post


Link to post
Share on other sites

I think setGrid's argument must be an array of arrays:

var grid = [ [0,0,1,0,0],
             [0,0,1,0,0],
             [0,0,1,0,0],
             [0,0,1,0,0],
             [0,0,0,0,0] ];

easystar.setGrid(grid);

Make sure you declare your iterator variables. I think you want to compare j to backHeight, not backWidth. getPixelRGB returns an object so you have to compare its rgba property (a number).

for (var i = 0; i < backWidth; i++) {
  for (var j = 0; j < backHeight; j++) {
    if (bmd.getPixelRGB(i, j).rgba === 255105180) {
      // …
    }
  }
}

When I have scripts at this stage I often find it helpful to step through execution line by line and examine the values, revise, and repeat. 

Share this post


Link to post
Share on other sites

A BitmapData itself isn't a display object. It needs an image or a sprite to display it.

bmd = game.make.bitmapData(backWidth, backHeight);
bmd.draw('walkablepath', 0, 0);

var bmdImage = bmd.addToWorld();
bmdImage.alpha = 0.5;

 

Share this post


Link to post
Share on other sites

Actually I literally just figured it out. I was able to do bmd.load('walkablepath') and it loaded it into background and I was able to parse the data appropriately... I believe anyway. Now I just have to test it with EasyStar. My only other current issue is that after it get's all the row data in the X grid, it should cut off the current dimensional array without the comma at the end of it. Instead, right now it is still adding it, which results in it looking like this: [0,1,1,1,1,1,1,1,1,1,1,] instead of like this [0,1,1,1,1,1,1,1,1,1] <- notice no comma after the last 1. That is what I want to achieve, but it is not working as intended. This is my revised code if you have any suggestions:

 

//Set the initial game paramters - Will start with 800x600 resolution and will use WebGL as a renderer and default back to Canvas if WebGL is not present.
var game = new Phaser.Game(800,600, Phaser.AUTO, '', { preload: preload, create: create, update: update});
var easystar = new EasyStar.js(); //Lets get easy star in here. 
var bmd; //This will be the object that will take the pixel data of the scene.

//Assets that will be preloaded at start
function preload(){
	game.load.image('background', 'assets/room1.png'); //The game room background that will be loaded.
	game.load.image('walkablepath', 'assets/walkablepath.png'); //The room's walkable area.
	game.load.image('maincharacter', 'assets/character.png', 32, 48); //The main characters animated spritesheet who will be walking around the room.
	
	
}

//The first function called when we start our game
function create(){
	//We are going to obtain the width and height of the background room.
	var backWidth = game.cache.getImage("background").width;var backHeight = game.cache.getImage("background").height;
	bmd = game.make.bitmapData(backWidth, backHeight); //Getting ready to determine the room size and get the pixel data of the walkable path.
	bmd.load('walkablepath'); //This will load the walkable path into memory. 
	game.add.sprite(0,0,'background'); // Will add the room background to the desktop. It will place the upper left part of the image to the upper left part of the screen.
	
	var walkableGrid = new Array(); //Lets make the grid that easy star will define as the walkable points. 
	var gridCollection;	 //This will collect the 2 dimensional array grids and push it to the walkableGrid.
	var walkableRGB = "rgba(255,105,180,1)"; //This is the RGB value of the area's the user can walk on. - Hot Pink is the RGB Color
	var color; //Will contain the pixel color of where the walkablepath search index is on.
	
	//Following code will begin at the top left corner of the walkable area and check each pixel for the hot pink color. If it finds it, it will add a 0. If not, 1. 
	for (i = 0; i < backWidth; i++) { 
		
		gridCollection = "[";
		
		for (j = 0; j < backHeight; j++) {
		color = bmd.getPixelRGB(i, j); //Store the color date of X and Y pixel
			
						/*if (color.rgba == "rgba(255,105,180,1)") {
			console.log("X: " + j + " Y: " + i + " = " + color.rgba);
		}*/
		
		
		if (color.rgba == walkableRGB){
			
			gridCollection = gridCollection + "0";
			
		} 
		
		if (color.rgba != walkableRGB) {
			
			gridCollection = gridCollection + "1";
			
		}
		
		//If there is still more width in the image, it will add a comma. Otherwise it won't and the array can be closed off.
		if (i != backWidth) {
			gridCollection = gridCollection + ",";
			
		}
		
			
	}
	//Close up and then Push the Array to the walkable grid
	    gridCollection = gridCollection + "]";
		walkableGrid.push(gridCollection);
	
		
		
      }
      
	bmd.destroy(); //let's destroy the walkable area path we created from view - we need to find a better way to do this process.
	easystar.setGrid(walkableGrid);  //Let's add the 2 dimensional grid array we just created to Easy star's pathfinding grid.
	easystar.setAcceptableTiles([0]); //Let's also make sure that easy star is aware that the 0 tiles are the tiles that the player can walk on.
	
	
	
	
}

function update(){
	
}

function calculateWalkPath() {  //This function will be called every time the user clicks on a path to walk to. 
	
	//Now let's calculate the path and presumably use tweening to move the character from it's current x and y position to it's next calculated position
	easystar.findPath(xClickDest, yClickDest, playerXpos, playerYpos, function( path ) {
		
	if (path === null) {
		//Do something like a shrug animation from the character for not being able to find a path.
	} else {
		game.add.tween(maincharacter).to( { x: path[0].x }, 2000, Phaser.Easing.Linear.None, true);
		game.add.tween(maincharacter).to( { y: path[0].y }, 2000, Phaser.Easing.Linear.None, true);
	}
});

//Let's get this party started.
easystar.setIterationsPerCalculation(1000);
easystar.calculate();

}

Thank you!

Share this post


Link to post
Share on other sites

Well I figured almost everything out correctly. Seems either I am not creating the array correctly or easy star is not being used correctly to determine the path. Here is my updated code:

 

//Set the initial game paramters - Will start with 800x600 resolution and will use WebGL as a renderer and default back to Canvas if WebGL is not present.
var game = new Phaser.Game(800,600, Phaser.AUTO, '', { preload: preload, create: create, update: update});
var easystar = new EasyStar.js(); //Lets get easy star in here. 
var bmd; //This will be the object that will take the pixel data of the scene.

//Assets that will be preloaded at start
function preload(){
	game.load.image('background', 'assets/room1.png'); //The game room background that will be loaded.
	game.load.image('walkablepath', 'assets/walkablepath.png'); //The room's walkable area.
	game.load.image('maincharacter', 'assets/character.png', 32, 48); //The main characters animated spritesheet who will be walking around the room.
	
	
}

//The first function called when we start our game
function create(){
	//We are going to obtain the width and height of the background room.
	var backWidth = game.cache.getImage("background").width;var backHeight = game.cache.getImage("background").height;
	bmd = game.make.bitmapData(backWidth, backHeight); //Getting ready to determine the room size and get the pixel data of the walkable path.
	bmd.load('walkablepath'); //This will load the walkable path into memory. 
	game.add.sprite(0,0,'background'); // Will add the room background to the desktop. It will place the upper left part of the image to the upper left part of the screen.
	mainchar = game.add.sprite(200,516,'maincharacter'); // Will add the room background to the desktop. It will place the upper left part of the image to the upper left part of the screen.
	
	var walkableGrid = new Array(); //Lets make the grid that easy star will define as the walkable points. 
	var gridCollection;	 //This will collect the 2 dimensional array grids and push it to the walkableGrid.
	var walkableRGB = "rgba(255,105,180,1)"; //This is the RGB value of the area's the user can walk on. - Hot Pink is the RGB Color
	var color; //Will contain the pixel color of where the walkablepath search index is on.
	
	//Following code will begin at the top left corner of the walkable area and check each pixel for the hot pink color. If it finds it, it will add a 0. If not, 1. 
	for (i = 0; i < backWidth; i++) { 
		
		gridCollection = "[";
		
		for (j = 0; j < backHeight; j++) {
		color = bmd.getPixelRGB(i, j); //Store the color date of X and Y pixel
			
							
		
		if (color.rgba == walkableRGB){
			
			gridCollection = gridCollection + "0";
			
		} 
		
		if (color.rgba != walkableRGB) {
			
			gridCollection = gridCollection + "1";
			
		}
		
		//If there is still more width in the image, it will add a comma. Otherwise it won't and the array can be closed off.
		if (i != backWidth) {
			gridCollection = gridCollection + ",";
			
		}
		
			
	}
	//Close up and then Push the Array to the walkable grid
	    gridCollection = gridCollection + "]";
		walkableGrid.push(gridCollection);
	
		
		
      }
      
	bmd.destroy(); //let's destroy the walkable area path we created from view - we need to find a better way to do this process.
	easystar.setGrid(walkableGrid);  //Let's add the 2 dimensional grid array we just created to Easy star's pathfinding grid.
	easystar.setAcceptableTiles([0]); //Let's also make sure that easy star is aware that the 0 tiles are the tiles that the player can walk on.
	game.input.onDown.add(calculateWalkPath, this);
	
	
	
}

function update(){
	
}

function calculateWalkPath() {  //This function will be called every time the user clicks on a path to walk to. 
	console.log(game.input.x + ", " + game.input.y + "/");
	console.log(mainchar.x + ", " + mainchar.y + "/");
	//Now let's calculate the path and presumably use tweening to move the character from it's current x and y position to it's next calculated position
	easystar.findPath(game.input.x, game.input.x, mainchar.x, mainchar.y, function( path ) {
		
	if (path === null) {
		//Do something like a shrug animation from the character for not being able to find a path.
	} else {
		
		mainchar.x = path[0].x;
		mainchar.y = path[0].y;
		console.log(path[0].x);
		console.log(path[0].y);
		
	}
});

//Let's get this party started.
easystar.setIterationsPerCalculation(1000);
easystar.calculate();

}

You can look at an example I am trying to run here: http://sw-bfs.com/examples/udemy/

Share this post


Link to post
Share on other sites

You can make walkableGrid like this:

var walkableGrid = [];

for (var i = 0; i < backWidth; i++) {
  var row = walkableGrid[i] = [];

  for (var j = 0; j < backHeight; j++) {
    row[j] = (bmd.getPixelRGB(i, j).rgba === walkableRGB) ? 0 : 1;
  }
}

easystar.setGrid(walkableGrid);

 

Share this post


Link to post
Share on other sites

I am just re-bumping this as I can not seem to find a solution to those and I Would really like to find one. Here is my demo: http://www.sw-bfs.com/examples/udemy/  You can use the Java console to see what is happening. 
This is the room background: http://www.sw-bfs.com/examples/udemy/assets/room1.png
The walkable path I am using as a path: http://www.sw-bfs.com/examples/udemy/assets/walkablepath.png
The character image: http://www.sw-bfs.com/examples/udemy/assets/character.png

 

My last post in this thread is the actual code I am still using. Can't figure this out for the life of me. Any help? Consider the walkable path image as the image file that considers the Hot Pink colored area the navigation Mesh. I suspect the error is where I move my character perhaps or in translating the hot pink area's into a walkable grid with Easy Star. Please help!

Share this post


Link to post
Share on other sites

Two things to consider: 
1.

easystar.findPath(game.input.x, game.input.y, mainchar.x, mainchar.y, function( path ) {//game.input.y instead of x


2. 

Reverse the array dimension , for example your image is 200*400,  but you pass an 400*200 array to easystar. So reverse backWidth and backHeight in the for loop.

Im not sure how easyStar implements this so can't help you with that. 

Share this post


Link to post
Share on other sites

Hi Samid737, 

I did both things. Still not working. I messed around some more with the console to see where the error may lie. The best I Can tell is that Path is returning Null. So easy star is definitely not finding the path. So either I am not doing something right with the grid, or I am not calculating the path correctly with Easy Star for some reason. Might be the grid is too large maybe? Either way, it is a little unnerving. Any other idea's guys?

Share this post


Link to post
Share on other sites

I faced a very similar problem. I just solved it in my case. I'm posting this for any future researchers that run into this problem.

easystar will most likely return null on a path because it hasn't calculated that path. Most likely, the problem is your easystar.calculate() method is unreachable. 
The basic is like this:
in your pixi setup() you should define all functions and events related to easystar pathfinding. Then, you call your gameloop() function from the setup() function such that it becomes a closure.

The important thing is that you easystar.calculate() is running in a ticker or setInterval. Otherwise, I'm pretty sure your arrays are correct. Easystar just isn't calculating on them thus returning null.

/*
	other pixi application, container setup code
*/


let easystar = new easystarjs.js();
let grid = Array(50).fill().map(() => Array(50).fill(0));

setup() {

	app.ticker.add(delta => gameLoop(delta));
} 


function gameLoop(delta) { // 60fps

	easystar.calculate();
	easystar.enableDiagonals();
}
	

function getPath() {
	initPath();
	let path = findPath();
}
	
function initPath() {
	easystar.setGrid(grid); 
	easystar.setAcceptableTiles([0]);
}

function findPath (startX, startY, endX, endY) {
	return new Promise ((resolve, reject) =>{
		console.log('path find init:',startX, startY, endX, endY);
		easystar.findPath(startX, startY, endX, endY, function( path ) {
			if (path === null) {
				console.log("Path was not found.");
				reject(path);
			} else {
				console.log("Path was found. The first Point is " + path[0].x + " " + path[0].y);
				resolve(path);
			}
		});
	});
}

 

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

  • Recently Browsing   0 members

    No registered users viewing this page.