Veduta

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.vmapfarm), 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

FieldTypeDefaultMeaning
vedutastringrequiredMust be "map/1".
size[columns, rows]required1–1024 each.
tilenumber1Meters per cell, above 0.
origin[x, y, z][0, 0, 0]The world position of the map's top-left corner.
terrainsarrayrequired1–1024 terrains (below): what cells are painted with.
layersarrayrequired1–16 layers (below), bottom first.
objectsarraynoneUp to 4096 objects (below).

Terrains

FieldTypeDefaultMeaning
keystringrequiredThe one character standing for the terrain in rows: any printable character but space, " and \ (ASCII first; é, , when they run out). Unique.
namestringrequiredWhat the game reads and paints with (map.get, map.set); a name, unique.
texturestringone of the twoA texture: its cells are drawn with it, unlit, cut out where it is transparent, with nearest filtering (pixel art).
materialstringone of the twoA material instead, for another look (lit, blended water, bilinear).
tile[column, row]noneThe texture is a tileset: the cell shows this tile of its grid, counted from 0. Only with texture.
variantsinteger11–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.
flipstringnone"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, …]noneWhat 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

FieldTypeDefaultMeaning
namestringrequiredA name, unique: the game names layers in map.get(x, y, "paths").
rows[string, …]requiredExactly size[1] strings of exactly size[0] characters: a terrain's key, or a space for an empty cell.
znumber0.1 × its indexAdded to the origin's z: which layer covers which.
layerinteger0Draw order among entities and layers, as an entity's layer (−1000 to 1000).
ysortbooleanfalseRows 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

FieldTypeDefaultMeaning
namestringrequiredA name, unique among the objects.
at[column, row]requiredThe top-left cell, on the map.
size[columns, rows][1, 1]Cells covered, staying on the map.
prefabstringnoneA 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, …]noneFor map.objects(tag).
propsobjectnoneAnything 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

ItemRange
size1–1024 columns and rows
terrains1–1024
layers1–16
objectsup to 4096