procedural-generation
π―Skillfrom taozhuo/game-dev-skills
Generates procedurally randomized content like terrain, dungeons, and cities using advanced noise algorithms and algorithmic techniques.
Installation
npx skills add https://github.com/taozhuo/game-dev-skills --skill procedural-generationSkill Details
Implements procedural generation systems including noise functions, terrain generation, dungeon generation, city generation, and object placement. Use when building roguelikes, open worlds, or any content that needs to be generated algorithmically.
Overview
# Roblox Procedural Generation
When implementing procedural generation, use these patterns for performant and interesting content.
Noise Functions
Multi-Octave Perlin Noise
```lua
local function octaveNoise(x, y, octaves, persistence, scale, lacunarity)
octaves = octaves or 4
persistence = persistence or 0.5
scale = scale or 1
lacunarity = lacunarity or 2
local total = 0
local frequency = scale
local amplitude = 1
local maxValue = 0
for i = 1, octaves do
total = total + math.noise(x frequency, y frequency) * amplitude
maxValue = maxValue + amplitude
amplitude = amplitude * persistence
frequency = frequency * lacunarity
end
return total / maxValue -- Normalize to [-1, 1]
end
-- Usage for terrain
local function getTerrainHeight(x, z)
local baseHeight = octaveNoise(x, z, 4, 0.5, 0.01) * 50 -- Large features
local detail = octaveNoise(x, z, 2, 0.5, 0.1) * 5 -- Small details
return baseHeight + detail + 10 -- Base height offset
end
```
Domain Warping
```lua
-- Use noise to distort noise coordinates for more organic shapes
local function warpedNoise(x, y, scale, warpStrength)
local warpX = math.noise(x scale, y scale, 0) * warpStrength
local warpY = math.noise(x scale, y scale, 100) * warpStrength
return math.noise((x + warpX) scale, (y + warpY) scale)
end
-- Creates more interesting, swirly patterns
local function getWarpedTerrainHeight(x, z)
local warp1 = warpedNoise(x, z, 0.005, 50)
local warp2 = warpedNoise(x, z, 0.02, 10)
return (warp1 40 + warp2 10) + 20
end
```
Ridged Noise (Mountains)
```lua
local function ridgedNoise(x, y, octaves, scale)
local total = 0
local frequency = scale
local amplitude = 1
local weight = 1
for i = 1, octaves do
local noise = math.noise(x frequency, y frequency)
noise = 1 - math.abs(noise) -- Create ridges
noise = noise * noise -- Sharpen ridges
noise = noise * weight
weight = math.clamp(noise * 2, 0, 1)
total = total + noise * amplitude
amplitude = amplitude * 0.5
frequency = frequency * 2
end
return total
end
```
Terrain Generation
Heightmap-Based Terrain
```lua
local TerrainGenerator = {}
function TerrainGenerator.generateChunk(chunkX, chunkZ, chunkSize, resolution)
local terrain = workspace.Terrain
local heightMap = {}
-- Generate height map
for x = 0, resolution do
heightMap[x] = {}
for z = 0, resolution do
local worldX = chunkX chunkSize + (x / resolution) chunkSize
local worldZ = chunkZ chunkSize + (z / resolution) chunkSize
local height = getTerrainHeight(worldX, worldZ)
heightMap[x][z] = height
end
end
-- Fill terrain
local cellSize = chunkSize / resolution
for x = 0, resolution - 1 do
for z = 0, resolution - 1 do
local worldX = chunkX chunkSize + x cellSize
local worldZ = chunkZ chunkSize + z cellSize
local h1 = heightMap[x][z]
local h2 = heightMap[x + 1][z]
local h3 = heightMap[x][z + 1]
local h4 = heightMap[x + 1][z + 1]
local minH = math.min(h1, h2, h3, h4)
local maxH = math.max(h1, h2, h3, h4)
local material = TerrainGenerator.getMaterial(maxH, maxH - minH)
terrain:FillBlock(
CFrame.new(worldX + cellSize/2, (minH + maxH)/2, worldZ + cellSize/2),
Vector3.new(cellSize, maxH - minH + 1, cellSize),
material
)
end
end
end
function TerrainGenerator.getMaterial(height, slope)
if slope > 2 then
return Enum.Material.Rock -- Steep = rock
elseif height > 80 then
return Enum.Material.Snow -- High = snow
elseif height > 40 then
return Enum.Material.Rock
elseif height > 5 then
return Enum.Material.Grass
else
return Enum.Material.Sand -- Low = sand/beach
end
end
```
Biome System
```lua
local function getBiome(x, z)
local temperature = octaveNoise(x, z, 2, 0.5, 0.001) -- -1 to 1
local moisture = octaveNoise(x + 1000, z + 1000, 2, 0.5, 0.001)
temperature = (temperature + 1) / 2 -- 0 to 1
moisture = (moisture + 1) / 2
if temperature < 0.3 then
return moisture > 0.5 and "snow_forest" or "tundra"
elseif temperature < 0.6 then
if moisture < 0.3 then
return "plains"
elseif moisture < 0.7 then
return "forest"
else
return "swamp"
end
else
return moisture < 0.4 and "desert" or "jungle"
end
end
local BiomeSettings = {
desert = {
heightScale = 20,
material = Enum.Material.Sand,
treeDensity = 0,
grassDensity = 0
},
forest = {
heightScale = 40,
material = Enum.Material.Grass,
treeDensity = 0.3,
grassDensity = 0.8
},
-- etc.
}
```
Dungeon Generation
BSP (Binary Space Partitioning)
```lua
local DungeonGenerator = {}
local function splitRoom(room, minSize)
local rooms = {}
local canSplitH = room.width >= minSize * 2
local canSplitV = room.height >= minSize * 2
if not canSplitH and not canSplitV then
return {room}
end
local splitHorizontal
if canSplitH and canSplitV then
splitHorizontal = math.random() > 0.5
else
splitHorizontal = canSplitH
end
if splitHorizontal then
local splitX = room.x + math.random(minSize, room.width - minSize)
local room1 = {x = room.x, y = room.y, width = splitX - room.x, height = room.height}
local room2 = {x = splitX, y = room.y, width = room.x + room.width - splitX, height = room.height}
for _, r in ipairs(splitRoom(room1, minSize)) do
table.insert(rooms, r)
end
for _, r in ipairs(splitRoom(room2, minSize)) do
table.insert(rooms, r)
end
else
local splitY = room.y + math.random(minSize, room.height - minSize)
local room1 = {x = room.x, y = room.y, width = room.width, height = splitY - room.y}
local room2 = {x = room.x, y = splitY, width = room.width, height = room.y + room.height - splitY}
for _, r in ipairs(splitRoom(room1, minSize)) do
table.insert(rooms, r)
end
for _, r in ipairs(splitRoom(room2, minSize)) do
table.insert(rooms, r)
end
end
return rooms
end
function DungeonGenerator.generate(width, height, minRoomSize)
local initialRoom = {x = 0, y = 0, width = width, height = height}
local partitions = splitRoom(initialRoom, minRoomSize)
-- Shrink partitions to create rooms with walls between
local rooms = {}
for _, partition in ipairs(partitions) do
local padding = 2
local room = {
x = partition.x + padding,
y = partition.y + padding,
width = partition.width - padding * 2,
height = partition.height - padding * 2
}
if room.width > 0 and room.height > 0 then
table.insert(rooms, room)
end
end
-- Connect rooms with corridors
local corridors = {}
for i = 1, #rooms - 1 do
local r1 = rooms[i]
local r2 = rooms[i + 1]
local x1 = r1.x + r1.width / 2
local y1 = r1.y + r1.height / 2
local x2 = r2.x + r2.width / 2
local y2 = r2.y + r2.height / 2
-- L-shaped corridor
if math.random() > 0.5 then
table.insert(corridors, {x1 = x1, y1 = y1, x2 = x2, y2 = y1})
table.insert(corridors, {x1 = x2, y1 = y1, x2 = x2, y2 = y2})
else
table.insert(corridors, {x1 = x1, y1 = y1, x2 = x1, y2 = y2})
table.insert(corridors, {x1 = x1, y1 = y2, x2 = x2, y2 = y2})
end
end
return rooms, corridors
end
function DungeonGenerator.buildInWorld(rooms, corridors, floorHeight)
local dungeonModel = Instance.new("Model")
dungeonModel.Name = "Dungeon"
-- Build rooms
for _, room in ipairs(rooms) do
local floor = Instance.new("Part")
floor.Size = Vector3.new(room.width, 1, room.height)
floor.Position = Vector3.new(room.x + room.width/2, floorHeight, room.y + room.height/2)
floor.Anchored = true
floor.Material = Enum.Material.Cobblestone
floor.Parent = dungeonModel
-- Walls
-- ... add wall parts
end
-- Build corridors
for _, corridor in ipairs(corridors) do
local dx = corridor.x2 - corridor.x1
local dy = corridor.y2 - corridor.y1
local length = math.sqrt(dxdx + dydy)
local floor = Instance.new("Part")
floor.Size = Vector3.new(3, 1, length)
floor.CFrame = CFrame.lookAt(
Vector3.new((corridor.x1 + corridor.x2)/2, floorHeight, (corridor.y1 + corridor.y2)/2),
Vector3.new(corridor.x2, floorHeight, corridor.y2)
)
floor.Anchored = true
floor.Material = Enum.Material.Cobblestone
floor.Parent = dungeonModel
end
dungeonModel.Parent = workspace
return dungeonModel
end
```
Cellular Automata (Caves)
```lua
local function generateCave(width, height, fillChance, iterations)
-- Initialize with random fill
local grid = {}
for x = 1, width do
grid[x] = {}
for y = 1, height do
if x == 1 or x == width or y == 1 or y == height then
grid[x][y] = 1 -- Walls at edges
else
grid[x][y] = math.random() < fillChance and 1 or 0
end
end
end
-- Apply cellular automata rules
for _ = 1, iterations do
local newGrid = {}
for x = 1, width do
newGrid[x] = {}
for y = 1, height do
local neighbors = 0
for dx = -1, 1 do
for dy = -1, 1 do
if dx ~= 0 or dy ~= 0 then
local nx, ny = x + dx, y + dy
if nx >= 1 and nx <= width and ny >= 1 and ny <= height then
neighbors = neighbors + grid[nx][ny]
else
neighbors = neighbors + 1 -- Out of bounds = wall
end
end
end
end
-- Rule: become wall if 5+ neighbors are walls
if neighbors >= 5 then
newGrid[x][y] = 1
elseif neighbors <= 3 then
newGrid[x][y] = 0
else
newGrid[x][y] = grid[x][y]
end
end
end
grid = newGrid
end
return grid
end
```
Object Placement
Poisson Disc Sampling
```lua
local function poissonDiscSampling(width, height, minDistance, maxAttempts)
maxAttempts = maxAttempts or 30
local cellSize = minDistance / math.sqrt(2)
local gridWidth = math.ceil(width / cellSize)
local gridHeight = math.ceil(height / cellSize)
local grid = {}
for i = 1, gridWidth do
grid[i] = {}
end
local points = {}
local activeList = {}
-- Start with random point
local startX = math.random() * width
local startY = math.random() * height
table.insert(points, {x = startX, y = startY})
table.insert(activeList, 1)
local gx = math.floor(startX / cellSize) + 1
local gy = math.floor(startY / cellSize) + 1
grid[gx][gy] = 1
while #activeList > 0 do
local activeIndex = math.random(#activeList)
local currentPoint = points[activeList[activeIndex]]
local found = false
for _ = 1, maxAttempts do
local angle = math.random() math.pi 2
local distance = minDistance + math.random() * minDistance
local newX = currentPoint.x + math.cos(angle) * distance
local newY = currentPoint.y + math.sin(angle) * distance
if newX >= 0 and newX < width and newY >= 0 and newY < height then
local gx = math.floor(newX / cellSize) + 1
local gy = math.floor(newY / cellSize) + 1
local valid = true
-- Check neighbors
for dx = -2, 2 do
for dy = -2, 2 do
local checkX = gx + dx
local checkY = gy + dy
if checkX >= 1 and checkX <= gridWidth and
checkY >= 1 and checkY <= gridHeight and
grid[checkX][checkY] then
local otherPoint = points[grid[checkX][checkY]]
local dist = math.sqrt((newX - otherPoint.x)^2 + (newY - otherPoint.y)^2)
if dist < minDistance then
valid = false
break
end
end
end
if not valid then break end
end
if valid then
local newIndex = #points + 1
table.insert(points, {x = newX, y = newY})
table.insert(activeList, newIndex)
grid[gx][gy] = newIndex
found = true
break
end
end
end
if not found then
table.remove(activeList, activeIndex)
end
end
return points
end
-- Place trees using Poisson disc
local function placeVegetation(area, density)
local minDistance = 5 / density -- Higher density = closer spacing
local points = poissonDiscSampling(area.width, area.height, minDistance)
for _, point in ipairs(points) do
local worldX = area.x + point.x
local worldZ = area.y + point.y
-- Raycast down to find ground
local ray = workspace:Raycast(
Vector3.new(worldX, 1000, worldZ),
Vector3.new(0, -2000, 0)
)
if ray then
local tree = ReplicatedStorage.Trees:GetChildren()[math.random(#ReplicatedStorage.Trees:GetChildren())]:Clone()
tree:PivotTo(CFrame.new(ray.Position))
tree.Parent = workspace.Vegetation
end
end
end
```
Seeded Generation
Deterministic Random
```lua
local SeededRandom = {}
function SeededRandom.new(seed)
local rng = Random.new(seed)
return {
next = function(self, min, max)
if max then
return rng:NextInteger(min, max)
elseif min then
return rng:NextInteger(1, min)
else
return rng:NextNumber()
end
end,
shuffle = function(self, array)
local n = #array
for i = n, 2, -1 do
local j = rng:NextInteger(1, i)
array[i], array[j] = array[j], array[i]
end
return array
end
}
end
-- Reproducible world generation
local function generateWorld(seed)
local rng = SeededRandom.new(seed)
-- Same seed always produces same world
local numRooms = rng:next(5, 10)
local rooms = {}
for i = 1, numRooms do
table.insert(rooms, {
x = rng:next(0, 100),
y = rng:next(0, 100),
width = rng:next(5, 15),
height = rng:next(5, 15)
})
end
return rooms
end
```
More from this repository10
Manages audio systems with advanced sound pooling, priority management, and performance optimization for immersive game sound experiences.
optimization skill from taozhuo/game-dev-skills
Implements robust game systems like inventory, shops, trading, and progression mechanics for Roblox RPGs with secure, stackable item management.
animation-system skill from taozhuo/game-dev-skills
Generates dynamic visual effects like particle systems, camera shakes, and impact animations for enhancing game experiences in Roblox.
Generates conceptual game art images using Gemini AI, providing precise style-specific prompts for character, environment, and UI design across various game aesthetics.
security-anticheat skill from taozhuo/game-dev-skills
Provides expert guidance on navigating Roblox-specific API behaviors, performance optimizations, and common coding pitfalls in Lua scripting.
Manages reliable Roblox data persistence by implementing robust data loading, saving, caching, and error handling for player progress and game state.
Enables comprehensive combat mechanics with advanced hitbox detection, damage calculation, and hit validation for creating dynamic player-vs-player and player-vs-NPC combat interactions.