Tile Map Generator

The @reldens/tile-map-generator package is a procedural tile map generator that produces Tiled-compatible JSON maps. It is the engine behind the admin Maps Wizard and can also be used directly via Node.js scripts.

What it does

  • Generates procedural tile-based maps with customizable parameters.
  • Produces Tiled Map Editor compatible JSON output.
  • Uses A* pathfinding to validate map connectivity.
  • Supports multiple generation strategies (single object loader, single composite loader, multiple by loader, multiple with associations).
  • Places elements with free-space constraints and ground tile variations.
  • Generates wall tiles, paths, borders, and corner transitions.
  • Builds multi-floor dungeons with stairs and change-points.

Installation

npm install @reldens/tile-map-generator

Generating maps from the command line

The package includes the reldens-generate-map command. It maps a set of parameters to one of the nine example scripts in examples/, runs it, and prints the full path of every map file the run wrote to disk. It does not reimplement any generation flow: it requires the example file, creates the exported class, awaits its execute() method and reads the paths back from the generators the example kept.

Running the command

From the package root (for example a clone of the tile-map-generator repository):

npm run reldens-generate-map -- --composite
  • The -- separator is the reliable form, because npm run consumes -- prefixed flags as its own configuration instead of forwarding them to the script.
  • The form without the separator (npm run reldens-generate-map --composite) also works: npm exports every unknown command line flag as an npm_config_* environment variable, and the command reads those when no argument was forwarded. Recent npm versions print an Unknown cli config warning for that form; the -- form does not warn.
  • The command is declared in the package bin entry ("reldens-generate-map": "bin/reldens-generate-map.js") and in its scripts ("reldens-generate-map": "node bin/reldens-generate-map.js"), so in a project that installs the package it can also be run with npx reldens-generate-map --composite.
  • Running with no parameters, or with a combination that does not match, prints the usage and every available combination, and exits with code 1.

Available parameters

The parameters are order independent (they are sorted before the lookup, so --composite --with-loader and --with-loader --composite are the same), but the combination must match one of these exactly:

  • --composite - examples/layer-elements-composite/generate.js
  • --composite --with-loader - examples/layer-elements-composite/generate-with-loader.js
  • --composite --dungeon --with-loader - examples/layer-elements-composite/generate-with-loader-dungeon.js
  • --composite --multiple-with-names - examples/layer-elements-composite/generate-multiples-with-names.js
  • --composite --multiple-with-names --with-loader - examples/layer-elements-composite/generate-with-loader-multiples-with-names.js
  • --composite --multiple-with-associations - examples/layer-elements-composite/generate-multiples-with-associations.js
  • --composite --multiple-with-associations --with-loader - examples/layer-elements-composite/generate-with-loader-multiples-with-associations.js (the town with inner houses, see Town With Inner Houses)
  • --object - examples/layer-elements-object/generate.js
  • --object --with-loader - examples/layer-elements-object/generate-with-loader.js
  • The parameter is --multiple-with-associations while the file name is generate-multiples-with-associations.js: the parameters are mapped explicitly, not derived from the file names, so the singular and plural difference is intentional.
  • --dungeon only exists in the --composite --with-loader form, and the --object family has no dungeon or multiple variants.

How the examples expose their generators

Every example is a class named after its file in capitalized camel case, and every class follows the same contract:

  • The constructor initializes this.generators, and this.associatedMaps for the association flows, so the containers exist before anything runs.
  • async execute() performs the generation and stores each RandomMapGenerator under this.generators[mapName] before calling generate(), so a failure part way through still leaves the already created generators reachable.
  • The direct invocation at the bottom of the file is guarded, which makes the file safe to require: running node examples/layer-elements-composite/generate.js generates as usual, while requiring the same file from the command only defines the class.
if(require.main === module){
    (new Generate()).execute();
}

The command takes the first export of the required module, the same discovery convention the package test runner uses for test classes, so no export name has to be registered anywhere.

