Tile Costs for Pathfinding
This example shows how tile costs can be considered for pathfinding. The liana has a tile cost of 4 when climbing up (the tiles have the property ge_cost_down = 4
). Characters have been set up to slow down by the factor of the tile costs. Try to climb up the liana to observe this.
You can turn considerCosts
([1], [2], [3]) on and off for the pathfinding of the NPC and observe how it will take the higher tile cost of the liana into account.
NOTE:
considerCosts
does only work with A*
Code
📖 How to execute examples locally
js
const game = new Phaser.Game(config(preload, create, update));
let considerCosts = false;
let gridEngine;
function preload() {
this.load.image("tiles", "../../assets/tf_jungle_tileset.png");
this.load.tilemapTiledJSON(
"jungle",
"../../assets/tile-costs.json"
);
this.load.spritesheet("player", "../../assets/characters.png", {
frameWidth: 52,
frameHeight: 72,
});
}
function create() {
const cloudCityTilemap = this.make.tilemap({ key: "jungle" });
cloudCityTilemap.addTilesetImage("jungle", "tiles");
for (let i = 0; i < cloudCityTilemap.layers.length; i++) {
const layer = cloudCityTilemap.createLayer(i, "jungle", 0, 0);
layer.scale = 3;
}
const playerSprite = this.add.sprite(0, 0, "player");
playerSprite.scale = 1.5;
const npcSprite = this.add.sprite(0, 0, "player");
npcSprite.scale = 1.5;
this.cameras.main.startFollow(playerSprite, true);
this.cameras.main.setFollowOffset(
-playerSprite.width,
-playerSprite.height
);
const gridEngineConfig = {
characters: [
{
id: "player",
sprite: playerSprite,
walkingAnimationMapping: 6,
startPosition: { x: 18, y: 14 },
},
{
id: "npc",
sprite: npcSprite,
walkingAnimationMapping: 2,
startPosition: { x: 15, y: 15 },
},
],
};
this.gridEngine.create(cloudCityTilemap, gridEngineConfig);
this.gridEngine.positionChangeStarted().subscribe(({ charId, enterTile, enterLayer, exitTile }) => {
const dir = GridEngineImports.directionFromPos(enterTile, exitTile);
const cost = this.gridEngine.getTileCost(enterTile, enterLayer, dir);
this.gridEngine.setSpeed(charId, 3 / cost);
});
gridEngine = this.gridEngine;
const triangleArgs = [0, 0, 50, 0, 24, 50, 0xffffff];
const arrow = this.add.triangle(
16 * 48 + 24,
9 * 48 + 24,
...triangleArgs
);
this.tweens.add({
targets: [arrow],
alpha: 0.2,
yoyo: true,
repeat: -1,
ease: "Sine.easeInOut",
});
settingsToConf();
}
function update() {
const cursors = this.input.keyboard.createCursorKeys();
if (cursors.left.isDown) {
this.gridEngine.move("player", "left");
} else if (cursors.right.isDown) {
this.gridEngine.move("player", "right");
} else if (cursors.up.isDown) {
this.gridEngine.move("player", "up");
} else if (cursors.down.isDown) {
this.gridEngine.move("player", "down");
}
}
function startPathfinding() {
considerCosts = !!document.getElementById("consider-costs").checked;
settingsToConf();
gridEngine.setPosition("npc", { x: 15, y: 15 });
gridEngine.moveTo("npc", { x: 16, y: 5 }, {
algorithm: "A_STAR",
considerCosts
});
}
function settingsToConf() {
let text = '';
text = JSON.stringify({
algorithm: "A_STAR",
considerCosts,
}, null, 2);
document.getElementById(
"json-config"
).innerHTML = `<pre>${text}</pre>`;
}
html
<!doctype html>
<html>
<head>
<link rel="stylesheet" href="../styles.css" />
<script src="../../js/phaser-3.80.1.min.js"></script>
<script src="../../js/grid-engine-2.44.3.min.js"></script>
<script src="../../js/examplePhaserConfig.js"></script>
<script src="code.js"></script>
<meta name="color-scheme" content="dark light" />
</head>
<body>
<div class="settings">
<h2>Settings:</h2>
<div class="wait-timeout-group">
<input
type="checkbox"
id="consider-costs"
name="consider-costs"
value="true"
/>
<label for="consider-costs">considerCosts</label>
</div>
<button class="apply" onclick="startPathfinding()">
Start pathfinding
</button>
<h3>MoveToConfig:</h3>
<div id="json-config"></div>
</div>
<div id="game" scrolling="no"></div>
</body>
</html>
js
function config(preload, create, update) {
return {
title: "GridEngineExample",
render: {
antialias: false,
},
type: Phaser.AUTO,
plugins: {
scene: [
{
key: "gridEngine",
plugin: GridEngine,
mapping: "gridEngine",
},
],
},
scale: {
width: 700,
height: 528,
},
scene: {
preload: preload,
create: create,
update: update,
},
parent: "game",
backgroundColor: "#48C4F8",
input: {
mouse: {
preventDefaultWheel: false
},
touch: {
capture: false
}
}
};
}
css
body {
margin: 0;
padding: 0;
background: #1e1e20;
font-family: Inter,-apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;
color: #cacaca;
}
.settings {
margin-top: 20px;
display: flex;
flex-direction: column;
/* border: 1px solid white; */
background: #282828;
margin-bottom: 20px;
width: 640px;
padding: 0 30px;
}
label {
font-weight: bold;
margin-top: 30px;
margin-bottom: 10px;
}
select {
width: 100px;
}
input {
width: 100px;
}
button {
width: 120px;
margin-top: 30px;
margin-bottom: 20px;
}
.wait-timeout-group {
display: flex;
align-items: center;
justify-content: flex-start;
}
.wait-timeout-group input {
width: auto;
margin-right: 10px;
}
.wait-timeout-group label {
margin:0;
}
#json-config {
margin-bottom: 10px;
}
#json-config pre {
font-family: "Consolas", monospace;
}
.disabled {
display: none;
}