Is there any way to split tilesets of premade map

Good evening everyone,
me and my friend developed a game and managed our maps with Tiled Map Editor but we are using tilesets with size of 2048x2048 but we want to to use smaller tilesets because bigger ones are causing lag in our game we want to use 1024x1024 max is there any way to split our tilesets which doesnt mess up our maps we have over 500 maps and we really want to reduce tileset size is there any way to solve this problem?

Thanks in advance.

There is no built-in action for this, but you can definitely solve this by scripting a custom action that would replace the big tilesets with smaller ones.

The scripting API is documented at Scripting — Tiled 1.6.0 documentation (since the full API docs have moved to Tiled Scripting API, and there is a tiled-api NPM package you can install to get auto-completion in some text editors). You can register a custom action with tiled.registerAction.

Since I assume that the reduction in tileset size will mean using smaller tiles, and thus the size of the tile layers will need to be increased, I’d suggest to not write a script that modifies a map in-place, but instead to write one that sets up a new map based on the old one and then saves this new map somewhere.

Hey thank you for your reply, our intention is to just split the 1 tileset into 4 smaller one and not mess up the maps would it be possible? like i have 1 tileset lets say A i want it to be splitted into B,C,D,E without messing up the map and i want map to read tiles from those BCDE tilesets

This would be possible, via scripting. The general procedure would be:

  1. Create the split tilesets manually.
  2. In a script, open those tilesets.
  3. For the currently open map, iterate over every layer (if your maps use Layer Groups, you’ll need to iterate over layers recursively, the scripting API currently provides no linear way to iterate grouped layers).
    1. For each tile in that layer, get its tile ID and calculate which new tileset this would belong to. If it’s a simple four-way split, this should be a simple calculation.
    2. Fetch the correct Tile from the appropriate new tileset, and replace the old tile with it.
  4. Remove the old tileset. You can do this manually or with the TileMap::removeTileset(tileset) method.

If you have a simple four-way split in your tileset, then the calculation from old tile ID to new tileset+tile ID would look something like this pseudocode (the API calls are all as they are in the Tiled JS API):

Tile oldTile = layer.tileAt(mapX, mapY);

//get the x, y of the tile in the tileset:
int tileX = oldTile.id % oldTilesetWidth;
int tileY = floor(oldTile.id / oldTilesetHeight);

Tileset newTileset;

if(tileX < oldTilesetWidth/2) {
   if(tileY < oldTilesetHeight/2)
      newTileset = topLeftTileset;
   else
      newTileset = bottomLeftTileset;
} else {
   if(tileY < oldTilesetHeight/2)
      newTileset = topRightTileset;
   else
      newTileset = bottomRightTileset;
}
//Get the tile's location within the new tileset:
tileX = tileX % floor(oldTilesetWidth/2);
tileY = tileY % floor(oldTilesetHeight/2);

//Now we know what tileset to use, and the tile's location in that tileset. From the location, we can calculate the new tile ID:
int newTileID = tileX + tileY * floor(oldTilesetWidth/2);
//And now we can get the tile to replace the old one with:
Tile newTile = newTileset.tile(newTileID);

If your split is more complex and tiles are rearranged, then you would need to create a mapping from oldTileID to {newTileset, newTileID}.

Hey there your solution looks really promising to me, i am sorry for late reply but can you tell me how can i iterate all tilesets in a map and all layers in a map with JS api? i dont have groups in any layers

I’d rather not write your entire script for you xP The API methods you’ll want to look into are

  • TileMap.tilesets (map property; all Tilesets associated with the map) or TileMap.usedTilesets() (map method, all tilesets that are actually used). Since your maps start out with just one tileset, you’ll probably just need to grab map.tilesets[0] to get your old tileset.
  • To get references to the new tilesets to use, you’ll need to open them, e.g. using tiled.open("PathToTileset..."). You can just have your four variables and assign each one using a different open statement.
  • TileMap.layerAt(index) and TileMap.layerCount. Since you don’t have groups, you can simply iterate from 0 to map.layerCount (exclusive) using a for loop and get each layer.

Details on all of these can be found in the scripting docs.

lol yea thats exactly what i was asking for thank you xD

I am sorry to disturb you again i have wrote a script but i cant seem to make it work i am sorry i am new with JS not very experienced if that’s the case in my script but can you have look on it and see if i did something wrong?

i’ve tried to look around alot but i cant seem to understand why i cant call any tilesets