The two loader driven multiple flows do not build their own generators; they copy the containers from the orchestrator after it runs:

await generator.generate();
this.generators = generator.generators;
this.associatedMaps = generator.associatedMaps;

The manual association flow (generate-multiples-with-associations.js) keeps each AssociatedMaps instance as this.associatedMaps[mapName].

How the generated map paths are collected

  • RandomMapGenerator assigns this.mapFileFullPath in assignBaseOptions and writes the map file to it in generate(). That property is the only place where the written path exists, since generate() returns the Tiled map object rather than the path.
  • The paths are read from two places only: the example generators, and the generators of every associatedMaps entry. AssociatedMaps holds the sub-maps and the floors in one flat generators object, so a single pass reaches every interior and every floor.
  • A path is only reported when FileHandler.isFile() confirms the file exists, because mapFileFullPath is assigned before generation and survives the two failure returns of generate() (the validation failure and the tilesheet copy failure).

Output

Logger is silent by default in this package: log() drops any level above RELDENS_LOG_LEVEL, which is 0 when unset, so even Logger.critical prints nothing. Like the package test runner, the command reserves a dedicated level for terminal output:

Logger.activeLogLevels = [this.outputLogLevel];
Logger.setLogLevel(this.outputLogLevel);
Logger.addTimeStamp = false;

A non-empty activeLogLevels is an exclusive allow-list, so the command output is not mixed with the generator internal logging. Each generated map is printed on its own line:

 - Generated map: /path/to/tile-map-generator/examples/layer-elements-composite/generated/town-001.json
  • The leading - comes from Logger.log(), which always prefixes the timestamp and the level label; with the timestamp disabled and an empty label the prefix collapses to a single dash.
  • The exit code is 0 when the example completed, and 1 when the parameters were missing or invalid, or when the example threw. When the example throws part way through, the maps already written are still printed before the failure exit.

Known constraints

  • The --composite, --composite --with-loader, --object and --object --with-loader combinations do not set a mapName, so their output file is named random-map-<date>.json and changes on every run. The printed path is read back from the generator, never predicted.
  • Sub-map and floor names in the association flows derive from the change points generated at runtime, so they cannot be listed before the run.
  • rootFolder stays __dirname in every example. The loaders and the optimizer resolve every composite JSON and tileset PNG relative to it, and the generated output folder is built from it, so the command works the same from any directory and always writes into the generated folder next to the example (inside the installed package when it runs from node_modules).

Generation strategies

The package exposes four generator strategies. The admin Maps Wizard lets you pick one through the UI; in code you pick the matching class.

  • RandomMapGenerator (single map, object loader) - generates one random map from a list of separate element JSON files. Each element is placed individually based on a quantity specified in the generator data.
  • RandomMapGenerator (single map, composite loader) - generates one random map from a single composite JSON containing every element. Element quantities come from a custom property on each base layer.
  • MultipleByLoaderGenerator (multiple maps) - generates many random maps from the same input. You pass a list of mapNames and the generator produces one map per name.
  • MultipleWithAssociationsByLoaderGenerator (multiple maps with associations) - generates linked main maps plus sub-maps. Useful for towns where each house door links to a separate inner house map.

See Generate Multiple Maps with Associations for the configuration of the multi-map strategy, and Town With Inner Houses for a complete worked example of it.

Loaders

  • LayerElementsObjectLoader - loads map configuration from JSON files; loads individual element files (houses, trees) referenced by the config; validates input against the object schema.
  • LayerElementsCompositeLoader - loads composite map configurations where every element is embedded in a single Tiled map JSON; validates against the composite schema.

