Skip to content

Map Format

Ethan Harvey edited this page Oct 17, 2026 · 6 revisions

Map Format

How a level map file is built, what every symbol means, and how entities on a map become things in the game.

All maps live in source/core/assets/maps/. template.json in that folder is a small working map with one of everything. Copy it to start a new one.

File structure

{
  "name": "Level 1 - Out of the Underworld",
  "tileSize": 0.5,
  "backgroundTexture": "images/level1/level1-map.png",
  "legend":       { "#": { "type": "WALL", "texture": "images/level1/cavern-rock-512px.png" } },
  "entityLegend": { "P": { "type": "PLAYER" } },
  "lighting": {"ambient": {"color": ..., "intensity": ...}, "player": {"radius": ..., "color": ...}}
  "layers": {
    "background": ["..."],
    "terrain":    ["..."],
    "foreground": ["..."],
    "entities":   ["..."]
  },
  "backdrops": {"sublvl1": [...]}
  "subLevels":   [ ... ],
  "transitions": [ ... ]
}
Key Required What it is
name yes Shown on the level title card and in logs.
tileSize no World size of one tile. Every map uses 0.5.
backgroundTexture no One image stretched over the whole map, behind the tile layers. Draw it at 16 pixels per tile, so a 56 by 64 tile map needs an 896 by 1024 image. If the file does not exist yet, the map still loads and a warning is logged.
legend yes Tile symbols used in background, terrain and foreground. See Tile legend.
entityLegend no Symbols used in the entities layer. See Entity legend.
lighting no Ambient and player lighting for the map.
layers yes The grids. See Layers.
backdrops no The backdrop overlays that are animated and move behind the actual map.
subLevels no Named sections of one map, such as Level 1's dungeon and Nether. Sets the camera bounds and the title shown when crossing between them.
transitions no Doorways to another map.

Layers

Each layer is a list of strings: one string per row, one character per tile. A space is an empty tile.

Layer Symbols from What it does
background legend Art drawn behind everything. No collision.
terrain legend Art the player stands on and bumps into. This is the collision layer.
foreground legend Art drawn in front of the terrain. No collision.
entities entityLegend Where the player, enemies and loot start. Not drawn.

Two rules catch most mistakes:

  • Rows go top to bottom in the file, but y = 0 is the bottom row in the game. The loader flips the rows, so the last string in a layer is the floor.
  • legend and entityLegend are separate. The same letter can mean one thing as a tile and another as an entity. In Level 1, S is the River Styx in legend and a skeleton in entityLegend.

Every layer must have the same number of rows. If the entities layer does not match, the map refuses to load.

Tile legend

Each entry gives a tile type and a texture:

"#": { "type": "WALL", "texture": "images/level1/cavern-rock-512px.png" }
Type Collision
FLOOR solid
WALL solid
PLATFORM one-way platform, can jump up through it
HAZARD hurts on contact
LADDER climbable, no collision
DECORATIVE none, art only

Tile properties

Any other key on a legend entry is kept as a property of that tile. The map system does not use it. The feature that needs it reads it back.

"~": { "type": "HAZARD", "texture": "images/level1/river-styx-512px.png", "damage": "15" }
TileDefinition tile = level.tileAt(x, y);
int damage = tile.getInt("damage", 10);  // 10 if missing or not a number
boolean secret = tile.flag("hidden");     // false if missing
Property Used by Effect
damage Hazard tiles Health lost on contact. Defaults to 10 when not set.

Use a property for new tile behaviour instead of adding a new type.

Entity legend

"entityLegend": {
  "P": { "type": "PLAYER" },
  "S": { "type": "ENEMY", "enemyType": "skeleton" },
  "R": { "type": "ENEMY", "enemyType": "ranged-skeleton" },
  "L": { "type": "LOOT" }
}
Type Extra keys What spawns
PLAYER none The player. One per map.
ENEMY enemyType The enemy registered under that name. See Enemy names.
LOOT none A spot where loot is placed. The item itself is rolled from the loot table.
MARKER kind (required), id and anything else Nothing by itself. Marks a spot for other code to read. See Markers.

Enemy names

These are the values enemyType accepts. Names ignore case. A name that is not listed here does not crash the level: that enemy is skipped and a warning is logged.