let splitTilesets = tiled.registerAction('SplitTilesets', function (action) {

    const tileSets = TileMap.usedTilesets;
    let tileSize = Tileset.tileSize;
    let tileLayers = [];
    const mapSize = TileMap.size;

    for (let i = 0; i < TileMap.layerCount; i++) {
        tileLayers.push(TileMap.layerAt(i))
    }

    //for (let i = 0; i < tileSetscount; i++) {
    let i = 0;
    let topLeftTileset = tiled.open(tileSets[i].fileName + "-1")
    let topRightTileset = tiled.open(tileSets[i].fileName + "-2")
    let bottomRightTileset = tiled.open(tileSets[i].fileName + "-3")
    let bottomLeftTileset = tiled.open(tileSets[i].fileName + "-4")

    TileMap.addTileset(topLeftTileset)
    TileMap.addTileset(topRightTileset)
    TileMap.addTileset(bottomRightTileset)
    TileMap.addTileset(bottomLeftTileset)

    for (let j = 0; j < tileLayers.length; j++) {
        if (tileLayers[j].isTileLayer) {
            for (let x = 0; x <= mapSize * tileSize; x++) {
                for (let y = 0; y <= mapSize * tileSize; y++) {
                    initNewTileID(x * tileSize, y * tileSize, tileLayers[j], tileSets[i], tileSets[i].imageHeight, tileSets[i].imageWidth,
                        topLeftTileset, topRightTileset, bottomLeftTileset, bottomRightTileset)
                }
            }
        }
    }
    //}
})

splitTilesets.text = "Split Tilesets";

tiled.extendMenu("Map", [
    {separator: true},
    {action: "SplitTilesets"},
])

function initNewTileID(mapX, mapY, layer, oldTileset, oldTilesetHeight,
                       oldTilesetWidth, topLeftTileset, topRightTileset, bottomLeftTileset, bottomRightTileset) {
    let oldTile = layer.tileAt(mapX, mapY);

//get the x, y of the tile in the tileset:
    let tileX = oldTile.id % oldTilesetWidth;
    let tileY = Math.floor(oldTile.id / oldTilesetHeight);
    let newTileset;

    if (tileX < oldTilesetWidth / 2) {
        if (tileY < oldTilesetHeight / 2)
            newTileset = topLeftTileset;
        else
            newTileset = bottomLeftTileset;
    } else {
        if (tileY < oldTilesetHeight / 2)
            newTileset = topRightTileset;
        else
            newTileset = bottomRightTileset;
    }
//Get the tile's location within the new tileset:
    tileX = tileX % Math.floor(oldTilesetWidth / 2);
    tileY = tileY % Math.floor(oldTilesetHeight / 2);

//Now we know what tileset to use, and the tile's location in that tileset. From the location, we can calculate the new tile ID:
    let newTileID = tileX + tileY * floor(oldTilesetWidth / 2);
//And now we can get the tile to replace the old one with:
    let newTile = newTileset.tile(newTileID);

    TileLayerEdit.setTile(mapX, mapY, newTile, oldTile.flag)
    TileLayerEdit.apply()

    TileMap.removeTileset(oldTileset)
}

First, “TileMap” in the documentation just refers to the class name of the object. layerCount, size, etc should all be referring to a specific map, which you’re not currently doing. You’re just referring to some nonexistent object called “TileMap” rather than a specific map. Pretty much everywhere you’re using “TileMap”, you’re going to get broken code because you don’t have a variable by that name.

Second, I’m pretty sure your tileset names for the four replacement tilesets aren’t going to be correct. Is each map going to have unique tilesets? If the maps use the same tilesets, why not hard-code the names and not bother with this complication of building them dynamically?

For better code organization, I recommend putting your initNewTileID function inside of the SplitTilesets action. This is allowed in JavaScript, and has the benefit of letting you use the four tilesets you defined without having to pass them as parameters.

There may be other issues in the code (I don’t have time to review your code fully), but hopefully fixing the two I mentioned will at least get you more useful debug messages.

Hey okay i get what you are saying just one final question i read whole documentation i cant seem to find thing which calls current opened map i used tiled.map but i think thats not correct, how can i find instance of current opened map in tiled? like is there a way without using tiled.open?

tiled.activeAsset gets you the current asset. Make sure it’s a map (tiled.currentAsset.isTileMap should be true) before doing any map-specific operations on it.

Looks like you were looking in the right place (the tiled namespace) but didn’t look carefully enough xP

ah thanks this helped :slight_smile:

let map = null;

