⚠ Archived V5-and-below docs. These pages cover the retired APIs and the earlier releases. They are frozen. The current docs live at the site root; the V6 map guides start at Level Design Overview.
HexLayout (2D, retired)
The HexGrid and HexLayout modules are obsolete compatibility surfaces.
They still compile and delegate to the unified grid, but new code uses
CellGrid2D with hex geometry plus Flow.
This page keeps the retired reference for existing levels.
Obsolete surface.
HexGrid.createand everyHexLayout.*call emit deprecation warnings. Author new maps with Code-first maps and the Hex Grids page.
The retired pipelines
The HexLayout module (sections, per-op painting) still compiles and works
exactly as before — the square Layout ops also run on hex storage for
pixel-perfect control:
// obsolete surface, kept for reference
let grid =
CellGrid2D.createHex {
Orientation = HexOrientation.PointyTop
Width = 30
Height = 20
Radius = 32f
Origin = Vector2.Zero
}
|> HexLayout.run (fun section ->
section
|> HexLayout.fill 0 0 30 20 GrassTile
|> HexLayout.border 0 0 30 20 WaterTile
|> HexLayout.set 15 10 CastleTile
)
Sections: relative positioning
Sections let you work in local coordinates. A section at (5, 3) means "start from column 5, row 3 in the parent":
section
|> HexLayout.section 5 3 (fun inner ->
// (0, 0) here = (5, 3) in parent
inner |> HexLayout.fill 0 0 4 3 ForestTile
)
|> HexLayout.section 12 8 (fun inner ->
// (0, 0) here = (12, 8) in parent
inner |> HexLayout.fill 0 0 3 3 MountainTile
)
Sections are zero-copy: they don't allocate new grids. They're windows into the same backing data.
Structural helpers
// Shrink section by N on all sides (useful for borders)
section |> HexLayout.padding 2 (fun inner ->
inner |> HexLayout.fill 0 0 8 6 GrassTile // 8x6 inside a 12x10 section
)
// Explicit padding per side: left, top, right, bottom
section |> HexLayout.paddingEx 1 2 1 2 (fun inner -> ...)
// Center a fixed-size block within the section
section |> HexLayout.center 4 4 (fun inner ->
inner |> HexLayout.set 2 2 ThroneTile // Centered in parent
)
// Place stamps in a row with spacing
section |> HexLayout.flowX 5 [
tower 3 5
tower 3 5
tower 3 5
]
// Place stamps in a column with spacing
section |> HexLayout.flowY 4 [
platform 6
platform 6
platform 6
]
Primitives: placing content
Single cells and lines
// Single cell
HexLayout.set col row content section
// Horizontal line
HexLayout.repeatX col row count content section
// Vertical line
HexLayout.repeatY col row count content section
Filled regions
// Fill a rectangle
HexLayout.fill col row width height content section
// Hollow rectangle (border only)
HexLayout.border col row width height content section
// Filled rectangle with different border
HexLayout.rect col row width height borderContent fillContent section
// Only the four corners
HexLayout.corners col row width height content section
// Clear a region
HexLayout.clear col row width height section
Conditional placement
// Only set if cell is empty (won't overwrite existing content)
HexLayout.setIfEmpty col row content section
Geometry: lines, circles, polygons
These work in grid coordinates, not world space. They're useful for marking paths, areas of effect, or procedural shapes.
// Line between two hexes (Bresenham's algorithm)
HexLayout.line 2 3 18 12 PathTile section
// Circle (midpoint algorithm)
HexLayout.circle 10 8 5 true AreaTile section // Filled circle
HexLayout.circle 10 8 5 false BorderTile section // Ring only
// Arbitrary polygon (vertex list in grid coordinates)
let vertices = [| struct(5, 2); struct(12, 2); struct(15, 8); struct(8, 10); struct(2, 6) |]
HexLayout.polygon vertices true ZoneTile section // Filled
HexLayout.polygon vertices false EdgeTile section // Outline only
Patterns: decorative and procedural
Checkerboard
// Full checkerboard pattern
HexLayout.checker LightGrass DarkGrass section
// Checkerboard on border only
HexLayout.checkerBorder col row width height LightStone DarkStone section
Random scatter
// Scatter N random tiles (same seed = same pattern every time)
HexLayout.scatter 50 42 FlowerTile section
// Scatter on border only
HexLayout.scatterBorder col row width height 10 42 RockTile section
// Scatter along a line
HexLayout.scatterLine 2 3 18 12 8 42 TreeTile section
// Scatter an entire stamp at random positions
HexLayout.scatterStamp 5 42 (fun s ->
s |> HexLayout.fill 0 0 2 2 BushTile
) section
Procedural generation
// Generate content from a function
HexLayout.generate col row width height (fun c r ->
if (c + r) % 3 = 0 then DenseForest else SparseForest
) section
Find and replace
// Replace all instances of one content type
HexLayout.replace GrassTile SnowTile section
// Probabilistic replace (30% chance per cell)
HexLayout.replaceScatter GrassTile SnowTile 0.3f 42 section
Iteration and transformation
// Read existing content without modifying
HexLayout.iter col row width height (fun c r tile ->
match tile with
| ValueSome t -> printfn "Found %A at (%d, %d)" t c r
| ValueNone -> ()
) section
// Transform existing content
HexLayout.map col row width height (fun tile ->
match tile with
| Tree age -> Tree(age + 1)
| other -> other
) section
Building stamps: reusable components
A stamp is a function HexGridSection<'T> -> HexGridSection<'T>. Build your level design vocabulary by creating stamps for common patterns.
Simple stamp
/// A small campfire clearing
let campfire (section: HexGridSection<Tile>) =
section
|> HexLayout.fill 0 0 3 3 GrassTile
|> HexLayout.set 1 1 CampfireTile
|> HexLayout.set 0 1 LogTile
|> HexLayout.set 2 1 LogTile
Parameterized stamp
/// A configurable watchtower
let stackMidTiles height midTile (section: HexGridSection<Tile>) =
// layers 1..height-2 form the shaft
(section, [ 1 .. height - 2 ])
||> List.fold (fun s i -> s |> HexLayout.set 1 i midTile)
let watchtower height baseTile midTile topTile (section: HexGridSection<Tile>) =
section
|> HexLayout.set 1 0 baseTile
|> stackMidTiles height midTile
|> HexLayout.set 1 (height - 1) topTile
Composing stamps
Stamps compose with >> (function composition):
let outpost =
watchtower 6 StoneBase StoneMid TorchTop
>> HexLayout.section 0 7 campfire
>> HexLayout.border 0 0 4 8 FenceTile
// note: negative coordinates are clipped, so a border starting at -1
// would silently drop its top and left edges
Domain modules
Organize stamps into namespaces for your game:
module Fantasy =
module Structures =
let house w h = ...
let tavern = house 8 6 >> interior ...
let castle w h = ...
module Nature =
let forestCluster count = ...
let river width = ...
let mountain radius = ...
module Combat =
let coverWall length = ...
let trench width depth = ...
let barricade = ...
Layered composition
Obsolete surface.
LayeredHexGridandLayeredHexLayoutare retired. A layered grid is a dictionary of grids, and game code can own the dictionary:
open System.Collections.Generic
let layers = Dictionary<int, CellGrid2D<Tile>>()
let hexLayer index paint =
let grid =
match Dictionary.tryGetValue index layers with
| ValueSome grid -> grid
| ValueNone ->
let grid =
CellGrid2D.createHex {
Orientation = HexOrientation.PointyTop
Width = 20
Height = 15
Radius = 32f
Origin = Vector2.Zero
}
layers.[index] <- grid
grid
Layout.run paint grid
hexLayer 0 (fun s -> s |> Layout.fill 0 0 20 15 GrassTile) |> ignore
Complete example: strategy map
This example runs on the obsolete compatibility surface.
HexGrid.createand everyHexLayout.*call below emit deprecation warnings. It remains as a porting reference.
type TerrainType =
| Grass | Forest | Mountain | Water | Sand | Road
// Named stamps: each region is a reusable function
let baseTerrain = HexLayout.fill 0 0 30 20 GrassTile
let mountainRange (s: HexGridSection<TerrainType>) =
s |> HexLayout.fill 0 0 8 3 MountainTile
|> HexLayout.scatter 5 42 ForestTile
let river = HexLayout.line 0 10 29 15 WaterTile
let forestCluster seed (s: HexGridSection<TerrainType>) =
s |> HexLayout.scatter 15 seed ForestTile
let road a b c d = HexLayout.line a b c d RoadTile
let settlement x y w h tile =
HexLayout.section x y (HexLayout.fill 0 0 w h tile)
let castle x y =
HexLayout.section x y (
HexLayout.fill 0 0 2 2 CastleTile
>> HexLayout.border 0 0 4 4 WallTile
)
let desert (s: HexGridSection<TerrainType>) =
s |> HexLayout.fill 0 0 6 4 SandTile
|> HexLayout.scatter 3 301 CactusTile
let strategyMap =
HexGrid.create 30 20 32f Vector2.Zero FlatTop
|> HexLayout.run (fun section ->
section
|> baseTerrain
|> HexLayout.section 10 5 (mountainRange)
|> river
|> HexLayout.section 2 2 (forestCluster 101)
|> HexLayout.section 20 12 (forestCluster 202)
|> road 5 3 15 10
|> road 15 10 25 17
|> castle 5 3
|> settlement 15 10 3 2 TownTile
|> settlement 25 17 2 2 VillageTile
|> HexLayout.section 22 2 desert
)
module List from Microsoft.FSharp.Collections
--------------------
type List<'T> = | op_Nil | op_ColonColon of Head: 'T * Tail: 'T list interface IReadOnlyList<'T> interface IReadOnlyCollection<'T> interface IEnumerable interface IEnumerable<'T> member GetReverseIndex: rank: int * offset: int -> int member GetSlice: startIndex: int option * endIndex: int option -> 'T list static member Cons: head: 'T * tail: 'T list -> 'T list member Head: 'T member IsEmpty: bool member Item: index: int -> 'T with get ...
val int: value: 'T -> int (requires member op_Explicit)
--------------------
type int = int32
--------------------
type int<'Measure> = int
Mibo