Header menu logo Mibo

Level Design

Mibo provides grid-based layout engines for designing game levels programmatically. The system is content-agnostic and works with any tile/entity type you define.

Core Philosophy

2D vs 3D Layout

Mibo provides separate layout engines for 2D and 3D games:

Feature

2D Layout

3D Layout

Module

Mibo.Layout

Mibo.Layout3D

Dimensions

X, Y

X, Y, Z

Storage

CellGrid2D<'T>

CellGrid3D<'T>

Cursor

GridSection2D<'T>

GridSection3D<'T>

World Space

Vector2

Vector3

Common Patterns

Both engines share the same design patterns:

Stamps

A stamp is a function that transforms a section:

// 2D stamp
type Stamp2D<'T> = GridSection2D<'T> -> GridSection2D<'T>

// 3D stamp
type Stamp3D<'T> = GridSection3D<'T> -> GridSection3D<'T>

Stamps compose with >> (function composition):

let myStructure =
    room 10 8 floor wall
    >> center 2 2 (treasureChest)
    >> section 8 4 (torchStand)

Scoping

Create nested sections for relative positioning:

// 2D
section |> Layout.section 5 3 (fun inner ->
    // (0, 0) maps to (5, 3) in parent
    inner |> fill 0 0 4 4 content
)

// 3D
section |> Layout3D.section 5 3 0 (fun inner ->
    // (0, 0, 0) maps to (5, 3, 0) in parent
    inner |> fill 0 0 0 4 4 4 content
)

DSL Pipeline

Operations return the section for fluent chaining:

// 2D
let grid =
    CellGrid2D.create 100 50 cellSize origin
    |> Layout.run (fun section ->
        section
        |> fill 0 0 100 50 floor
        |> border 0 0 100 50 wall
        |> set 50 25 chest
    )

// 3D
let grid =
    CellGrid3D.create 100 50 50 cellSize origin
    |> Layout3D.run (fun section ->
        section
        |> fill 0 0 0 100 50 50 floor
        |> shell 0 0 0 100 50 50 wall
        |> set 50 25 25 chest
    )

Content Types

Grids are generic - you define what each cell contains:

// 2D example
type Tile = 
    | Floor of TileType
    | Wall of WallType
    | Prop of PropType
    | Spawn of EntityType

let myGrid = CellGrid2D.create 100 50 cellSize origin

// 3D example
type Cell =
    | Block of BlockType
    | Entity of EntityType
    | SpawnPoint of SpawnType
    | Trigger of TriggerInfo

let my3DGrid = CellGrid3D.create 100 50 50 cellSize origin

World Position Conversion

Convert grid coordinates to world space for rendering:

// 2D
let worldPos = CellGrid2D.getWorldPos x y grid  // Vector2

// 3D
let worldPos = CellGrid3D.getWorldPos x y z grid  // Vector3

Performance

Both engines use zero-cost abstractions:

Iteration

Iterate over populated cells for rendering:

// 2D
grid |> CellGrid2D.iter (fun x y tile ->
    let worldPos = CellGrid2D.getWorldPos x y grid
    renderTile worldPos tile
)

// 3D
grid |> CellGrid3D.iter (fun x y z content ->
    let worldPos = CellGrid3D.getWorldPos x y z grid
    spawnModel worldPos content
)

Domain Modules

Mibo includes pre-built stamps for common game types:

2D Games

3D Games

Choosing Between 2D and 3D

Use 2D Layout for: - Side-scrolling platformers - Top-down RPGs and roguelikes - Isometric games (3D projection, 2D layout) - Tile-based puzzle games

Use 3D Layout for: - First-person shooters - Dungeon crawlers - Outdoor exploration games - Voxel-based games

Getting Started

type Stamp2D<'T> = obj -> obj
'T
type Stamp3D<'T> = obj -> obj
val myStructure: (obj -> obj)
val floor: value: 'T -> 'T (requires member Floor)
val grid: obj
val set: elements: 'T seq -> Set<'T> (requires comparison)
type Tile = | Floor of obj | Wall of obj | Prop of obj | Spawn of obj
val myGrid: obj
type Cell = | Block of obj | Entity of obj | SpawnPoint of obj | Trigger of obj
val my3DGrid: obj
val worldPos: obj

Type something to start searching.