let splitTilesets = tiled.registerAction('SplitTilesets', function (action) {
    const activeAsset = tiled.activeAsset;
    if (activeAsset.isTileMap) {
        map = activeAsset;
    }

    for (let j = 0; j < map.layerCount; j++) {
        let layer = map.layerAt(j);

        for (let x = 0; x < map.size.width; x++) {
            let mapX = x;

            for (let y = 0; y < map.size.height; y++) {
                let mapY = y;

                //get the x, y of the tile in the tileset:
                let oldTile = layer.tileAt(mapX, mapY);

                if (oldTile != null) {
                    //console.log(tileSets[i].fileName)

                    let tileset = oldTile.tileset;
                    let topLeftTileset = tiled.open(tileset.fileName.replace(/\.[^/.]+$/, "") + "-0" + ".tsx")
                    let topRightTileset = tiled.open(tileset.fileName.replace(/\.[^/.]+$/, "") + "-1" + ".tsx")
                    let bottomRightTileset = tiled.open(tileset.fileName.replace(/\.[^/.]+$/, "") + "-2" + ".tsx")
                    let bottomLeftTileset = tiled.open(tileset.fileName.replace(/\.[^/.]+$/, "") + "-3" + ".tsx")

                    if (topLeftTileset.isTileset) {
                        map.addTileset(topLeftTileset)
                        map.addTileset(topRightTileset)
                        map.addTileset(bottomRightTileset)
                        map.addTileset(bottomLeftTileset)
                    }

                    let oldTilesetHeight = tileset.imageHeight;
                    let oldTilesetWidth = tileset.imageWidth;

                    console.log("Old tileset Width : " + oldTilesetWidth)
                    console.log("Old tileset Height : " + oldTilesetHeight)

                    console.log("At Layer : " + layer.name)
                    console.log("On tileset : " + tileset.name)
                    console.log("Old Tile ID : " + oldTile.id)

                    let tileX = oldTile.id % oldTilesetWidth;
                    let tileY = Math.floor(oldTile.id / oldTilesetHeight);

                    console.log("Old tile X : " + tileX);
                    console.log("Old tile Y : " + tileY);

                    let newTileset;

                    if (tileX < oldTilesetWidth / 2) {
                        if (tileY < oldTilesetHeight / 2)
                            newTileset = topLeftTileset;
                        else
                            newTileset = bottomLeftTileset;
                    } else {
                        if (tileY < oldTilesetHeight / 2)
                            newTileset = topRightTileset;
                        else
                            newTileset = bottomRightTileset;
                    }

                    //Get the tile's location within the new tileset:
                    tileX = tileX % Math.floor(oldTilesetWidth / 2);
                    tileY = tileY % Math.floor(oldTilesetHeight / 2);

                    console.log("New Tileset Width : " + newTileset.imageWidth)
                    console.log("New Tileset Width : " + newTileset.imageHeight)
                    console.log("New Tile X : " + tileX)
                    console.log("New Tile Y : " + tileY)

                    //Now we know what tileset to use, and the tile's location in that tileset. From the location, we can calculate the new tile ID:
                    let newTileID = tileX + tileY * Math.floor(oldTilesetWidth / 2);
                    //And now we can get the tile to replace the old one with:
                    console.log("On new Tileset : " + newTileset.name)
                    console.log("New Tile ID : " + newTileID)

                    let newTile = newTileset.tile(newTileID);

                    let layerEdit = layer.edit();
                    layerEdit.setTile(mapX, mapY, newTile, oldTile.flag)
                    layerEdit.apply();

                    console.log("\n")
                }
            }
            
        }
    }
})

splitTilesets.text = "Split Tilesets";

tiled.extendMenu("Map", [
    {separator: true},
    {action: "SplitTilesets"},
])

hey eishiya i am sorry to bother you again i built this whole script but i think the way you gave me to generate new ID is flawed somehow or either i am doing something wrong i am getting this error can you please help me

image
here’s the output when i am generating a tile which is not 0 i am itarating every time in every layer please help me out thank you

You’re calculating the old tileset ID instead of the new one on this line:

let newTileID = tileX + tileY * Math.floor(oldTilesetWidth / 2);

You should be using the new tileset’s width here. This error was in my example pseudocode and it looks like you copypasted a lot of stuff directly from there. The code was meant to be an example to help you think through the problem, it was not meant to be used directly.


Ah actually i tried that before and i am getting the same result and yea i used your logic in the code to understand what you were saying i tried bunch of stuff but i cant seem to get ID right

let map = null;