Enemy Names Built by
Skeleton skeleton, enemy-skeleton-hoplite NPCFactory.createSkeleton.
Ranged skeleton ranged-skeleton, rangedskeleton NPCFactory.createRangedSkeleton.
Minotaur minotaur NPCFactory.createMinotaur. Crashes when spawned: NPCs.json has no charge block for it.
Centaur centaur, enemy-centaur NPCFactory.createCentaur. Crashes when spawned: NPCs.json has no charge block for it.
Cyclops cyclops NPCFactory.createCyclops. Crashes when spawned: its ranged cooldown in NPCs.json is shorter than its weapon's wind-up.
Ghost ghost NPCFactory.createGhost. Not for use in maps, does not fit the theme.
Ghost king ghostking, ghost_king NPCFactory.createGhostKing. Does not fit the theme. Used as Level 3's boss for now, until Minotaur or Cyclops is fixed.
Traveler (NPC) npc:traveler NPCFactory.createTravelerNPC.

Use the first name listed for each. The others are kept so older maps still load.

How a symbol becomes an enemy

The map never picks an entity's sprite. It only says what goes where:

  1. The entities layer has an S at some tile.
  2. entityLegend says S is an ENEMY named skeleton.
  3. When the level loads, LevelGameArea asks EntitySpawnRegistry to build skeleton.
  4. The registry calls the factory registered for that name, NPCFactory.createSkeleton.
  5. The factory builds the enemy, including its sprite, animations, AI and health.

To change how an enemy looks or behaves, change its factory, not the map.

Adding a new enemy

  1. Write a factory method that takes the player entity and returns the new enemy, like the ones in NPCFactory.
  2. Add one line to DefaultEntitySpawns:
    EntitySpawnRegistry.registerIfAbsent("harpy", NPCFactory::createHarpy);
  3. In a map, add a symbol for it and place that symbol in the entities layer:
    "H": { "type": "ENEMY", "enemyType": "harpy" }

No map system code changes.

Markers

A marker puts a named spot on the map for another system to use: an NPC, a pet, a checkpoint, a light. The map system reads it and hands it over. It does not spawn anything.

"N": { "type": "MARKER", "kind": "npc", "id": "traveler" }
for (Marker marker : level.markers("npc")) {
    // marker.id(), marker.position(), marker.get("anyKey")
}

kind groups markers and is required. id and any other keys are optional. Positions are in tiles, so multiply by level.tileSize() for world units. No map uses markers yet.

Backdrops

The optional backdrops section defines parallax image layers rendered behind the tiles and characters.

Each key identifies a sub-level ID, or "*" to apply the backdrop to the whole map. Each value is an array of layers, ordered back to front.

"backdrops": {
  "*": [
    {
      "texture": "images/backgrounds/sky.png",
      "scroll": 0.1
    },
    {
      "texture": "images/backgrounds/mountains.png",
      "scroll": 0.3,
      "alpha": 0.8
    }
  ],
  "dungeon": [
    {
      "texture": "images/backgrounds/dungeon.png",
      "scroll": 0.0
    }
  ]
}

Layer Properties

Property Required Description
texture Yes Path to the image asset.
scroll No Horizontal parallax scrolling factor; defaults to 0.
drift No Object with optional x and y drift values; both default to 0.
spansMap No Whether the layer spans the map; defaults to false.
rows No Object with from and to tile-row bounds.
alpha No Layer opacity from 0 to 1; defaults to 1.
flicker No Optional animation settings for a strip of frames, useful for distant lightning.

Flicker Properties

The optional flicker object supports the following properties:

Property Default Description
frames 1 Number of animation frames.
fps 12 Frames per second.
minGap 3 Minimum delay between flickers.
maxGap 8 Maximum delay between flickers.
height 0.35 Relative height of the flicker strip.

Notes

  1. Every layer must specify a valid texture path.
  2. Use "*" for a whole-map backdrop, or a sub-level ID to define a backdrop for that section.
  3. Layers are drawn in array order, from back to front.
  4. The separate overlays section uses the same format but draws layers in front of tiles and characters, for effects such as rain.
  5. backgroundTexture is a separate option for a single image stretched across the entire map.

Transitions

The optional transitions section defines doorway or portal regions that move the player to another map when entered.

Each transition is a rectangular trigger area specified in tile coordinates. An optional texture can be rendered for the transition entity.

"transitions": [
  {
    "id": "exit-to-level2",
    "x": 4,
    "y": 62,
    "width": 1,
    "height": 1,
    "texture": "images/gate.png",
    "destinationMap": "maps/level2.json",
    "destinationSpawn": {
      "x": 2,
      "y": 2
    }
  }
]

