Map format — map/1
A map is a grid of cells painted with terrains, in layers, plus objects: named rectangles of cells the game reads (doors, spawn points, fields). It is the ground of a 2D game seen from above, like a farm, a town or a dungeon floor. A scene names the map it stands on; the engine draws it under the entities and the game reads and paints its cells.
File: assets/maps/<name>.vmap. The map name is the file name without .vmap
(farm.vmap → farm), 1–64 characters of a-z, 0-9, _, -, starting with a letter or
digit; a folder under assets/maps/ works too, the name staying unique.
Example
{
"veduta": "map/1",
"size": [12, 8],
"terrains": [
{ "key": ".", "name": "grass", "texture": "grass", "tags": ["tillable"] },
{ "key": "~", "name": "water", "texture": "water", "tags": ["water", "solid"] },
{ "key": "s", "name": "sand", "texture": "sand" },
{ "key": "d", "name": "path", "texture": "dirt" },
{ "key": "=", "name": "soil", "texture": "soil", "tags": ["soil"] }
],
"layers": [
{ "name": "ground", "rows": [
"............",
"..ssss......",
".ss~~ss.....",
".s~~~~s.....",
".ss~~ss.....",
"..ssss......",
"............",
"............"
] },
{ "name": "paths", "rows": [
" d ",
" d ",
" d ",
" dddd",
" ",
" ",
" ",
" "
] }
],
"objects": [
{ "name": "house_door", "at": [8, 0], "tags": ["door"], "props": { "to": "house", "x": 3, "y": 7 } },
{ "name": "field", "at": [8, 5], "size": [4, 3], "tags": ["field"] }
]
}
and the scene that stands on it:
{
"veduta": "scene/1",
"camera": { "type": "orthographic", "size": 8, "position": [6, -4, 100], "look_at": [6, -4, 0] },
"map": "farm",
"entities": []
}
Every character of a row is one cell: the terrain whose key it is, or a space for an
empty cell. The rows read like the map looks.
Coordinates
A map lies in the XY plane of a 2D game, seen by a camera looking down −Z
(docs/2d.md). Cell (x, y) is x columns right and y rows down from the
top-left cell (0, 0). With origin (ox, oy, oz) and tile t, cell (x, y) covers world x
from ox + x·t to ox + (x + 1)·t and world y from oy − y·t down to oy − (y + 1)·t: the origin
is the map's top-left corner. Its center is (ox + (x + 0.5)·t, oy − (y + 0.5)·t). A layer
is drawn at z = oz + its z.
Top-level fields
| Field | Type | Default | Meaning |
|---|---|---|---|
veduta | string | required | Must be "map/1". |
size | [columns, rows] | required | 1–1024 each. |
tile | number | 1 | Meters per cell, above 0. |
origin | [x, y, z] | [0, 0, 0] | The world position of the map's top-left corner. |
terrains | array | required | 1–1024 terrains (below): what cells are painted with. |
layers | array | required | 1–16 layers (below), bottom first. |
objects | array | none | Up to 4096 objects (below). |
Terrains
| Field | Type | Default | Meaning |
|---|---|---|---|
key | string | required | The one character standing for the terrain in rows: any printable character but space, " and \ (ASCII first; é, █, あ when they run out). Unique. |
name | string | required | What the game reads and paints with (map.get, map.set); a name, unique. |
texture | string | one of the two | A texture: its cells are drawn with it, unlit, cut out where it is transparent, with nearest filtering (pixel art). |
material | string | one of the two | A material instead, for another look (lit, blended water, bilinear). |
tile | [column, row] | none | The texture is a tileset: the cell shows this tile of its grid, counted from 0. Only with texture. |
variants | integer | 1 | 1–64: the cell shows one of this many tiles, from tile on along the grid (default from [0, 0]), picked by the cell's place: always the same tile in the same cell, so grass does not repeat. |
flip | string | none | "h" mirrors the texture or tile left to right, "v" top to bottom, "hv" both: one drawn corner makes four. Only with texture; an autotile ignores it. |
tags | [string, …] | none | What the game asks about a cell (map.has(x, y, "solid")): names, no repeats. |
A cell shows the whole texture, or one frame of it: a sheet (grid or frames) shows
frame 0, or its play clip runs by itself on every cell (water, lava). See
docs/texture.md.
Layers
| Field | Type | Default | Meaning |
|---|---|---|---|
name | string | required | A name, unique: the game names layers in map.get(x, y, "paths"). |
rows | [string, …] | required | Exactly size[1] strings of exactly size[0] characters: a terrain's key, or a space for an empty cell. |
z | number | 0.1 × its index | Added to the origin's z: which layer covers which. |
layer | integer | 0 | Draw order among entities and layers, as an entity's layer (−1000 to 1000). |
ysort | boolean | false | Rows lower on the map are drawn nearer the camera, 1/1024 per meter: an entity at map.z(y) of its feet walks behind the walls and trees below it and in front of those above. See Depth. |
The first layer is usually filled (the ground); layers above it are mostly empty (paths,
flowers, a floor). A layer that must cover the entities (tree tops, roofs) needs a z above
theirs: actors at z 1 walk under a layer at "z": 2.
Objects
| Field | Type | Default | Meaning |
|---|---|---|---|
name | string | required | A name, unique among the objects. |
at | [column, row] | required | The top-left cell, on the map. |
size | [columns, rows] | [1, 1] | Cells covered, staying on the map. |
prefab | string | none | A prefab (docs/prefab.md) spawned when the map loads, its footprint (read as width × height) centered on the object, at the map's z, so a sprite taller than a cell stands on the object's lower side: entities named <object>_<entity>, with the object's tags added. They leave when map.load brings another map. |
tags | [string, …] | none | For map.objects(tag). |
props | object | none | Anything the game wants: property name → string, number or boolean (names without spaces or .). |
An object without a prefab is data: the engine draws nothing for it, the game decides
what it means (a door that loads another map, a spawn point). One with a prefab puts
its entities on the map (an NPC, a chest, a tree), and their kind reads the object with
map.object_of(e).
Tilesets
One texture can hold every tile of a place: a PNG cut by grid (docs/texture.md),
without a play clip. Terrains name their tile of it:
"terrains": [
{ "key": ".", "name": "grass", "texture": "town", "tile": [0, 0], "variants": 4 },
{ "key": "#", "name": "wall", "texture": "town", "tile": [0, 1], "tags": ["solid"] },
{ "key": "┌", "name": "roof_nw", "texture": "town", "tile": [1, 1], "tags": ["solid"] },
{ "key": "┐", "name": "roof_ne", "texture": "town", "tile": [1, 1], "flip": "h", "tags": ["solid"] }
]
The tiles of a set are not animated and have no edge (the texture's play clip and edge are ignored); a terrain without tile and
variants still shows a texture whole, with its frames, clips and borders. veduta build
warns about a tile outside the grid.
Depth
A layer is flat: everything on it is under, or over, the entities. With "ysort": true
each row is a little nearer the camera than the row above, and map.z(y) gives the z of
something standing at world y, so set an entity's z from its feet every tick:
e.z = map.z(e.y - 0.5) -- a sprite 1 meter high: its feet are half a meter under its center
The hero then walks in front of the wall above them and behind the wall below. What is
taller than a cell and must hide the hero as a whole (a tree, a house) is better a prefab
of an object, drawn as one entity with its own map.z.
Edges
A terrain whose texture has an edge (docs/texture.md) draws a
border over its lower neighbours: water spills over the sand around it, sand over grass,
with a wandering line instead of square cells, and no transition tiles to draw. For every
cell, each neighbour (sides and diagonals) of higher priority draws its border in the
quarters of the cell it touches: a band along a side, both bands at an inner corner, a
rounded corner when it touches only the diagonal. Borders of higher priority are drawn
over lower ones.
In a layer above the first, an empty cell counts as lower than any terrain: a path on the
paths layer gets a border over the ground below, and paths drawn one cell wide look
like paths, not rows of squares. A terrain without an edge has square cells.
Autotiles
A terrain whose texture is an autotile (docs/texture.md) is drawn with the tiles of its image, picked by each cell's 8 neighbours: its borders against anything else on its layer (another terrain, an empty cell) are the ones drawn by hand, an island's corners outside, a lake's inside. Put a terrain like a cliff or a hedge on a layer of its own above the ground, with the transparent parts of its tiles showing the ground below. A neighbour off the map counts as the terrain itself.
In the game
In Lua (docs/lua.md):
kinds.hero = {
update = function(e)
local x, y = map.cell(e.x, e.y) -- the cell under the hero
local _, dy = input.dpad()
if dy > 0 and not map.has(x, y - 1, "solid") then
e.y = e.y + 4 * engine.dt -- water or a fence above stops it
end
if input.pressed("a") and map.get(x, y) == "grass" then
map.set(x, y, "soil") -- tilled: drawn at once, borders and all
end
for _, door in ipairs(map.objects("door")) do
if door.x == x and door.y == y then map.load(door.props.to) end
end
end,
}
Moving among walls is map.move(e, dx, dy): the entity's box stops at the cells tagged
solid and slides along them. map.path finds the way around them, map.sight tells
whether two cells see each other, map.find lists the cells of a terrain or a tag.
map.data and map.set_data keep values in cells (a crop's age).
map.get, map.set and map.tags name a layer as their last argument (default: the
first for get and set, every layer for has and tags). map.load(name) replaces
the scene's map, as its file describes it, and keeps the entities: one scene can walk
from the farm to the town. In Go: ctx.Map() (package tilemap) and ctx.LoadMap(name).
Changes last until the scene or the map is loaded again, which starts from the file. Keep
what the player changed (tilled soil, planted seeds) in a save and paint it back after
loading: save.write("farm", map.changes()), then map.apply(save.read("farm")). Every change is a map_set event in the trace (layer, x, y, terrain), and
map.load a map_load event; snapshots keep the cells.
Drawing
A layer is drawn in chunks of 16 × 16 cells: a chunk out of view is not drawn, and painting a cell rebuilds only its chunk (and the chunk next to it when its border reaches there). A cell is 2 triangles; each quarter with a border 2 more. At a camera 12 cells high, about 4 chunks are in view.
Checks
Decoding is strict (unknown fields, wrong types and duplicate keys are errors), and every
problem is reported at once with its file, line and column: size in range; keys one
allowed character and unique; terrain, layer and object names valid and unique; a
terrain with exactly one of texture and material; rows of the right count and length,
each character a key or a space; tile, variants and flip in range and with a
texture; objects on the map; props strings, numbers or booleans.
veduta build also warns when a terrain's texture or material, or a scene's map, is not
found.
Limits
| Item | Range |
|---|---|
size | 1–1024 columns and rows |
| terrains | 1–1024 |
| layers | 1–16 |
| objects | up to 4096 |