Tile Map Generator

This tool was designed to help users to easily create tons of random tile maps for their games.

This tool (for now) works under the command line, you will need to create a small script to provide the data and then run the tool execution through Node JS.

You can find the tool on GitHub or just install it through NPM:

- GitHub: https://github.com/damian-pastorini/tile-map-generator/

- NPM: https://www.npmjs.com/package/@reldens/tile-map-generator

npm i @reldens/tile-map-generator

Once you install it, you can use the tool in two ways:

- Generating files for each element to be passed to the map generator:

https://github.com/damian-pastorini/tile-map-generator/tree/master/examples/layer-elements-object

- Generating a single file with all the contents and setting everything on the tile map:

https://github.com/damian-pastorini/tile-map-generator/tree/master/examples/layer-elements-composite

Note: make sure to have a "generated" folder created previously to start generating the maps (I will fix this in the following versions).

First let's have a summary on how the generator itself works:

- You need to provide the map data, most of these fields are the same provided in any Tiled-Map JSON file under the "tileset" property, but some with small changes:

"columns":52, // same
"image":"outside.png", // tileSheetName
"imageheight":384, // imageHeight
"imagewidth":832, // imageWidth
"margin":0, // same
"spacing":0, // same
"tilecount":1248, // tileCount
"tileheight":16, // tileSize
"tilewidth":16, // tileSize

And some new:

tileSheetPath: '/full-path-to-the-tileSheetName',
layerElements: {element1JSON, element2JSON}, // this is the array of map elements that will be used to randomly create the map
elementsQuantity: {element1JSON: 2, element2JSON: 7}, // this is the quantity that should be placed for each element on the map
groundTile: 116, // the tile index for the ground
mainPathSize: 3, // the amount of tiles used for the paths starting point
blockMapBorder: true, // is to prevent any elements from been place in any borders, and block the players from reach the border
freeSpaceTilesQuantity: 3, // the amount of tiles that will be added additionally to map size as free space
variableTilesPercentage: 22, // this is the proportion of the free space in the map that will get a randomized randomGroundTiles
pathTile: 121, // the tile used to create the paths
collisionLayersForPaths: ['change-points', 'collisions', 'tree-base'], // the layers that should block the tiles for the paths
randomGroundTiles: [23,28,29], // the tile indexes used for the variableTilesPercentage
surroundingTiles: { // this are the tile indexes used for the path borders
 '-1,-1': 127, // 294, // top-left
 '-1,0': 124, // 295, // top-center
 '-1,1': 130, // 296, // top-right
 '0,-1': 126, // 293, // middle-left
 '0,1': 129, // 289, // middle-right
 '1,-1': 132, // 292, // bottom-left
 '1,0': 131, // 291, // bottom-center
 '1,1': 133, // 290, // bottom-right
},
corners: { // this are the tile indexes used for the path corners
 '-1,-1': 285, // 294, // top-left
 '-1,1': 284, // 296, // top-right
 '1,-1': 283, // 292, // bottom-left
 '1,1': 282, // 290, // bottom-right
}

When the process starts the map size will be calculated based on the area for the total elements to be placed + the specified free space, for example:

- element 1: 2x2 tiles, quantity 2

- element 2: 3x3 tiles, quantity 1

- freeSpaceTilesQuantity = 2

The map will get the size putting the first 2 elements in the same row, the next element in the second row, and then will add the free space, this will give us:

- 4 tiles width for the elements on the first row by 2 tiles height > map size 4x2

- for the next row it already has space enough for the 3 tiles width, so it will only add 3 tiles more to the height > updated map size 4x5

- then add the free space of 2 tiles > updated map size 6x7

The elements will be placed randomly without colliding each other.

After calculate the map size and placed the elements, it will search for each element with a path layer and the single tile marked with the "pathTile".

With this information it will generate paths from the main path starting point (randomly placed in the map borders using the mainPathSize), to each element pathTile.

In order to get the layer elements we need to create the contents by following some conventions and setting the configurations required for the randomize process (like how many of each element, paths, ground variations, etc.).

Let's open Tiled (https://www.mapeditor.org/), and check how a normal map for Reldens looks like (see the image above is from the default sample theme).

