Phaser AE

Level generators

Free packv3.0.0-42DLogic

Mazes, roguelike dungeons and caves, generated from a seed. Each comes with the questions a game asks of its level (where can I move, what is the route, which room is the boss's) and exports a grid of floor and wall tiles ready for a tilemap.

This is a Free pack. Every phaser.io account can use it in their games.

Released October 14, 2026 · updated October 20, 2026

Level generators: a screenshot of its demo
A note about these graphics

Expansion packs don't come with graphics. This screenshot is one way of drawing what the pack does, made for its demo: none of the sprites, tiles or backgrounds in it are in the pack.

That's by design. A pack is a game's logic, mechanics and effects, and it works with whatever art you give it. Some packs draw effects, backdrops or 3D scenes of their own, but none carries a game's sprites.

So the look is yours. Make the art on phaser.io, bring your own, or let your agent draw it in code: the same pack can look like anything.

The pack

levelgen

function levelgen(): Pack<{ levelgen: LevelgenApi }>

The level generators pack: Maze (ten algorithms, braid, rooms, exits, queries), Dungeon (rooms, corridors, doors, tags), Cave (cellular automata, largest region, farthest pair). Headless; each returns a thick 0/1 tile grid for tiles-2d.

Classes

Cave

class Cave

A generated cave: the tile grid plus the queries a game needs. Construct once (generation runs in the constructor); read from it every frame.

const cave = new Cave({ cols: 80, rows: 50, seed: 'cavern-1', fillProb: 0.46 });
const grid = cave.tiles();                    // 0 floor / 1 wall → CollisionGrid
const [entrance, exit] = cave.farthestPair(); // two far-apart floor cells
  • readonly cols: number — Grid width in tiles.
  • readonly rows: number — Grid height in tiles.
  • readonly seed: number — The numeric seed actually used.
  • readonly grid: Uint8Array — Per-tile data, row-major: 1 wall, 0 floor. Prefer isWall / tiles, but exposed for custom exporters.
  • constructor(opts: CaveOptions)
  • index(col: number, row: number): number — Row-major tile index for (col, row). No bounds check.
  • inBounds(col: number, row: number): boolean — Is (col, row) inside the grid?
  • isWall(col: number, row: number): boolean — Is the tile solid rock? Out-of-bounds reads as wall.
  • isFloor(col: number, row: number): boolean — Is the tile open (walkable) cavern floor?
  • cellAt(x: number, y: number, cellSize = 1, originX = 0, originY = 0): Cell | null — Which cell contains world/pixel position (x, y) at cellSize units per tile from (originX, originY). null when outside the grid.
  • get openCount(): number — Count of open floor tiles.
  • floors(): Cell[] — Every open floor cell — for scattering spawns / ore / pickups.
  • farthestPair(): [Cell, Cell] | [] — Two far-apart floor cells (an approximate cave "diameter"), good for an entrance/exit pair. Returns [] if there's no floor.
  • path(from: Cell, to: Cell): Cell[] — Shortest walkable path from from to to (4-dir BFS through floor), inclusive; [] if unreachable.
  • tiles(opts: CaveTileOptions = {}): number[][] — The cave as a THICK 0/1 tile grid (grid[y][x]), ready for a Tilemap / CollisionGrid / wall-box build.
  • toString(): string — ASCII rendering (# wall, space floor) — tests + console.

Dungeon

class Dungeon

A generated dungeon: the tile grid plus room/door metadata and the queries a game needs. Construct once (generation runs in the constructor); read from it every frame.

const dungeon = new Dungeon({ cols: 60, rows: 40, seed: 'level-1', extraConnections: 3 });
const grid = dungeon.tiles();                        // 0 floor / 1 wall → CollisionGrid
const start = dungeon.roomsByTag('start')[0];        // where to drop the player
const boss = dungeon.roomsByTag('boss')[0];          // the objective room
  • readonly cols: number — Grid width in tiles.
  • readonly rows: number — Grid height in tiles.
  • readonly seed: number — The numeric seed actually used.
  • readonly grid: Uint8Array — Per-tile data, row-major: 1 wall, 0 floor, 2 door. Prefer isWall / tiles, but exposed for custom exporters.
  • readonly rooms: DungeonRoom[] — Every placed room.
  • readonly doors: Cell[] — Detected doorway tiles (1-wide thresholds into rooms).
  • readonly connections: Array<[number, number]> — Room-graph edges as [roomId, roomId] pairs (which rooms are joined).
  • constructor(opts: DungeonOptions)
  • index(col: number, row: number): number — Row-major tile index for (col, row). No bounds check.
  • inBounds(col: number, row: number): boolean — Is (col, row) inside the grid?
  • isWall(col: number, row: number): boolean — Is the tile solid wall? Out-of-bounds reads as wall.
  • isFloor(col: number, row: number): boolean — Is the tile walkable (floor or door)?
  • cellAt(x: number, y: number, cellSize = 1, originX = 0, originY = 0): Cell | null — Which cell contains world/pixel position (x, y) at cellSize units per tile from (originX, originY). null when outside the grid.
  • roomAt(col: number, row: number): DungeonRoom | null — The room whose rectangle contains (col, row), or null (a corridor / wall).
  • roomsByTag(tag: RoomTag): DungeonRoom[] — All rooms carrying a given RoomTag.
  • floors(): Cell[] — Every walkable (floor or door) cell — for scattering pickups / spawns.
  • path(from: Cell, to: Cell): Cell[] — Shortest walkable path from from to to (4-dir BFS through floor + doors), inclusive; [] if unreachable.
  • tiles(opts: DungeonTileOptions = {}): number[][] — The dungeon as a THICK 0/1 tile grid (grid[y][x]), ready for a Tilemap / CollisionGrid / wall-box build. Doors default to the floor value.
  • toString(): string — ASCII rendering (# wall, + door, . floor) — tests + console.

Maze

class Maze

A generated 2D maze: the wall grid plus the geometry exporters and gameplay queries a game needs. Construct once (generation runs in the constructor); the instance is then immutable data you read from every frame.

const maze = new Maze({ cols: 20, rows: 15, algorithm: 'prim', seed: 'level-1', braid: 0.3 });
const grid = maze.tiles();                 // thick walls → CollisionGrid / Tilemap
if (maze.canMove(c, r, E)) c++;            // gameplay movement test
const dir = maze.directionToExit(c, r);    // hint arrow toward nearest exit
  • static readonly N: Dir — North direction index (row − 1).
  • static readonly E: Dir — East direction index (col + 1).
  • static readonly S: Dir — South direction index (row + 1).
  • static readonly W: Dir — West direction index (col − 1).
  • readonly cols: number — Grid width in cells.
  • readonly rows: number — Grid height in cells.
  • readonly algorithm: MazeAlgorithm — Algorithm used to carve this maze.
  • readonly seed: number — The numeric seed actually used (after hashing a string seed).
  • readonly cells: Uint8Array — Per-cell wall bitmask, row-major (cells[row * cols + col]). A set bit means the wall is PRESENT: N=1, E=2, S=4, W=8. @internal-ish — prefer wall / canMove, but exposed for custom exporters.
  • readonly exits: Exit[] — The resolved border exits (entrances and exits are interchangeable).
  • readonly rooms: Room[] — The open rooms that were stamped (empty when rooms was not requested).
  • constructor(opts: MazeOptions)
  • index(col: number, row: number): number — Row-major cell index for (col, row). No bounds check.
  • inBounds(col: number, row: number): boolean — Is (col, row) inside the grid?
  • wall(col: number, row: number, dir: Dir): boolean — Is there a wall on the dir side of cell (col, row)? Out-of-bounds cells read as fully walled.
  • canMove(col: number, row: number, dir: Dir): boolean — Can an actor in cell (col, row) step in dir? True when no wall blocks that side. Stepping through a border opening (an Exit) counts as a move that LEAVES the maze, so this returns true there too — check inBounds on the target if you need to distinguish "exited".
  • step(col: number, row: number, dir: Dir): Cell — The cell reached by stepping dir from (col, row) (may be out of bounds).
  • neighbours(col: number, row: number): Array<Cell & { dir: Dir }> — Open (wall-free) in-bounds neighbours of (col, row), each with the dir taken to reach it.
  • cellAt(x: number, y: number, cellSize = 1, originX = 0, originY = 0): Cell | null — Which cell contains pixel/world position (x, y)? Maps by the thin-wall layout where one cell spans cellSize units from (originX, originY). Returns null when the point is outside the grid. (For a THICK tiles render, cell (c, r) is tile (2c+1, 2r+1).)
  • tiles(opts: TileOptions = {}): number[][] — Expand to a THICK 0/1 wall grid of size (2·cols+1) × (2·rows+1), row-major (grid[y][x]). Cell centres and open passages become floor; walls and the pillars between them stay solid. Feed straight into a Tilemap / CollisionGrid, or place one wall box per solid tile for a Rogue-style 3D dungeon. Exits appear as gaps punched in the outer ring.
  • walls(): WallSeg[] — The maze as THIN wall segments in cell units (0…cols × 0…rows) — the puzzle-book look. Each internal edge appears once; border edges are included except where an exit opens. Draw each as a line (d.line(x1*scale, y1*scale, x2*scale, y2*scale, ...)).
  • distanceField(targets: Cell[]): Int32Array — BFS distance (in cells, through open passages) from every cell to the NEAREST of targets, as a row-major Int32Array. Unreachable cells are -1. Cheap to reuse: compute one field toward a goal, then read a direction from any cell in O(1) with directionTo.
  • path(from: Cell, to: Cell): Cell[] — Shortest path from from to to as an inclusive list of cells ([from … to]), or [] when unreachable. For repeated queries toward one goal, build a distanceField once and step it with directionTo.
  • directionTo(col: number, row: number, goal: Cell | Int32Array): Dir | -1 — The first Dir to step from (col, row) to get closer to a goal, or -1 if there's nowhere better to go. Pass a precomputed distanceField to reuse it across cells/frames; pass a single Cell for a one-off.
  • exitField(): Int32Array — The distance-to-nearest-exit field (built once, then cached). -1 where no exit is reachable, or everywhere when the maze has no exits.
  • directionToExit(col: number, row: number): Dir | -1 — The Dir to step from (col, row) toward the nearest Exit, or -1 if already at an exit / none is reachable.
  • deadEnds(): Cell[] — Every dead-end cell (exactly one open side). Handy for placing treasure, keys, or spawn points.
  • toString(): string — A compact ASCII rendering (# walls, space floor) — for tests and console debugging.

Types

CaveOptions

interface CaveOptions

Configuration for a new Cave. cols/rows are required.

  • cols: number — Grid width in tiles.
  • rows: number — Grid height in tiles.
  • seed?: number | string — Seed for reproducible caves (string is hashed). Omit for random.
  • fillProb?: number — Initial wall probability, 0…1. Higher = tighter caves. Default 0.45.
  • iterations?: number — Cellular-automata smoothing passes. More = rounder. Default 4.
  • wallThreshold?: number — Neighbour wall count (of 8) at/above which a cell becomes wall. Default 5.
  • keepLargest?: boolean — Discard disconnected floor pockets, keeping the largest cavern (so the cave is one connected space). Default true.

CaveTileOptions

interface CaveTileOptions

Options for Cave.tiles — the 0/1 grid.

  • wall?: number — Value for wall tiles. Default 1.
  • floor?: number — Value for floor tiles. Default 0.

Cell

interface Cell

A grid cell.

  • col: number — Column.
  • row: number — Row.

Dir

type Dir = 0 | 1 | 2 | 3

A cardinal direction as an index: N E S W = 0 1 2 3. Read the named values off the class — Maze.N, Maze.E, Maze.S, Maze.W.

DungeonOptions

interface DungeonOptions

Configuration for a new Dungeon. cols/rows are required.

  • cols: number — Grid width in tiles.
  • rows: number — Grid height in tiles.
  • seed?: number | string — Seed for reproducible dungeons (string is hashed). Omit for random.
  • roomAttempts?: number — How many times to try placing a room. Default scales with area.
  • minRoom?: number — Minimum room side in tiles. Default 4.
  • maxRoom?: number — Maximum room side in tiles. Default 9.
  • roomMargin?: number — Minimum wall gap kept between rooms. Default 1.
  • extraConnections?: number — Extra corridors beyond the connecting tree, for loops. Default 2.
  • corridorWidth?: number — Corridor width in tiles. Default 1 (doors are only detected at width 1).

DungeonRoom

interface DungeonRoom

One generated room (a rectangle of floor), in tile coordinates.

  • id: number — Stable room index.
  • col: number — Left column (inclusive).
  • row: number — Top row (inclusive).
  • w: number — Width in tiles.
  • h: number — Height in tiles.
  • cx: number — Centre column (floored).
  • cy: number — Centre row (floored).
  • tag: RoomTag — Role in the layout (see RoomTag).

DungeonTileOptions

interface DungeonTileOptions

Options for Dungeon.tiles — the 0/1(/door) grid.

  • wall?: number — Value for wall tiles. Default 1.
  • floor?: number — Value for floor tiles. Default 0.
  • door?: number — Value for door tiles. Default = floor (doors read as walkable floor).

Exit

interface Exit

A resolved exit: the border cell and the outward direction it opens.

  • col: number — Column of the border cell.
  • row: number — Row of the border cell.
  • dir: Dir — Outward direction through the border wall.

ExitSpec

interface ExitSpec

One requested border opening (passed in the exits array).

  • side: 'N' | 'E' | 'S' | 'W' — Which outer edge to open.
  • at?: number — Position ALONG that edge (column for N/S, row for E/W). Random if omitted.

LevelgenApi

interface LevelgenApi

game.levelgen. Seeds default to the game's seed so a level replays; pass seed for one of your own.

  • maze(opts: MazeOptions): Maze — A maze: wall-per-edge corridors.
  • dungeon(opts: DungeonOptions): Dungeon — A dungeon: rooms joined by corridors, doors, tagged rooms.
  • cave(opts: CaveOptions): Cave — A cave: cellular-automata caverns.

MazeAlgorithm

type MazeAlgorithm = | 'backtracker' | 'prim' | 'kruskal' | 'wilson' | 'aldous-broder' | 'hunt-and-kill' | 'eller' | 'recursive-division' | 'binary-tree' | 'sidewinder'

Which carving algorithm to run. Each biases the maze's look:

  • backtracker (default) — long, winding corridors, few branches ("rivery").
  • prim / kruskal — short branches, bushy, many junctions; look alike.
  • wilson / aldous-broder — unbiased uniform spanning tree (no texture); Aldous-Broder is slow, keep it to small grids.
  • hunt-and-kill — backtracker-ish but with more, shorter dead-ends.
  • eller — row-at-a-time, uniform-ish, very fast.
  • recursive-division — nested chambers, naturally roomy/rectilinear.
  • binary-tree — trivial, strong NE diagonal bias + two open border runs.
  • sidewinder — trivial, one fully-open top row, horizontal-run texture.

MazeOptions

interface MazeOptions

Configuration for a new Maze. cols/rows are required.

  • cols: number — Grid width in cells.
  • rows: number — Grid height in cells.
  • algorithm?: MazeAlgorithm — Carving algorithm. Default 'backtracker'.
  • seed?: number | string — Seed for reproducible mazes — same seed + options ⇒ identical maze. A string is hashed. Omit for a fresh random maze each call.
  • braid?: number — Fraction of dead-ends (0…1) to open into loops, giving MULTIPLE routes between any two points (a "braided" maze). 0 = perfect maze (exactly one path anywhere); 1 = every dead-end removed. Default 0.
  • rooms?: RoomOptions | number — Carve open rooms into the maze. A number is shorthand for { count }.
  • exits?: number | ExitSpec[] — Border openings. A number n punches n exits spread around the border (n=1 → one; n=2 → opposite corners; more → cycled sides). An array places them explicitly. Default 2. Use 0 for a fully sealed maze.

Room

interface Room

A carved room rectangle, in cell coordinates (top-left inclusive).

  • col: number — Left column.
  • row: number — Top row.
  • w: number — Width in cells.
  • h: number — Height in cells.

RoomOptions

interface RoomOptions

Open-room carving controls (passed as rooms in MazeOptions).

  • count?: number — How many rectangular rooms to attempt to stamp. Default 3.
  • minSize?: number — Minimum room side in cells. Default 2.
  • maxSize?: number — Maximum room side in cells. Default 4.

RoomTag

type RoomTag = 'start' | 'boss' | 'treasure' | 'junction' | 'normal'

How a room reads in the layout — for placing the player, objective and loot.

TileOptions

interface TileOptions

Options for Maze.tiles — the thick 0/1 wall grid.

  • wall?: number — Value written for wall tiles. Default 1.
  • open?: number — Value written for open (floor) tiles. Default 0.

WallSeg

interface WallSeg

A thin wall segment in cell units (0 … cols / 0 … rows), for line drawing.

  • x1: number — Start X in cell units.
  • y1: number — Start Y in cell units.
  • x2: number — End X in cell units.
  • y2: number — End Y in cell units.