Map authoring by blueprint image + schematic inspection
A hands-on alternative to writing per-map Python generators. You paint a top-down blueprint (or generate one as art); the toolkit converts it to a playable, validated map and lets you see the layout instantly without a 2-minute Unity capture.
The three tools (unity/tools/maptools/)
| Tool | What it does | Speed |
|---|---|---|
map_schematic.py <map.json> |
Renders any map's terrain grid as a colour PNG with spawn/resource/prop markers — the fast inspection loop | <1s, no Unity |
image_to_map.py <blueprint.png> --id … --name … --biome … |
Converts a painted blueprint → validated map JSON (BFS connectivity, symmetry, in-bounds) | <1s |
terrain_tiers.py <map.json> |
Builds a coherent elevation heightfield — lower plains → rolling hills → high ground → towering mountains, biased toward the rim, with graded slopes. BFS-proven walkable network | <1s |
terrain_surface.py <map.json> |
Paints realistic surface materials onto that elevation — grass plains → dirt/scree hills → rock cliffs → biome caps (snow / scorched / bare rock). Connectivity-safe by construction | <1s |
dress_map.py <map.json> |
Scatters biome-appropriate props/decor (trees, rocks, tufts, wrecks) with symmetry | <1s |
stamps.py |
Pre-QA'd region stamps (forest, settlement) placed by a single seed pixel | — |
map_palette.py |
The shared terrain↔colour key (below) | — |
Blueprint colour key (paint terrain with these)
Paint 1 pixel = 1 map cell. Nearest-colour match on import, so approximate hues are fine.
| Paint | Terrain | Notes |
|---|---|---|
green #568C3E |
grass | |
tan #D6C08A |
sand | desert base |
brown #966E42 |
dirt | |
dark grey #5A544E |
rock | IMPASSABLE — chokepoint walls |
blue #305C9E |
water | IMPASSABLE deep water |
light blue #6EA0C8 |
shallow ford | passable water crossing |
plank brown #967846 |
bridge | over water |
dark asphalt #46464A |
paved_road | |
mid grey #78746C |
road | dirt track |
charcoal #3C3630 |
scorched | battle scar |
pale cyan #C8E0EC |
ice | snow maps |
Pin dots (drop these bright colours as markers)
Reserved saturated hues NOT used by terrain — drop a small dot; the cell under it keeps the base terrain. Multi-pixel dots are auto-clustered to one pin (centroid).
| Pin | Meaning |
|---|---|
pure red #FF0000 |
player spawn (team = paint order) |
magenta #FF00FF |
ore node |
orange #FF8000 |
oil derrick (contested economy) |
yellow #FFFF00 |
strategic point (tech) |
cyan #00FFFF |
strategic point (radar) |
bright green #00FF00 |
forest region stamp seed |
violet #8000FF |
settlement region stamp seed |
Workflow
# 1. paint/generate blueprint.png (left half only if using --mirror)
# 2. convert -> validated map
python3 unity/tools/maptools/image_to_map.py blueprint.png \
--id my_map --name "My Map" --biome desert --mirror
# 3. inspect the layout instantly
python3 unity/tools/maptools/map_schematic.py \
unity/Assets/StreamingAssets/data/maps/my_map.json
# 4. redraw blueprint, repeat — or capture in-engine when the layout reads right
--mirror paints only the left half and generates the right by 180° rotation, so the map is
guaranteed competitively symmetric — the painter can't accidentally make it unfair.
Terrain relief & realism pipeline (mountains · hills · plains · snow · lava)
A flat blueprint gives you the layout; this pass gives it realistic 3D landscape. Run the two elevation tools after the layout reads right — they are deterministic (seeded from the map id), symmetry-aware, and each ends with a BFS reachability gate that hard-fails rather than ship a map with a severed spawn.
M=unity/Assets/StreamingAssets/data/maps/my_map.json
# 1. RELIEF — raise coherent plains→hills→mountains (rim-biased so bases sit in the valley).
# --clamp 3.2 keeps ridges high-but-passable for open / many-spawn maps (steppe, 32-base basins).
python3 unity/tools/maptools/terrain_tiers.py "$M" # add --clamp 3.2 for open maps
# 2. SURFACE — repaint the ground by elevation+slope so mountains stop rendering as green grass.
# Biome is auto-read from the map's `biome` field (snow→snow caps, volcanic→scorched, else rock).
python3 unity/tools/maptools/terrain_surface.py "$M" # or --biome snow|volcanic|desert
# 3. DECOR — scatter trees/rocks/tufts on the new terrain, then inspect.
python3 unity/tools/maptools/dress_map.py "$M" --mirror auto
python3 unity/tools/maptools/map_schematic.py "$M"
Why two passes. terrain_tiers.py only moves the ground up; it never re-textures it, so a
peak it raised still renders as green grass. Across the shipped set this left tens of thousands
of impassable mountain cells painted grass (a glacier map with green peaks, a volcano with green
slopes). terrain_surface.py closes that gap by banding the surface:
| Elevation | Temperate | Snow biome | Volcanic |
|---|---|---|---|
| plains (low) | (left as authored) | (left as authored) | (left as authored) |
| rolling hills / high walkable | grass + dirt scree on steep swells | same | same |
| mountain cliffs (> 3.5 walk cap) | rock | rock on steep faces | rock |
| summits | snow on tall peaks only (≥ 6.5) | snow blankets the high ground | scorched charcoal |
Connectivity is provably preserved: an impassable char (rock) is only ever written to a cell
whose elevation is already above the 3.5 walk cap, and every walkable cell only receives passable
ground. Roads / water / rail / bridges / concrete / ore are never touched. The pass prints a
SURFACE {…} line with the change histogram and an errors list that must be empty.
Why this beats a blind generator
- You author with your eyes. Layout is a picture you paint, not terrain math you can't see.
- The schematic is the inner loop. A dark wadi, a broken lane, a resource on water — all visible in <1s, not hidden behind a far 3D overview.
- Validation is kept as a gate. BFS connectivity, symmetry, in-bounds, determinism still run on every import — the fairness/lockstep guarantees the generators gave, unchanged.
- Stamps compose from verified pieces. A forest/settlement is designed + QA'd once, then reused by dropping a seed pixel.
Proven end-to-end: a painted blueprint → validated JSON → loads in-engine (connectivity 0 warnings, deterministic checksum match).