As you can see, the layers naming is following the conventions described in https://www.reldens.com/documentation/how-to-create-a-new-scene-room, in summary:

- "collisions" is used for everything that will collide with the player or enemies.

- "over-player" is used to make the entire layer always be displayed over the player.

- "change-points" is used to define where the room will send the player to another room.

- "respawn-area" is used to define where specific objects will respawn after defeat.

Obviously "collisions" and "over-player" can be combined so you can get the player collide whith stuff that could be below the current tile without need to define the same tile twice.

Additionally, for the randomize process you will need an extra convention:

- "path" which is used to place the points where the elements will be connected to the paths in the random map (this will get clear later).


With these in mind, we will need some extra conventions for the randomize process, so let's prepare the elements and required scripts.

The "objects" approach


For this, the simple part is the name conventions, since you need to create a new Tiled-Map per element on the map the naming is almost the same in every file. The tool later will pick up each of these to generate the random map and merge the required data from each element.

As you can see in the screenshot above, each element for the map is a different file and the conventions are the described ones.

Also, you can note the "path" here been a single tile below the house door.

All these assets are available as well in the examples folder (in the packages and GitHub):

https://github.com/damian-pastorini/tile-map-generator/tree/master/examples/layer-elements-object

The next step is to generate the script, which is the most "complex" part for this approach.

The problem here is: we need to define everything manually on the script:

- You need to require each map file.

- Then specify every element quantity.

- Need to define every single tile index for every randomize part: pathTile, groundTile, randomGroundTiles, surroundingTiles and corners.

In the examples, the groundTile will be the grass, the pathTile will be the full floor tile, the surroundingTiles are the ones used to wrap the path, and the corners are for the crossing paths.

One IMPORTANT note here is: you need to +1 to the tile index display in the Tiled app, in the following screenshot the correct tile index for the marked tile is not 282 but 283, this is because how the layers are later build

Here's the example script for the map randomizer:

const { RandomMapGenerator} = require('@reldens/tile-map-generator');
const { layerElements } = require('./layer-elements');

const mapData = {
 rootFolder: __dirname,
 tileSize: 32,
 tileSheetPath: 'tilesheet.png',
 tileSheetName: 'tilesheet.png',
 imageHeight: 578,
 imageWidth: 612,
 tileCount: 306,
 columns: 18,
 margin: 1,
 spacing: 2,
 // tiles: [],
 layerElements,
 elementsQuantity: {house1: 3, house2: 2, tree: 6},
 groundTile: 116,
 mainPathSize: 3,
 blockMapBorder: true,
 freeSpaceTilesQuantity: 2,
 variableTilesPercentage: 15,
 pathTile: 121,
 collisionLayersForPaths: ['change-points', 'collisions', 'tree-base'],
 randomGroundTiles: [26, 27, 28, 29, 30, 36, 37, 38, 39, 50, 51, 52, 53],
 surroundingTiles: {
 '-1,-1': 127, // top-left
 '-1,0': 124, // top-center
 '-1,1': 130, // top-right
 '0,-1': 126, // middle-left
 // '0,0': 121, // middle-center
 '0,1': 129, // middle-right
 '1,-1': 132, // bottom-left
 '1,0': 131, // bottom-center
 '1,1': 133, // bottom-right
 },
 corners: {
 '-1,-1': 285, // top-left
 '-1,1': 284, // top-right
 '1,-1': 283, // bottom-left
 '1,1': 282, // bottom-right
 }
};

const generator = new RandomMapGenerator(mapData);

generator.generate();

By running this script with:

node ./generate.js

You will get as many map variations you want.





The "composite" approach


Here, the simple part is the script, most of the required information (like tiles and so) will be taken from the map file itself, so let's see how it looks like:

const { ElementsProvider } = require('@reldens/tile-map-generator/lib/generator/elements-provider');
const { RandomMapGenerator} = require('@reldens/tile-map-generator');
const map = require('./reldens-town-composite.json');
const rootFolder = __dirname;