Core classes

  • RandomMapGenerator (lib/random-map-generator.js) - main generator class. Methods: generate(), fromElementsProvider(), placeElements(), generateLayersList(). Validates connectivity with A* pathfinding.
  • ElementsProvider (lib/generator/elements-provider.js) - splits and processes elements from composite Tiled maps. Extracts each element (house, tree, etc.) into its own layer group.
  • PathFinder (lib/path-finder/path-finder.js) - wrapper for the pathfinding npm package; creates pathfinding grids from map data and runs A* for connectivity checks.
  • PathConnector (lib/generator/path-connector.js) - connects isolated map areas with paths; generates main paths and element-to-path connections; applies path tiles with surrounding borders and corners.
  • WallsGenerator (lib/generator/walls-generator.js) - generates wall tiles around paths; supports inner and outer walls; applies wall patterns based on surrounding tiles.
  • SpotGenerator (lib/generator/spot-generator.js) - generates invisible spots / named areas on the map; supports nested spots with depth control; used for spawn points, safe zones, special areas.
  • MapGridBuilder (lib/generator/map-grid-builder.js) - initializes empty map grids; marks walkable / non-walkable positions; handles border blocking and entry positions.
  • PositionFinder (lib/generator/position-finder.js) - finds valid positions for element placement; sorts positions by distance from map center or borders.
  • PropertiesMapper (lib/generator/properties-mapper.js) - maps tile properties for different tile types; handles surrounding tiles (borders, corners) used for path tile variations.

Validators

  • OptionsValidator - validates required generation options (tileSize, tileSheet, dimensions, elements).
  • MapValidator - validates generated map structure.
  • PathConnectivityValidator - validates that all map areas are reachable via paths using pathfinding.
  • BoundaryValidator - prevents out-of-bounds element placement.
  • ElementPlacementValidator - validates element placement against constraints (free space, overlaps).
  • FreeSpaceValidator - validates free space around elements (minimum spacing).
  • WallsValidator - validates wall tile placement and patterns.
  • GroundVariationsValidator - validates ground tile variations (percentages applied correctly).
  • SpotsValidator - validates spot generation (boundaries, nesting).

Map components

  • JsonFormatter - formats map data into Tiled JSON format.
  • MapDataMapper - maps data between formats; converts ElementsProvider data to map configuration.
  • TilesShortcuts - shortcuts for common tile operations; maps tile IDs to semantic names (path, ground, wall).
  • WallsMapper - maps wall tiles to positions and handles wall tile variations.
  • WangsetMapper - maps Tiled Wangset tiles for terrain transitions.

Tile patterns

  • BordersAndCornersTiles - tile patterns for borders and corners; provides sequences for pattern matching and replacement.
  • Corners - corner tile patterns and transitions.
  • InnerWalls - inner wall patterns placed on the inside of path borders.
  • OuterWalls - outer wall patterns placed on the outside of path borders.
  • OuterWallsMerge - merges outer wall patterns and handles overlap resolution.

Schemas

  • MapDataSchema (lib/schemas/map-data-schema.js) - JSON schema for object-based map data validation.
  • MapCompositeDataSchema (lib/schemas/map-composite-data-schema.js) - JSON schema for composite map data validation.

Pipeline integration

This package is the third stage of a four-stage Maps Wizard pipeline:

  1. Admin UI - the user configures tilesets and spots in the browser, then triggers Generate.
  2. @reldens/tileset-to-tilemap - receives the UI state, produces composite.json + map-generator-config.json + element PNGs. See Tileset to Tilemap.
  3. @reldens/tile-map-optimizer - strips unused tiles from the composite, produces an optimized PNG and JSON. See Tile Map Optimizer.
  4. @reldens/tile-map-generator (this package) - reads the config plus the optimized composite, generates the final Tiled map JSON.

Important notes

  • Generates Tiled Map Editor compatible JSON maps (drop-in for any Tiled-aware engine).
  • Uses @reldens/tile-map-optimizer for output optimization and element splitting.
  • Integrates with the pathfinding npm package for A* pathfinding.
  • Map layer optimization includes merging layers and removing empty layers.
  • Supports map associations (stairs, change-points, return-points) for multi-floor dungeons.
  • Always uses Logger from @reldens/utils instead of console.log.
  • Always uses FileHandler from @reldens/server-utils for file operations.

Related Documentation

Go Up