Transition Properties

Property Required Description
id No Unique identifier for the transition. Defaults to transition-N if omitted.
x Yes Tile x-coordinate of the transition trigger area.
y Yes Tile y-coordinate of the transition trigger area.
width No Width of the trigger area in tiles. Defaults to 1.
height No Height of the trigger area in tiles. Defaults to 1.
texture No Texture displayed for the transition entity.
destinationMap Yes Path to the destination map file.
destinationSpawn No Spawn location within the destination map. If omitted, the destination map's default player spawn is used.

Destination Spawn Properties

Property Required Description
x Yes Destination tile x-coordinate.
y Yes Destination tile y-coordinate.

Notes

  1. destinationMap must reference a valid map JSON file.
  2. Transition coordinates use map tile coordinates, not world coordinates.
  3. Trigger regions may span multiple tiles using the width and height properties.
  4. Transition textures are optional; invisible transition zones are supported.
  5. Transitions outside the map bounds generate a warning but do not prevent the map from loading.
  6. If `destinationSpawn

Reading a level from code

Use LevelView rather than reading map data directly. It keeps working when the file format changes.

LevelView level = levelGameArea.getLevel();

level.tileAt(x, y);         // the tile and its properties, or null
level.isSolid(x, y);
level.isSupporting(x, y);   // floors, walls and one-way platforms
level.isWalkable(x, y);
level.isHazard(x, y);
level.groundTiles();        // open tiles with solid footing
level.markers("npc");

Reading outside the map returns null or false instead of throwing.

Checking a map

ShippedMapsTest loads every file in assets/maps on every push. The build fails if a map has no terrain layer, a spawn or doorway outside the map, a doorway to a map that does not exist, or a texture that is missing. Run it locally from source/:

./gradlew :core:test --tests "*ShippedMapsTest*"

Symbols by level

Generated from the map files. If you change a map's legend, update its table here.

Level 1: Out of the Underworld

File: level1-greek.json

Level Configuration

Property Value
Name Level 1 - Out of the Underworld
Tile Size 0.5
Sublevels dungeon, nether

Entities

Symbol Type Spawns / ID
P PLAYER
S ENEMY skeleton
R ENEMY ranged-skeleton
M ENEMY minotaur
Y ENEMY cyclops
C ENEMY centaur
D ENEMY cerberus
L LOOT
* MARKER npc:shop
2 MARKER npc:wizard

Tiles

Symbol Type Texture Notes
u DECORATIVE images/level1/bg-tunnel-512px.png
# WALL images/level1/cavern-rock-512px.png
V WALL images/level1/cavern-rock-veined-512px.png
^ WALL images/level1/cavern-ceiling-512px.png
_ FLOOR images/level1/cavern-floor-ash-512px.png
% FLOOR images/level1/cavern-floor-sulfur-512px.png
- PLATFORM images/level1/ledge-basalt-512px.png
= PLATFORM images/level1/ledge-bone-512px.png
~ HAZARD images/level1/river-phlegethon-512px.png Point light: lava_glow, radius 1.1, flicker 0.2
e HAZARD images/level1/hazard-embers-512px.png Point light: lava_glow, radius 0.8, flicker 0.4
W WALL images/level1/wall-limestone-512px.png
w WALL images/level1/wall-limestone-cracked-512px.png
) DECORATIVE images/level1/enemy-keres-512px.png
m WALL images/level1/wall-limestone-mossy-512px.png
F FLOOR images/level1/floor-limestone-512px.png
M FLOOR images/level1/floor-marble-512px.png
P PLATFORM images/level1/platform-marble-512px.png
p PLATFORM images/level1/platform-wood-512px.png
D DECORATIVE images/level1/frieze-meander-512px.png
X HAZARD images/level1/hazard-spikes-bronze-512px.png
L HAZARD images/level1/hazard-firepit-512px.png Point light: fire_glow, radius 1.5, flicker 0.4
O HAZARD images/level1/hazard-pit-512px.png Defined but not placed in the terrain layer
l LADDER images/level1/ladder-wood-512px.png
r LADDER images/level1/ladder-bronze-512px.png
i DECORATIVE images/level1/lift-cable-512px.png
C WALL images/level1/crust-seam-512px.png
T WALL images/level1/tunnel-wall-512px.png
t FLOOR images/level1/tunnel-floor-512px.png
s PLATFORM images/level1/tunnel-step-512px.png
@ DECORATIVE images/player/hero-hoplite-512px.png
! DECORATIVE images/level1/decor-stele-512px.png
J DECORATIVE images/level1/hero-fallen-512px.png
Y DECORATIVE images/level1/item-xiphos-512px.png
A DECORATIVE images/level1/decor-oil-lamp-512px.png Point light: oil_lamp, radius 1.5, flicker 0.1
Q DECORATIVE images/level1/decor-amphora-512px.png
U DECORATIVE images/level1/decor-urn-512px.png
K DECORATIVE images/level1/hazard-swinging-blade-512px.png
R DECORATIVE images/level1/decor-column-doric-512px.png
B DECORATIVE images/level1/decor-bone-pile-512px.png
o DECORATIVE images/level1/item-obol-512px.png
c DECORATIVE images/level1/item-chest-512px.png
d DECORATIVE images/level1/item-phial-red-512px.png
q DECORATIVE images/level1/decor-gorgoneion-512px.png
+ DECORATIVE images/level1/decor-tripod-512px.png Point light: elysian_fade, radius 1.5, flicker 0.2
k DECORATIVE images/level1/hazard-flame-jet-512px.png Point light: fire_glow, radius 1.1, flicker 0.4
: DECORATIVE images/level1/decor-chain-bronze-512px.png
z DECORATIVE images/level1/item-pouch-512px.png
N DECORATIVE images/level1/decor-olive-vine-512px.png
a DECORATIVE images/level1/item-phial-olive-512px.png
$ DECORATIVE images/level1/item-key-bronze-512px.png
& DECORATIVE images/level1/item-bolt-512px.png
H DECORATIVE images/level1/item-hoplon-512px.png
y DECORATIVE images/level1/item-phial-blue-512px.png
? DECORATIVE images/level1/decor-ember-vent-512px.png Point light: lava_glow, radius 1.1, flicker 0.4
h DECORATIVE images/level1/decor-stalagmite-512px.png
j DECORATIVE images/level1/decor-stalactite-512px.png
Z DECORATIVE images/level1/enemy-shade-512px.png
; DECORATIVE images/level1/decor-column-broken-512px.png
( DECORATIVE images/level1/enemy-cerberus-512px.png
n DECORATIVE images/level1/item-laurel-512px.png
g DECORATIVE images/level1/gate-exit-512px.png
[ DECORATIVE images/level1/lift-car-512px.png
] DECORATIVE images/level1/lift-winch-512px.png

Level 2: Mount Olympus

File: level2.json

Entities

Symbol Type Spawns
P PLAYER Player
S ENEMY skeleton
R ENEMY ranged-skeleton
M ENEMY minotaur
Y ENEMY cyclops
C ENEMY centaur
L LOOT Loot
G ENEMY medusa
1 MARKER npc:philosopher
3 MARKER npc:satyr
H ENEMY harpy
B ENEMY ranged-harpy
D ENEMY cerberus

Tiles

Symbol Type Texture Notes
O WALL none foothill-soil
E WALL none foothill-grass-edge
Q WALL none quarry-marble-cut
R WALL none quarry-rubble
g WALL none cliff-granite
S WALL none summit-marble
I WALL none summit-gold-inlay
K PLATFORM none cliff-shelf
c ONE_WAY_PLATFORM none cloud-solid
d ONE_WAY_PLATFORM none cloud-drift; defined but not placed
f ONE_WAY_PLATFORM none cloud-fade
t ONE_WAY_PLATFORM none cloud-thermal
m ONE_WAY_PLATFORM none cloud-storm
P DECORATIVE none cliff-piton
V DECORATIVE none summit-pillar-decoration
X HAZARD none rockslide

Level 3: Zeus's Palace

File: level3.json

Entities

Symbol Type Spawns
P PLAYER
Z ENEMY ghostking
L LOOT

Tiles

Symbol Type Texture Notes
# WALL summit-marble-512px.png
G WALL summit-gold-inlay-512px.png
F WALL floor-marble-512px.png
b DECORATIVE bg-sky-summit-512px.png
| DECORATIVE decor-column-doric-512px.png
q DECORATIVE decor-gorgoneion-512px.png
t DECORATIVE decor-tripod-512px.png
o DECORATIVE decor-oil-lamp-512px.png
v DECORATIVE decor-shrine-votive-512px.png
^ DECORATIVE cloud-storm-512px.png

Footnote

Click for more information on lighting format

Clone this wiki locally