Custom Tiles & Maps¶
Beyond generating and loading maps, PGTG lets you replace the content of tiles: the wall layout of each road piece, where obstacles can be generated, and how traffic flows.
The three tile data files¶
Each is an optional path passed to the constructor. When omitted, PGTG uses its packaged defaults.
| Constructor argument | Describes | Default file |
|---|---|---|
tiles |
The wall/exit layout of every tile shape. | pgtg/data/tiles/tiles.json |
obstacle_masks |
Named cell patterns used to stamp obstacles onto a tile. | pgtg/data/tiles/obstacle_masks.json |
traffic_lanes |
The lanes and spawn points cars use in each tile shape. | pgtg/data/tiles/traffic_lanes.json |
from pgtg import PGTGEnv
env = PGTGEnv(
tiles="my_tiles.json",
obstacle_masks="my_obstacle_masks.json",
traffic_lanes="my_traffic_lanes.json",
)
Start from the defaults
The most reliable way to author these files is to copy the packaged defaults and edit them.
They live in pgtg/data/tiles/ and document the exact expected structure.
Both JSON and YAML are accepted.
Edit them visually
Writing these grids by hand is fiddly. The Tile Editor (WIP) is a companion visual tool that paints walls and exits, draws traffic routes by dragging, and validates the result before an environment loads it.
Tile dimensions must be consistent
Tile size is inferred from the provided files (the packaged defaults are 9×9 cells).
All three datasets in use must share the same dimensions, so passing tiles with
non-default dimensions requires matching obstacle_masks and traffic_lanes files.
PGTG validates this at construction time.
Changing the tile size¶
Tile size isn't a separate setting but is instead inferred from the grids in the files you pass.
To use a different size (bigger, smaller, or non-square), write tiles, obstacle_masks, and
traffic_lanes files whose grids all share the new width and height.
PGTG then picks it up automatically, and every dimension derived from it
(the map observation planes, the position bounds, rendering, …) follows along.
This is the same mechanism whether you're tweaking one shape or replacing the entire
tile set.
The example below shrinks the default 9×9 tiles down to a 5×5 east-west straight tile (exit tuple 0,1,0,1).
{
"0,1,0,1": [
[["wall"], ["exit west"], ["exit west"], ["exit west"], ["wall"]],
[["wall"], [], [], [], ["wall"]],
[["wall"], [], [], [], ["wall"]],
[["wall"], [], [], [], ["wall"]],
[["wall"], ["exit east"], ["exit east"], ["exit east"], ["wall"]]
]
}
obstacle_masks.json and traffic_lanes.json must use matching 5×5 grids (an empty
5×5 grid works for both if this tile has no obstacles or traffic lanes):
{
"blob": [
[[], [], [], [], []],
[[], [], [], [], []],
[[], [], [], [], []],
[[], [], [], [], []],
[[], [], [], [], []]
]
}
from pgtg import PGTGEnv
env = PGTGEnv(
map_path="path/to/a_map_using_only_0_1_0_1_tiles.json",
tiles="tiles.json",
obstacle_masks="obstacle_masks.json",
traffic_lanes="traffic_lanes.json",
)
env.tile_width, env.tile_height # (5, 5)
Cover every tile shape your maps use
A real map needs more than one exit tuple.
Build out the full set of tile shapes, not just the single shape shown here,
and keep obstacle_masks covering every mask name your maps or procedural
generation reference.
tiles¶
Keys are exit tuples written as a comma-joined string "N,E,S,W" (each 0/1); values are
a W×H grid of cells (9×9 in the packaged defaults), where each cell is a list of feature
strings. The important markers are "wall" for impassable cells and "exit north" /
"exit east" / "exit south" / "exit west" for the cells that connect to a neighboring
tile; an empty list is open road.
{
"1,0,0,0": [
[
["wall"],
["wall"],
["wall"],
["wall"],
["wall"],
["wall"],
["wall"],
["wall"],
["wall"]
],
[
"... one list per column; the north exit sits at the START of the middle columns ..."
],
[["exit north"], [], [], [], [], [], ["wall"], ["wall"], ["wall"]]
],
"0,1,0,0": ["..."]
}
obstacle_masks¶
Keys are mask names; values are a W×H grid where each cell is ["obstacle"] (part of the
mask) or [] (not). Masks decide which cells of a tile an obstacle covers. The built-in
mask names are blob, small_blob, chess_field, reverse_chess_field, top_half,
bottom_half, left_half, and right_half (plus directional traffic_light_* masks). A
map file can reference a mask by name via a tile's "obstacle_mask" field. Randomly
generated maps with obstacles pick from the built-in names, so a custom masks file used
with them must define those names too.
traffic_lanes¶
Keys are exit tuples (as in tiles); values are a W×H grid where each cell may contain a
lane marker such as "car_lane north south" (a lane with a flow direction and orientation)
and/or "car_spawner" (a cell where new cars can appear). Empty cells carry no lane. The
file must cover every exit tuple the map uses.
Authoring obstacles inside a map file¶
Fixed map files can pin obstacles to specific tiles by adding two fields to a tile entry:
obstacle_type—"ice","sand","broken_road","traffic_light", or the registerednameof a custom obstacle.obstacle_mask— a mask name from yourobstacle_masksfile.
This allows building fully deterministic evaluation scenarios.