Procedural Generation
The Procedural Generation System provides tools for creating randomized content - dungeons, terrain, and other game environments. It includes heightmaps for terrain generation, BSP for room-based dungeons, and noise functions for natural patterns.
Overview
McRogueFace’s procgen tools work together:
- Noise generates natural-looking random values
- HeightMaps store and manipulate 2D value grids
- BSP partitions space for room-based layouts
# Generate a noise-based terrain
noise = mcrfpy.Perlin(seed=42)
heightmap = mcrfpy.HeightMap(width=80, height=45)
heightmap.fill_noise(noise, scale=0.1)
Objects
| Object | Purpose |
|---|---|
| Perlin | Perlin noise generation |
Noise Subsystem
Noise functions generate pseudo-random values that vary smoothly across space, perfect for natural-looking terrain and textures.
Perlin Noise
Perlin noise produces smooth, continuous random values:
noise = mcrfpy.Perlin(seed=42)
# Sample at any point
value = noise.at(x * 0.1, y * 0.1) # Returns -1.0 to 1.0
Scale and Octaves
The sampling scale controls feature size:
# Large features (continents)
large = noise.at(x * 0.01, y * 0.01)
# Small features (rocks)
small = noise.at(x * 0.5, y * 0.5)
# Combine for detail at multiple scales
combined = large * 0.6 + small * 0.4
Noise to Tiles
Convert noise values to tile types:
for y in range(grid.height):
for x in range(grid.width):
value = noise.at(x * 0.1, y * 0.1)
cell = grid.at(x, y)
if value < -0.2:
cell.tilesprite = WATER
cell.walkable = False
elif value < 0.3:
cell.tilesprite = GRASS
cell.walkable = True
else:
cell.tilesprite = MOUNTAIN
cell.walkable = False
HeightMap Subsystem
HeightMaps are 2D arrays of float values, useful for terrain generation and cellular automata.
Creating HeightMaps
heightmap = mcrfpy.HeightMap(width=80, height=45)
# Fill with noise
heightmap.fill_noise(noise, scale=0.1)
# Or fill with a constant
heightmap.fill(0.5)
Operators
HeightMaps support mathematical operations:
# Unary operations
heightmap.normalize() # Scale to 0.0-1.0
heightmap.invert() # Flip values
heightmap.clamp(0.0, 1.0) # Limit range
# Binary operations
combined = heightmap1.add(heightmap2)
combined = heightmap1.multiply(heightmap2)
Convolution Operations
Apply convolution kernels for effects like blur and edge detection:
# Smooth the heightmap (Gaussian blur with kernel3)
blur_weights = [1, 2, 1, 2, 4, 2, 1, 2, 1] # Flattened 3x3 kernel
blurred = heightmap.kernel3(blur_weights) # Returns new HeightMap
# Edge detection with sparse_kernel (arbitrary offsets)
edges = heightmap.sparse_kernel({
(-1, 0): -1, (1, 0): -1,
(0, -1): -1, (0, 1): -1,
(0, 0): 4
})
Cellular Automata
Use heightmaps for cellular automata cave generation:
# Initialize with random values
import random
for y in range(height):
for x in range(width):
heightmap.set(x, y, 1.0 if random.random() < 0.45 else 0.0)
# Apply cellular automata rules
for _ in range(5):
new_map = mcrfpy.HeightMap(width, height)
for y in range(height):
for x in range(width):
neighbors = count_neighbors(heightmap, x, y)
if neighbors >= 5:
new_map.set(x, y, 1.0)
else:
new_map.set(x, y, 0.0)
heightmap = new_map
BSP Subsystem
Binary Space Partitioning creates room-based dungeon layouts by recursively dividing space.
BSP Algorithm Overview
- Start with the full map area
- Recursively split into smaller areas
- Place rooms within leaf nodes
- Connect rooms with corridors
Basic BSP Generation
def generate_bsp_dungeon(width, height, min_room=6):
# Create BSP tree
bsp = mcrfpy.BSP(0, 0, width, height)
bsp.split_recursive(
depth=4,
min_width=min_room + 2,
min_height=min_room + 2
)
rooms = []
# Create rooms in leaf nodes
for node in bsp.leaves():
# Leave space for walls
room_x = node.x + 1
room_y = node.y + 1
room_w = node.width - 2
room_h = node.height - 2
rooms.append((room_x, room_y, room_w, room_h))
return rooms
BSP to HeightMap
Convert BSP results to a heightmap for further processing:
heightmap = mcrfpy.HeightMap(width, height)
heightmap.fill(0.0) # Start with walls
for room_x, room_y, room_w, room_h in rooms:
for y in range(room_y, room_y + room_h):
for x in range(room_x, room_x + room_w):
heightmap.set(x, y, 1.0) # Floor
Connecting Rooms
Connect adjacent BSP nodes with corridors:
def connect_rooms(heightmap, room1, room2):
# Get room centers
x1 = room1[0] + room1[2] // 2
y1 = room1[1] + room1[3] // 2
x2 = room2[0] + room2[2] // 2
y2 = room2[1] + room2[3] // 2
# L-shaped corridor
for x in range(min(x1, x2), max(x1, x2) + 1):
heightmap.set(x, y1, 1.0)
for y in range(min(y1, y2), max(y1, y2) + 1):
heightmap.set(x2, y, 1.0)
Combining Techniques
Real dungeon generation often combines multiple techniques:
def generate_dungeon(grid):
# 1. BSP for room layout
rooms = generate_bsp_dungeon(grid.width, grid.height)
# 2. Apply rooms to grid
for x, y, w, h in rooms:
for ry in range(y, y + h):
for rx in range(x, x + w):
cell = grid.at(rx, ry)
cell.tilesprite = FLOOR
cell.walkable = True
# 3. Connect rooms
for i in range(len(rooms) - 1):
connect_rooms_on_grid(grid, rooms[i], rooms[i+1])
# 4. Add noise-based decorations
noise = mcrfpy.Perlin(seed=random.randint(0, 99999))
for y in range(grid.height):
for x in range(grid.width):
cell = grid.at(x, y)
if cell.walkable and noise.at(x * 0.3, y * 0.3) > 0.5:
cell.tilesprite = DECORATED_FLOOR