const execute = async () => {
 let elementsProvider = new ElementsProvider({map, rootFolder, factor: 1});
 await elementsProvider.splitElements();
 let optimizedMap = elementsProvider.optimizedMap;
 let optimizedTileset = optimizedMap.tilesets[0];
 let mapData = {
 rootFolder,
 tileSize: optimizedMap.tilewidth,
 tileSheetPath: elementsProvider.fileHandler.joinPaths('generated', optimizedTileset.image),
 tileSheetName: optimizedTileset.image,
 imageHeight: optimizedTileset.imageheight,
 imageWidth: optimizedTileset.imagewidth,
 tileCount: optimizedTileset.tilecount,
 columns: optimizedTileset.columns,
 margin: optimizedTileset.margin,
 spacing: optimizedTileset.spacing,
 tiles: optimizedTileset.tiles,
 layerElements: elementsProvider.croppedElements,
 elementsQuantity: elementsProvider.elementsQuantity,
 groundTile: elementsProvider.groundTile,
 mainPathSize: 3,
 blockMapBorder: true,
 freeSpaceTilesQuantity: 2,
 variableTilesPercentage: 15,
 pathTile: elementsProvider.pathTile,
 collisionLayersForPaths: ['change-points', 'collisions', 'tree-base'],
 randomGroundTiles: elementsProvider.randomGroundTiles,
 surroundingTiles: elementsProvider.surroundingTiles,
 corners: elementsProvider.corners
 };
 const generator = new RandomMapGenerator(mapData);
 generator.generate();
};

execute();

The "ElementsProvider" is a helper class that will read the map file to get all the required data from the tiles custom properties, then will optimize the map (to merge all the tile sets into a single file for the random map), it can scale it if you need (Note: actually you need to set the factor: 1 to avoid the scale, I will fix this soon), and will give you a result ready to be used on the map-generator.

What won't be provided by the "ElementsProvider"?

- mainPathSize: this is where the paths to the elements will start, and the size is the amounts of tiles the path is going to ocuppy on the map border.

- blockMapBorder: is to prevent any elements from been place in any borders, and it will also generate a "collisions" layer to prevent the players from reach the border (Note: this will be configurable soon).

- freeSpaceTilesQuantity: the map size will be calculated from the total of elements provided multiplied by each element area, this parameter is the amount of tiles that will be added additionally to map size as free space (disregarding how the elements are placed in the map).

- variableTilesPercentage: this is the proportion of the free space in the map, that will get a randomized tile from the groundVariations.

Back on Tiled! In the image above you can see the example for the composite map-file.

As you can see, this file use multiple "tilesets", but not all of the tiles are been used in the composite.

Also, the tile size is 16x16, and we want to have 32x32 tiles.

For these changes is where the Tile Map Optimizer kicks in: as first step of the "ElementsProvider" it will generate an instance of the TileMapOptimizer, which will scrap only the used tiles from each tileset, generate a new image, and create a copy of the map to update the JSON tile indexes references.

At the same time if the "factor" was specified (in our case factor = 2, for 16x16 > 32x32), it will resize the image.

Now let's look at the required conventions, starting for the layers names:

- "ground", this must contain a single tile which will be used as base ground.

- "ground-variations", this should contain all the tiles that can be used for the ground variations.

- "path", this layer must contain all the layers related to the path, not only the path base but also the surrouding tiles and the corners if required.

These 3 are special names used by the tool.

The last rule is to split the layers name in minimum 3 parts by a score "-":

[element-key]-[element-number-reference]-[any-other-required-convention-or-custom-name]

The next step is to edit each tileset:

- Select the used tiles and include a custom property called "key" on each tile related to the required value: "groundTile", "pathTile", each surrounding (top-left, top-center, top-right, middle-lef, middle-right, bottom-left, bottom-center, bottom-right), and each corner tiles (corner-top-left, corner-top-right, corner-bottom-left, corner-bottom-right).

This way you don't need to manually figure it out each tile index, but the generator will get the index by searching on these marked tiles by the property called "key".

Note: if your "tilesets" have any animations, these will be ported automatically by the Tile Optimizer.

For last, in the first layer of each element, you need to include the "quantity" property.

Just pick the layer, and click "Add new property" (type "int"), and the value should be the amount of that element that you may want on the map.

And that's pretty much it! Throw your files in a folder with the script and run it as many times you need to genearte different maps!