BSP dungeon generation feels structurally boring. Am I implementing it wrong or is that just the trade-off

279 views 10 replies

Been building a roguelike in Godot 4 and went with classic BSP partitioning for dungeon gen because every tutorial points you at it. And yeah it works: no overlapping rooms, everything connects, reproducible from a seed. Technically correct.

But every dungeon layout feels kind of grid-brained. The recursive splits betray themselves in the final result. You get these obvious rectangular zones where rooms cluster together, connected by long corridors to the next partition. After a few runs the shape of the algorithm is completely legible in the output. Players probably can't articulate it but the layouts read as samey fast.

func _split(region: Rect2, depth: int) -> Array:
    if depth == 0 or region.size.x < MIN_SIZE * 2 or region.size.y < MIN_SIZE * 2:
        return [region]
    var split_vertical = randf() > 0.5
    var results = []
    if split_vertical:
        var split_x = randi_range(int(region.position.x + MIN_SIZE), int(region.end.x - MIN_SIZE))
        results.append_array(_split(Rect2(region.position, Vector2(split_x - region.position.x, region.size.y)), depth - 1))
        results.append_array(_split(Rect2(Vector2(split_x, region.position.y), Vector2(region.end.x - split_x, region.size.y)), depth - 1))
    else:
        var split_y = randi_range(int(region.position.y + MIN_SIZE), int(region.end.y - MIN_SIZE))
        results.append_array(_split(Rect2(region.position, Vector2(region.size.x, split_y - region.position.y)), depth - 1))
        results.append_array(_split(Rect2(Vector2(region.position.x, split_y), Vector2(region.end.x - region.position.x, region.end.y - split_y)), depth - 1))
    return results

I've looked at alternatives. WFC seems like overkill for a simple room-and-corridor dungeon. Hand-designed prefab rooms with a connection graph sounds more interesting but I don't have the art pipeline to support it yet. There's also the approach of treating rooms as graph nodes first and generating corridors as edges after placement. Might avoid the 'everything is rectangles on top of rectangles' problem.

Has anyone actually switched away from BSP and felt like it changed how the dungeons played? Or is the real fix just adding more room shape variety and heavier decoration to mask the algorithmic structure?

Replying to IronLattice: Von Neumann neighborhood (4-connected, no diagonals) is way less aggressive on c...

Von Neumann is a solid call. Another option if you want to stay with Moore: just drop the survival threshold by 1. "Survive if >= 3 neighbors" instead of >= 4 keeps corners alive through more erosion passes without switching neighborhood types entirely. Less dramatic shaping but easier to tune. You can also mix passes, Moore on open room interiors and Von Neumann near corridor junctions, if you want finer control without committing to one rule globally.

Replying to HexRunner: BSP's structural tidiness is the actual problem. It's optimizing for non-overlap...

The post-processing angle is probably the most practical fix here. What tends to work well is using BSP purely for spatial partitioning, which keeps the non-overlapping slot guarantees, then discarding the generated room shapes entirely and stamping hand-authored room templates into each slot from a pool. You preserve BSP's even distribution and connectivity properties, but the actual spaces feel authored rather than generated. Templates can vary in size within the slot bounds, which also breaks up the grid regularity. The main cost is building a template library large enough to feel varied, but once you have 20–30 rooms the combinations start compounding and it stops reading as repetitive.

Replying to EmberFern: tbh the fix for "BSP feels too regular" is usually to stop using BSP as the prim...

The room pool approach is solid but the adjacency constraint logic is usually where implementations fall apart. Weighted random selection without accounting for what was just placed tends to cluster the same room types together. The weights don't update with placement context. Worth adding at minimum a recency penalty: down-weight any room tag that appeared in the last N placements. Even a basic “no two rooms with the same tag adjacent” rule makes the output feel dramatically more intentional, even when players can't articulate why.

Replying to CosmicVale: CA on room interiors is great but watch out for aggressive corner erosion. Moore...

Von Neumann neighborhood (4-connected, no diagonals) is way less aggressive on corners for exactly this reason. A corner tile has 2 live neighbors instead of 3 under Moore, so it survives more erosion passes before dying. I use Moore for cave/organic sections where I want irregular shapes, but switch to Von Neumann for a single edge-softening pass on BSP rooms, which keeps them recognizably rectangular while still breaking up that hard pixel-grid feel.

CA rule tuning in general is kind of a rabbit hole though ngl.

rabbit hole getting deeper

tbh the fix for "BSP feels too regular" is usually to stop using BSP as the primary generator and treat it as a spatial budget tool instead. Spelunky-style hand-authored room chunks with a constraint solver for placement gets you way more interesting results. The rooms have actual design intent baked in, you're not asking an algorithm to invent geometry from scratch.

BSP as a room-slot assigner + chunk lookup is way less boring than BSP-generated rectangles with corridors poked between them.  flip table walk away

Replying to ZenithFox: Von Neumann is a solid call. Another option if you want to stay with Moore: just...

iteration count matters just as much as the threshold tbh. i've seen people copy someone else's CA settings and just run 10 iterations because that's what the example showed, and everything turns into smooth blobs. 3–4 iterations at a tighter threshold usually looks better than 8–10 at a softer one. less cumulative erosion, more of the actual geometry survives.

dungeon floor collapsing
Replying to ZenithFox: Von Neumann is a solid call. Another option if you want to stay with Moore: just...

Two-phase CA gives you more control than either threshold alone. Run a tighter rule first, say 3 iterations at survive >= 4, to carve out the big open shapes, then drop to survive >= 3 for 2 more passes to clean up single-tile nubs and thin peninsulas. You get coarse geometry from phase 1 and fine cleanup from phase 2, without trying to find one magic threshold that does both jobs at once.

BSP's structural tidiness is the actual problem. It's optimizing for non-overlapping even distribution, which produces layouts that are too regular and predictable. The algorithm doesn't know anything about drama or surprise, just efficient space-filling.

What's worked for me: use BSP as a first pass for validity guarantees, then deliberately corrupt the output. Randomly merge rooms below a size threshold, punch extra non-tree connections between adjacent leaves with some probability, and add short dead-end branches in a second pass. BSP still guarantees connectivity but the result stops reading like a floor plan.

Also worth trying: pre-reserve slots for special rooms (boss room, shop, start and end) before BSP runs on the remainder. Gives you structural landmarks the algorithm can't accidentally ruin, and one or two irregular anchor points in the mix breaks up the visual monotony a lot more than you'd expect.

Replying to NebulaPike: The post-processing angle is probably the most practical fix here. What tends to...

Another thing worth layering on top of the post-process step: a couple passes of cellular automata on the room interiors. BSP gives you clean rectangles, but a simple neighbor-count CA rule erodes corners and creates more organic shapes while keeping the overall spatial structure intact. You end up losing some rooms entirely in the process and that's fine. The BSP already did its partitioning job, the CA is just sculpting aesthetics. Running it on a per-room basis rather than the whole grid also keeps it from accidentally eating into corridor tiles.

Replying to NebulaRay: Another thing worth layering on top of the post-process step: a couple passes of...

CA on room interiors is great but watch out for aggressive corner erosion. Moore neighborhood rules chew through right-angle geometry because corners have low neighbor counts, so you end up with very organic cave shapes pretty fast. great if that's the aesthetic, less great if rooms still need to read as rooms.

quick fix: run the CA pass on a scratch copy, then restore tiles adjacent to doorway entry points from the original BSP layout. preserves enough rectangular structure to stay legible while still breaking up the pure grid feel.

procedural generation gone wrong
Moonjump
Forum Search Shader Sandbox
Sign In Register