Dungeon Generation
Relevant source files
The following files were used as context for generating this wiki page:
- core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/levels/Level.java
- core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/levels/RegularLevel.java
- core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/levels/MiningLevel.java
- core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/levels/builders/Builder.java
- core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/levels/rooms/Room.java
- core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/levels/painters/Painter.java
- core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/Dungeon.java
- core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/tiles/DungeonTileSheet.java
Dungeon generation in Shattered Pixel Dungeon is the procedural system responsible for creating the game's randomized levels. Each level is an instance of a Level subclass, which orchestrates a series of components to build a playable map from scratch. The process is deterministic, controlled by a master seed and a per-depth seed, ensuring that a given seed will always produce the same dungeon layout.
The generation process can be broadly divided into structural layout, terrain painting, and population. For most levels, this involves initializing a set of logical Room objects, using a Builder to arrange them and connect them with corridors, using a Painter to translate this logical layout into a tilemap, and finally populating the map with mobs, items, and other game elements.
Core Components
The dungeon generation system is modular, relying on several key abstract classes to define the process.
| Class | Purpose | Source File |
|---|---|---|
Level | The abstract base class for all dungeon levels. It holds the map data, entity lists, and orchestrates the high-level generation flow. | Level.java |
RegularLevel | A subclass of Level that implements the common room-and-corridor generation style using Builder and Painter components. | RegularLevel.java |
Room | A logical representation of an area in the dungeon, such as a standard room, a shop, or a secret room. | Room.java |
Builder | An abstract class responsible for arranging a list of Rooms into a coherent level layout, defining their positions and connections. | Builder.java |
Painter | An abstract class that translates the logical Room layout from the Builder into a concrete tilemap by setting Terrain types. | Painter.java |
Dungeon | A static utility class that manages the overall game state, including selecting which Level subclass to instantiate for the current depth. | Dungeon.java |
This diagram shows the relationships between the main classes involved in dungeon generation.
Sources: Level.java, RegularLevel.java, MiningLevel.java, Builder.java, Painter.java, Room.java
Generation Flow
The generation of any level is initiated by Dungeon.newLevel() and follows a consistent, high-level sequence managed by the Level.create() method.
Sources: Dungeon.java:310, Level.java:231
This flowchart illustrates the main steps in the Level.create() method.
Sources: Dungeon.java:310-377, Level.java:231-300
- Select Level Type:
Dungeon.newLevel()selects the appropriateLevelsubclass (e.g.,SewerLevel,CavesBossLevel,MiningLevel) based on the currentDungeon.depthandDungeon.branch.
Sources: Dungeon.java:313-377 - Instantiate and Create: A new instance of the selected level class is created, and its
create()method is called.
Sources: Level.java:231 - Initialize RNG: The random number generator is seeded using
Dungeon.seedCurDepth(), which generates a unique, deterministic seed for the current depth and branch. This ensures levels are reproducible.
Sources: Level.java:233, Dungeon.java:420-435 - Determine Level Feeling: For non-boss levels, a
Feelingmay be randomly assigned. This can modify generation parameters, such as making the level larger, darker, or adding more traps.
Sources: Level.java:261-291 - Build Layout: The abstract
build()method is called. This is where subclasses implement their specific layout logic. For aRegularLevel, this involves initializing rooms, using aBuilderto connect them, and aPainterto draw them.
Sources: Level.java:298, RegularLevel.java:100 - Build Flag Maps: After the layout is built,
buildFlagMaps()is called to create boolean arrays (passable,solid,losBlocking, etc.) based on theTerrainflags of each tile in themaparray.
Sources: Level.java:299, 931-988 - Populate: The
createMobs()andcreateItems()methods are called to populate the level with enemies and loot.
Sources: Level.java:301-302
Level Feelings
A Level.Feeling is a special theme that can be randomly applied to a standard dungeon floor, modifying its generation.
| Feeling | Effect |
|---|---|
CHASM | The default wall terrain is replaced with Terrain.CHASM. |
WATER | More water is generated. |
GRASS | More grass is generated. |
DARK | The hero's viewDistance is reduced. |
LARGE | The level is larger and contains more food. |
TRAPS | More traps are generated. |
SECRETS | More secret rooms are generated. |
NONE | No special feeling is applied. |
| Sources: Level.java:160-175, 261-291 |
RegularLevel Generation
Most floors in the dungeon are instances of RegularLevel or its subclasses. These levels follow a room-and-corridor structure. The build() method in RegularLevel orchestrates this process.
Sources: RegularLevel.java:100-113
The following diagram shows the sequence of operations within RegularLevel.build().
Sources: RegularLevel.java:100-113
Room Initialization
The initRooms() method creates an ArrayList<Room> that will be used by the Builder. It adds:
- An
EntranceRoomand anExitRoom. - A number of
StandardRooms, determined bystandardRooms(). Larger rooms are created by reducing the total count. SpecialRooms (e.g.,ShopRoom,PitRoom), determined byspecialRooms().SecretRooms, determined bySecretRoom.secretsForFloor().
Sources: RegularLevel.java:115-152
Level Building
The builder() method in RegularLevel selects a Builder implementation. The default implementation randomly chooses between a LoopBuilder and a FigureEightBuilder. The builder's build() method takes the list of rooms and attempts to arrange them into a valid, connected layout. This process is repeated in a do-while loop until a successful layout is generated.
Sources: RegularLevel.java:106-110, 164-177
Level Painting
Once a valid room layout is established by the Builder, the painter().paint() method is called. The Painter is responsible for translating the logical room structure into a concrete tilemap in level.map. It does this by:
- Filling the area of each room with floor tiles.
- Drawing walls around each room.
- Carving out tunnels to connect the rooms as defined by the
Builder. - Placing doors at the connections.
- Performing final decoration passes, such as adding
WALL_DECOor other features specific to the painter (e.g.,CavesPainteradds gold veins).
Sources: RegularLevel.java:112, 179, CavesPainter.java:31-106
Level Population
After the level's terrain is fully generated, it is populated with mobs and items.
Mob Creation
The createMobs() method is responsible for spawning enemies. In RegularLevel, it determines a mobLimit based on the dungeon depth and spawns that many mobs. Mobs are placed in random StandardRooms, ensuring they are not too close to the level entrance.
Sources: RegularLevel.java:195-281
Item Creation
The createItems() method places items throughout the level. This includes:
- Guaranteed Drops: Items like Potions of Strength and Scrolls of Upgrade are added to a spawn list (
itemsToSpawn) ifDungeon.posNeeded()orDungeon.souNeeded()return true. - Random Drops: A random number of items are generated via
Generator.random()and placed in the level, sometimes in chests or as mimics. - Special Drops: Logic for dropping quest items (e.g.,
DriedRose.Petal), lore pages, and items from challenges (e.g.,Torchfor the Darkness challenge) is also handled here.
Sources: Level.java:238-260, RegularLevel.java:329-537
Terrain and Tile System
The physical layout of the level is stored in the level.map integer array. Each integer corresponds to a terrain type defined in Terrain.java. These terrain types have associated flags that determine their properties (e.g., PASSABLE, SOLID, LOS_BLOCKING).
The visual representation of these terrain types is managed by DungeonTileSheet.java. This class maps Terrain constants to specific tile indices on a spritesheet. It also contains complex logic for "stitching" tiles together, such as creating smooth transitions between water and land, or rendering wall overhangs correctly.
For example, the stitchWaterTile method calculates the correct water tile to use based on its four neighbors, allowing for 16 different variations to connect smoothly with land.
// core/src/main/java/com/shatteredpixel/shatteredpixeldungeon/tiles/DungeonTileSheet.java:140-147
public static int stitchWaterTile(int top, int right, int bottom, int left){
int result = WATER;
if (waterStitcheable(top)) result += 1;
if (waterStitcheable(right)) result += 2;
if (waterStitcheable(bottom)) result += 4;
if (waterStitcheable(left)) result += 8;
return result;
}
Sources: DungeonTileSheet.java:140-147
Similarly, complex logic exists for stitching different types of walls and handling chasm edges. This system allows for a visually rich and coherent environment from a simple integer-based map.
Sources: Level.java:931-988, DungeonTileSheet.java
Conclusion
The dungeon generation system in Shattered Pixel Dungeon is a robust and extensible procedural framework. By separating the concerns of logical layout (Room, Builder), terrain representation (Painter, Terrain), and population (createMobs, createItems), it allows for a wide variety of level types and features. The use of a deterministic, seeded RNG ensures that every dungeon is both unique and reproducible, a cornerstone of the roguelike genre.