let splitTilesets = tiled.registerAction('SplitTilesets', function (action) {
    const activeAsset = tiled.activeAsset;
    if (activeAsset.isTileMap) {
        map = activeAsset;
    }

    for (let j = 0; j < map.layerCount; j++) {
        let layer = map.layerAt(j);

        for (let x = 0; x < map.size.width; x++) {
            let mapX = x;

            for (let y = 0; y < map.size.height; y++) {
                let mapY = y;

                //get the x, y of the tile in the tileset:
                let oldTile = layer.tileAt(mapX, mapY);

                if (!oldTile) {
                    //console.log(tileSets[i].fileName)

                    let tileset = oldTile.tileset;
                    let topLeftTileset = tiled.open(tileset.fileName.replace(/\.[^/.]+$/, "") + "-0" + ".tsx")
                    let topRightTileset = tiled.open(tileset.fileName.replace(/\.[^/.]+$/, "") + "-1" + ".tsx")
                    let bottomRightTileset = tiled.open(tileset.fileName.replace(/\.[^/.]+$/, "") + "-2" + ".tsx")
                    let bottomLeftTileset = tiled.open(tileset.fileName.replace(/\.[^/.]+$/, "") + "-3" + ".tsx")

                    if (topLeftTileset.isTileset) {
                        map.addTileset(topLeftTileset)
                        map.addTileset(topRightTileset)
                        map.addTileset(bottomRightTileset)
                        map.addTileset(bottomLeftTileset)
                    }

                    let oldTilesetHeight = tileset.imageHeight;
                    let oldTilesetWidth = tileset.imageWidth;

                    console.log("Old tileset Width : " + oldTilesetWidth)
                    console.log("Old tileset Height : " + oldTilesetHeight)

                    console.log("At Layer : " + layer.name)
                    console.log("On tileset : " + tileset.name)
                    console.log("Old Tile ID : " + oldTile.id)

                    let tileX = oldTile.id % oldTilesetWidth;
                    let tileY = Math.floor(oldTile.id / oldTilesetHeight);

                    console.log("Old tile X : " + tileX);
                    console.log("Old tile Y : " + tileY);

                    let newTileset;

                    if (tileX < oldTilesetWidth / 2) {
                        if (tileY < oldTilesetHeight / 2)
                            newTileset = topLeftTileset;
                        else
                            newTileset = bottomLeftTileset;
                    } else {
                        if (tileY < oldTilesetHeight / 2)
                            newTileset = topRightTileset;
                        else
                            newTileset = bottomRightTileset;
                    }

                    //Get the tile's location within the new tileset:
                    tileX = tileX % Math.floor(oldTilesetWidth / 2);
                    tileY = tileY % Math.floor(oldTilesetHeight / 2);

                    console.log("New Tileset Width : " + newTileset.imageWidth)
                    console.log("New Tileset Height : " + newTileset.imageHeight)
                    console.log("New Tile X : " + tileX)
                    console.log("New Tile Y : " + tileY)

                    //Now we know what tileset to use, and the tile's location in that tileset. From the location, we can calculate the new tile ID:
                    let newTileID = tileX + tileY * Math.floor(newTileset.imageWidth / 2);
                    //And now we can get the tile to replace the old one with:
                    console.log("On new Tileset : " + newTileset.name)
                    console.log("New Tile ID : " + newTileID)

                    let newTile = newTileset.tile(newTileID);

                    let layerEdit = layer.edit();
                    layerEdit.setTile(mapX, mapY, newTile, oldTile.flag)
                    layerEdit.apply();

                    console.log("\n")
                }
            }
        }
    }
})

splitTilesets.text = "Split Tilesets";

tiled.extendMenu("Map", [
    {separator: true},
    {action: "SplitTilesets"},
])

You wouldn’t need to halve it here. I guess we didn’t actually make a mistake there, whoops. oldTilesetWidth/2 = newTilesetWidth.

The issue in the code is that you’re mixing up the image width and the tileset width (ditto for height). All the logic I provided is in terms of tiles, but you’re using the image width in there. Those are not the same! You need to calculate the size of the tileset in tiles before using it. I left that out of my pseudocode for brevity, but notice that nowhere in the code did I use imageWidth and imageHeight xP
If your tileset doesn’t have margins and spacing, then the calculation is trivial: tilesetWidth = Math.floor(tileset.imageWidth / tileset.tileWidth). If it does have margins and/or spacing, you’ll need to account for those. Subtract the margin first, and then see how many spaced tiles you can fit into what’s left.

Also, the code doesn’t get this far yet, but it’s worth fixing: when setting the tile, you can’t use oldTile.flag, that doesn’t exist. Tiles don’t carry flags with them, the flags are part of the map data. You need to use layer.flagsAt(mapX, mapY).

Also note that you shouldn’t repeat those tiled.open and map.addTileset calls in the loop. You’d only want to load the new tilesets and add them to the map once.

1 Like