Scripting HelpersARCHIVE
A little piece of Roblox history. Preserved for everyone, one question at a time.Read about the archive →
✓ ANSWER ACCEPTED

How do I procedurally generate structures?

Currently I am using the publicly available smooth terrain procedural generation code from the roblox wiki. I would like to know how to do things like generating trees, biomes, rocks, and bodies of water. I have fiddled with the script for hours but can't figure anything out.

local Players = game:GetService("Players")
 
------------------------------------------------------------------------------------------------------------------------------------------------
 
local BASE_HEIGHT 		= 10				-- The main height factor for the terrain.
local CHUNK_SCALE 		= 3 				-- The grid scale for terrain generation. Should be kept relatively low if used in real-time.
local RENDER_DISTANCE 	= 120 / 4 			-- The length/width of chunks in voxels that should be around the player at all times
local X_SCALE 			= 90 / 4			-- How much we should strech the X scale of the generation noise
local Z_SCALE 			= 90 / 4			-- How much we should strech the Z scale of the generation noise
local GENERATION_SEED	= math.random() 	-- Seed for determining the main height map of the terrain.
 
------------------------------------------------------------------------------------------------------------------------------------------------
 
local chunks = {}
 
local function chunkExists(chunkX, chunkZ)
	if not chunks[chunkX] then
		chunks[chunkX] = {}
	end
	return chunks[chunkX][chunkZ]
end
 
local function mountLayer(x, heightY, z, material)
	local beginY = -BASE_HEIGHT
	local endY = heightY
	local cframe = CFrame.new(x * 4 + 2, (beginY + endY) * 4 / 2, z * 4 + 2)
	local size = Vector3.new(4, (endY - beginY) * 4, 4)
	workspace.Terrain:FillBlock(cframe, size, material)	
end
 
function makeChunk(chunkX, chunkZ)
	local rootPosition = Vector3.new(chunkX * CHUNK_SCALE, 0, chunkZ * CHUNK_SCALE)
	chunks[chunkX][chunkZ] = true -- Acknowledge the chunk's existance.
	for x = 0, CHUNK_SCALE - 1 do
		for z = 0, CHUNK_SCALE - 1 do
			local cx = (chunkX * CHUNK_SCALE) + x
			local cz = (chunkZ * CHUNK_SCALE) + z
			local noise = math.noise(GENERATION_SEED, cx / X_SCALE, cz / Z_SCALE)
			local cy = noise * BASE_HEIGHT
			mountLayer(cx, cy, cz, Enum.Material.Grass)
		end
	end
end
 
function checkSurroundings(location)
	local chunkX, chunkZ = math.floor(location.X / 4 / CHUNK_SCALE), math.floor(location.Z / 4 / CHUNK_SCALE)
	local range = math.max(1, RENDER_DISTANCE / CHUNK_SCALE)
	for x = -range, range do
		for z = -range, range do
			local cx, cz = chunkX + x
			local cz = chunkZ + z
			if not chunkExists(cx, cz) then
				makeChunk(cx, cz)
			end
		end
	end
end
 
while true do
	for _, player in pairs(Players:GetPlayers()) do
		if player.Character then
			local humanoidRootPart = player.Character:FindFirstChild("HumanoidRootPart")
			if humanoidRootPart then
				checkSurroundings(humanoidRootPart.Position)
			end
		end
	end
	wait(1)
end

1 answer

✓ Accepted answer

In this script, it looks like checkSurroundings determines what chunks need to be created based on where the game's players are, makeChunk generates a chunk given a position on the chunk grid, and mountLayer actually creates that chunk in the world.

The "shape" of the terrain is actually created by the line math.noise(GENERATION_SEED, cx / X_SCALE, cz / Z_SCALE), which generates a semi-random height at every location on the map. This then is used to create chunks of roblox terrain in the world. To change how it looks, you need to edit this line, or the parameters influencing it.

To generate structures, you could use more random noise to determine on what chunk to place them then modify the mountLayer function to actually place them in the world.

Below, I've modified the script to generate blocks randomly, 5% of the time. Hopefully this is enough for you to get started - it should be pretty easy to adapt the example to copy and place actual models in the world.

local Players = game:GetService("Players")

------------------------------------------------------------------------------------------------------------------------------------------------

local BASE_HEIGHT       = 10                -- The main height factor for the terrain.
local CHUNK_SCALE       = 3               -- The grid scale for terrain generation. Should be kept relatively low if used in real-time.
local RENDER_DISTANCE   = 120 / 4           -- The length/width of chunks in voxels that should be around the player at all times
local X_SCALE           = 90 / 4            -- How much we should strech the X scale of the generation noise
local Z_SCALE           = 90 / 4            -- How much we should strech the Z scale of the generation noise
local GENERATION_SEED   = 1234     -- Seed for determining the main height map of the terrain.

------------------------------------------------------------------------------------------------------------------------------------------------

local chunks = {}

local function chunkExists(chunkX, chunkZ)
    if not chunks[chunkX] then
        chunks[chunkX] = {}
    end
    return chunks[chunkX][chunkZ]
end

local function genPart(x, y, z) --Generates a pillar at a passed position
	local p = Instance.new("Part", workspace)
	p.Size = Vector3.new(4,10,4)
	p.Color = Color3.new(1,.5,.5)
	p.Position = Vector3.new(x * 4 + 2, y * 4 + 2, z * 4 + 2)
	p.Anchored = true
end

local function mountLayer(x, heightY, z, material)
	if math.random() < .05 then
		genPart(x, heightY, z)
	end
    local beginY = -BASE_HEIGHT
    local endY = heightY
    local cframe = CFrame.new(x * 4 + 2, (beginY + endY) * 4 / 2, z * 4 + 2)
    local size = Vector3.new(4, (endY - beginY) * 4, 4)
    workspace.Terrain:FillBlock(cframe, size, material) 
end

function makeChunk(chunkX, chunkZ)
    local rootPosition = Vector3.new(chunkX * CHUNK_SCALE, 0, chunkZ * CHUNK_SCALE)
    chunks[chunkX][chunkZ] = true -- Acknowledge the chunk's existance.
    for x = 0, CHUNK_SCALE - 1 do
        for z = 0, CHUNK_SCALE - 1 do
            local cx = (chunkX * CHUNK_SCALE) + x
			local cz = (chunkZ * CHUNK_SCALE) + z
            local noise = math.noise(GENERATION_SEED, cx / X_SCALE, cz / Z_SCALE)
            local cy = noise * BASE_HEIGHT
            mountLayer(cx, cy, cz, Enum.Material.Grass)
        end
    end
end

function checkSurroundings(location)
    local chunkX, chunkZ = math.floor(location.X / 4 / CHUNK_SCALE), math.floor(location.Z / 4 / CHUNK_SCALE)
    local range = math.max(1, RENDER_DISTANCE / CHUNK_SCALE)
    for x = -range, range do
        for z = -range, range do
            local cx, cz = chunkX + x
            local cz = chunkZ + z
            if not chunkExists(cx, cz) then
                makeChunk(cx, cz)
            end
        end
    end
end

while true do
    for _, player in pairs(Players:GetPlayers()) do
        if player.Character then
            local humanoidRootPart = player.Character:FindFirstChild("HumanoidRootPart")
            if humanoidRootPart then
                checkSurroundings(humanoidRootPart.Position)
            end
        end
    end
    wait(1)
end

This discussion is preserved as it was. New answers, comments, and votes are closed.