So I am working on a small hobby project using Phaser 3 and have run into some issues working with Tile map layers.
Right now I am creating a tilemap layer and using multiple spritesheets I'm loading in. I used to have several layers I used to handle this and that worked great, however I was looking to consolidate this down to one layer since it appears that there is functionality for using multiple tilesets in one layer (see the tileset options for an array of tilesets). I've run into the issue that after setting it up and trying to place tiles from the specified tileset It is only ever pulling the sprites from the first one at the index of whatever tileset I'm trying to pull from. so if I dump all the tiles in a grid I get this:
You see the first tilesheet is being repeated even though the lengths of each are correct. So I get all 350 tiles from the first sheet, then 318 or so for the second, then 60, then 30 but all of them start pulling the original spritesheet's images.
Here's the relevant code:
initTileSets() {
// Initialize tilemap
this.bgTilemap = this.scene.make.tilemap({
width: this.mapWidth,
height: this.mapHeight,
tileWidth: this.tileSize,
tileHeight: this.tileSize
});
// Add all tilesets to the tilemap
this.terrainTiles = this.bgTilemap.addTilesetImage('terrainTileset');
this.terrainTiles2 = this.bgTilemap.addTilesetImage('terrainTileset2');
this.rogueTiles = this.bgTilemap.addTilesetImage('rogueTileset');
this.snowTiles = this.bgTilemap.addTilesetImage('snowTileset');
//Set the length of each tileset
let terrainTilesetLength = this.terrainTiles.total;
let terrainTileset2Length = this.terrainTiles2.total;
let rogueTilesetLength = this.rogueTiles.total;
let snowTilesetLength = this.snowTiles.total;
//Set the first gid of each tileset to the total of the previous tileset
this.terrainTiles.firstgid = 0;
this.terrainTiles2.firstgid = terrainTilesetLength;
this.rogueTiles.firstgid = terrainTilesetLength + terrainTileset2Length;
this.snowTiles.firstgid = terrainTilesetLength + terrainTileset2Length + rogueTilesetLength;
// Store tileset information
this.tilesetsInfo = [
{ name: 'terrainTileset', firstgid: this.terrainTiles.firstgid },
{ name: 'terrainTileset2', firstgid: this.terrainTiles2.firstgid },
{ name: 'rogueTileset', firstgid: this.rogueTiles.firstgid },
{ name: 'snowTileset', firstgid: this.snowTiles.firstgid }
];
let allTilesets = [
this.terrainTiles,
this.terrainTiles2,
this.rogueTiles,
this.snowTiles
];
// Create two main layers
this.terrainLayer = this.bgTilemap.createBlankLayer('terrainLayer', allTilesets, 0, 0);
this.detailLayer = this.bgTilemap.createBlankLayer('detailLayer', allTilesets, 0, 0);
//log detaillayer
console.log(this.detailLayer);
// Optionally, you can manage all layers together if you need to apply settings to all
this.allLayers = [
this.terrainLayer,
this.detailLayer
];
}
getTilesetForIndex(globalIndex)
{
for (let i = this.tilesetsInfo.length - 1; i >= 0; i--)
{
let tileset = this.tilesetsInfo[i];
if (globalIndex >= tileset.firstgid)
{
return this.bgTilemap.getTileset(tileset.name);
}
}
return null;
}
placeTile(x, y, globalIndex, layer)
{
let tileset = this.getTilesetForIndex(globalIndex);
if (!tileset)
{
console.error(`Tileset not found for global index: ${globalIndex}`);
return;
}
//log name of tileset, first gid, and global index
//console.log('tileset: ' + tileset.name + ' | firstgid: ' + tileset.firstgid + ' | globalIndex: ' + globalIndex);
// Calculate the local tile index within the selected tileset
let localIndex = globalIndex - tileset.firstgid;
// Place the tile
layer.putTileAt(localIndex, x, y, false, tileset.name);
}
getTotalTilesInLayer(layer)
{
let totalTiles = 0;
layer.tileset.forEach(tileset => {
totalTiles += tileset.total;
});
return totalTiles;
}
placeDebugTileLine()
{
for (let i = 0; i < this.getTotalTilesInLayer(this.detailLayer); i++)
{
let row = Math.floor(i/25);
let col = i % 25;
this.placeTile(col, row, i, this.detailLayer);
}
}
One thing I have noticed is that in placeTile() the layer.putTileAt method seems to ignore the final parameter. I can put in 'invalidTileSetNameHere' and it will continue as normal in the screenshot which seems odd given the documentation on it.
Update: Using info from the response I now have this. However I get an undefined error once the map.putTileAt loop reaches i = 350 which is the first index after the first tileMap's length (terrainTilemap) has been exhausted.
initTileSets2()
{
let map = this.scene.make.tilemap({
key:'map',
width: 16,
height: 16,
tileWidth: 16,
tileHeight: 16
});
// Add all tilesets to the tilemap
let tiles1 = map.addTilesetImage('terrainTileset'); // 350 tiles
let tiles2 = map.addTilesetImage('terrainTileset2'); // 324 tiles
let tiles3 = map.addTilesetImage('rogueTileset'); // 60 tiles
let tiles4 = map.addTilesetImage('snowTileset'); // 30 tiles
//Set the first gid of each tileset to the total of the previous tileset
tiles1.firstgid = 0;
tiles2.firstgid = tiles1.total;
tiles3.firstgid = tiles2.firstgid + tiles2.total;
tiles4.firstgid = tiles3.firstgid + tiles3.total;
let allTilesets = [
tiles1,
tiles2,
tiles3,
tiles4
];
let layer1 = map.createBlankLayer('layer1', allTilesets, 20, 50).setScale(0.75);
let tilePerRow = 25;
for (let i = 0; i < tiles4.firstgid; i++) {
let col = i % tilePerRow;
let row = Math.floor( i / tilePerRow );
console.log('i: ' + i + ' | col: ' + col + ' | row: ' + row);
map.putTileAt( i, col, row )
}
this.map = map;
}
One other oddity I've found: if I hardcode to stop at i = 350 (length of the first tileset) and remove the updates to the firstgid for each set of tiles, I get the following render.
This appears to be each tileset being rendered on top of the previous one starting and index 0. So the tiles are all in the map, just when I try to make them not get stacked on top of eachother/replace the previous tileset's tiles I get the scenario before.
The last thing you commented about layer.putTileAt()
, works as designed. You are calling the function on the layer
, not on the map
. The layer doesn't have a fifth parameter (link to documentation), and on the map
function , it I would be the layer
.
SideQuestion: Was there an error in the console?
But As far as I can see it it works, I just encountered a minor bug, I'm not sure if of my code or phaser, I added a comment in my code, how I "fixed it".
Small Demo, that does more ore less the same as your code:
(There seems to be a minor bug in phaser , or my code that's what the last created texture had to be bigger)
Maybe you can use this demo to help find your error.
document.body.style = 'margin:0;';
var config = {
width: 536,
height: 183,
scene: { create },
};
new Phaser.Game(config);
console.clear();
function create () {
this.add.text(10,10, 'OUTPUT')
.setScale(1.5)
.setOrigin(0)
.setStyle({fontStyle: 'bold', fontFamily: 'Arial'});
// Creating Textures for the tilesets
let graphics = this.make.graphics();
graphics.fillStyle(0xffffff);
graphics.fillRect(0, 0, 10, 10);
graphics.fillStyle(0x0);
graphics.fillRect(10, 0, 10, 10);
graphics.generateTexture('tileset_1', 20, 10);
graphics.fillStyle(0xff0000);
graphics.fillRect(0, 0, 10, 10);
graphics.fillStyle(0xcdcdcd);
graphics.fillRect(10, 0, 10, 10);
graphics.generateTexture('tileset_2', 20, 10);
graphics.fillStyle(0x00ff00);
graphics.fillRect(0, 0, 10, 10);
graphics.fillStyle(0xffff00);
graphics.fillRect(10, 0, 10, 10);
graphics.generateTexture('tileset_3', 20, 10);
graphics.fillStyle(0x0000ff);
graphics.fillRect(0, 0, 10, 10);
graphics.fillStyle(0xff00ff);
graphics.fillRect(10, 0, 10, 10);
// BUG (maybe): BugFixing had to make the image bigger
graphics.generateTexture('tileset_4', 90, 10);
// END -> Creating Textures for the tilesets
initTileSets.call(this);
}
function initTileSets() {
let map = this.make.tilemap({
key:'map',
width: 10,
height: 10,
tileWidth: 10,
tileHeight: 10
});
// Add all tilesets to the tilemap
let tiles1 = map.addTilesetImage('tileset_1');
let tiles2 = map.addTilesetImage('tileset_2');
let tiles3 = map.addTilesetImage('tileset_3');
let tiles4 = map.addTilesetImage('tileset_4');
//Set the length of each tileset
let tile1Length = tiles1.total;
let tile2Length = tiles2.total;
let tile3Length = tiles3.total;
let tile4Length = tiles4.total;
//Set the first gid of each tileset to the total of the previous tileset
tiles1.firstgid = 0;
tiles2.firstgid = tile1Length;
tiles3.firstgid = tiles2.firstgid + 2;
tiles4.firstgid = tiles3.firstgid + 2;
// Store tileset information
this.tilesetsInfo = [
{ name: 'tiles1', firstgid: tiles1.firstgid },
{ name: 'tiles2', firstgid: tiles2.firstgid },
{ name: 'tiles3', firstgid: tiles3.firstgid },
{ name: 'tiles4', firstgid: tiles4.firstgid }
];
let allTilesets = [
tiles1,
tiles2,
tiles3,
tiles4
];
let layer1 = map.createBlankLayer('layer1', allTilesets, 20, 50)
.setScale(2);
let tilePerRow = 3;
for (let i = 0; i < 8; i++) {
let col = i % tilePerRow;
let row = Math.floor( i / tilePerRow );
map.putTileAt( i, col, row )
}
this.map = map;
}
<script src="//cdn.jsdelivr.net/npm/phaser/dist/phaser.min.js"></script>
Update2:
I posted this issue in the phaser discourse forum, showed they an option, how to solve this issue in a different way. (also a great place to find specific phaser help)
Simply adding the gid
, in the "creation" of the tileset image function addTilesetImage
, instead of afterwards.
Simple remove this lines
tiles1.firstgid = 0;
tiles2.firstgid = tile1Length;
tiles3.firstgid = tiles2.firstgid + 2;
tiles4.firstgid = tiles3.firstgid + 2;
And alter addTilesetImage
lines, to add the gid
as 7th parameter (link to documentation).
// Add all tilesets to the tilemap
let tiles1 = map.addTilesetImage('tileset_1','tileset_1',
10,10,
0,0,
0); // <- GID
let tiles2 = map.addTilesetImage('tileset_2','tileset_2',
10,10,
0,0,
2); // <- GID
let tiles3 = map.addTilesetImage('tileset_3','tileset_3',
10,10,
0,0,
4); // <- GID
let tiles4 = map.addTilesetImage('tileset_4', 'tileset_4',
10,10,
0,0,
6); // <- GID
And than you wouldn't need the aforementioned workaround.