Tutorial · Gameplay

How to generate terrain in Roblox Studio with a script

A flat baseplate makes any game feel empty. Roblox Terrain gives you hills, sand, rock, snow and water that players can walk on and swim in, and you can make it with a script instead of painting it by hand. This guide builds an island map with a beach all round, rolling hills, snowy peaks, a lake and the sea, from one script with a handful of settings. Change the seed and you get a new coastline.

Updated 25 October 20266 min readIntermediateLuau

In short

Loop over the map in 4-stud steps. For each spot, work out a ground height: an island mask that is high in the middle and falls to zero at the coast, times math.noise for hills. Fill a column of rock up to that height with Terrain:FillBlock, then a 4-stud top layer whose material depends on the height: sand near the water, grass on the slopes, rock and snow up high. Fill the sea with one slab of Water first, and the land replaces it where it rises.

  • Terrain is made of 4 by 4 by 4 stud voxels, so step in 4s.
  • Run it from the Command Bar to build the terrain once and save it in your place, or as a server Script to build it every time a server starts.
  • No code? Studio's Terrain Editor has a Generate tool with biomes. The editor tools.

Get the script

Terrain generator script: islands, hills and a lake

One Luau script builds islands with beaches, noise hills, rock and snow peaks, a lake with a bank and the sea around them out of real Roblox Terrain.

Script in ServerScriptService named TerrainGenerator
-- Script in ServerScriptService named TerrainGenerator (or paste it into the Command Bar,
-- View > Command Bar, to build the terrain once in edit mode and save it with the place).
-- Islands with hills, beaches, a lake and the sea around them, made of real Terrain.
local SEED = 42 -- change it for a different coastline and hills
local CELL = 4 -- studs per column; Terrain voxels are 4 studs
local SEA_LEVEL = 0
local SEA_FLOOR = -24
local HILL_HEIGHT = 70 -- the tallest hills reach about this high
local ISLANDS = {
	{ center = Vector2.new(0, 0), radius = 260 },
	{ center = Vector2.new(330, 190), radius = 90 },
}
local LAKE = { center = Vector2.new(-60, 40), radius = 50, level = 14 }
local CLEAR_FIRST = false -- true wipes ALL existing terrain first

local RunService = game:GetService("RunService")
local terrain = workspace.Terrain

-- 0 at the coast, 1 in the middle of the nearest island, with a soft edge
local function islandMask(x: number, z: number): number
	local best = 0
	for _, island in ISLANDS do
		local d = (Vector2.new(x, z) - island.center).Magnitude / island.radius
		best = math.max(best, 1 - d * d)
	end
	return best
end

-- Height of the ground at (x, z): island shape + two layers of noise for hills
local function heightAt(x: number, z: number): number
	local mask = islandMask(x, z)
	if mask <= 0 then
		return SEA_FLOOR
	end
	local broad = math.noise(x / 180, z / 180, SEED) * 0.5 + 0.5
	local detail = math.noise(x / 45, z / 45, SEED + 10) * 0.25
	local h = SEA_LEVEL - 6 + mask * (12 + (broad + detail) * HILL_HEIGHT * mask)
	-- the lake: a bowl up to 8 studs deep, with a bank around it so the water stays in
	local lakeDist = (Vector2.new(x, z) - LAKE.center).Magnitude / LAKE.radius
	if lakeDist < 1 then
		h = LAKE.level - 8 * (1 - lakeDist * lakeDist)
	elseif lakeDist < 1.4 then
		h = math.max(h, LAKE.level + 2)
	end
	return h
end

local function surfaceMaterial(h: number): Enum.Material
	if h < SEA_LEVEL + 3 then
		return Enum.Material.Sand
	elseif h < SEA_LEVEL + 45 then
		return Enum.Material.Grass
	elseif h < SEA_LEVEL + 60 then
		return Enum.Material.Rock
	end
	return Enum.Material.Snow
end

if CLEAR_FIRST then
	terrain:Clear()
end

-- The extent to cover: every island plus a margin of sea
local minX, maxX, minZ, maxZ = math.huge, -math.huge, math.huge, -math.huge
for _, island in ISLANDS do
	minX = math.min(minX, island.center.X - island.radius - 60)
	maxX = math.max(maxX, island.center.X + island.radius + 60)
	minZ = math.min(minZ, island.center.Y - island.radius - 60)
	maxZ = math.max(maxZ, island.center.Y + island.radius + 60)
end

-- The sea first: one slab of water, then the land replaces it where it rises
local seaSize = Vector3.new(maxX - minX, SEA_LEVEL - SEA_FLOOR, maxZ - minZ)
terrain:FillBlock(CFrame.new((minX + maxX) / 2, (SEA_LEVEL + SEA_FLOOR) / 2, (minZ + maxZ) / 2), seaSize, Enum.Material.Water)
terrain:FillBlock(CFrame.new((minX + maxX) / 2, SEA_FLOOR - 4, (minZ + maxZ) / 2), Vector3.new(seaSize.X, 8, seaSize.Z), Enum.Material.Sand)

for x = minX, maxX, CELL do
	for z = minZ, maxZ, CELL do
		local h = heightAt(x, z)
		if h > SEA_FLOOR then
			local bottom = SEA_FLOOR - 8
			local top = math.max(h - 4, bottom + 1)
			-- the body of the column, then 4 studs of surface on top
			terrain:FillBlock(CFrame.new(x, (bottom + top) / 2, z), Vector3.new(CELL, top - bottom, CELL), Enum.Material.Rock)
			terrain:FillBlock(CFrame.new(x, top + 2, z), Vector3.new(CELL, 4, CELL), surfaceMaterial(h))
			-- land below sea level stays under water; inside the lake, water up to its level
			local waterTop = if (Vector2.new(x, z) - LAKE.center).Magnitude < LAKE.radius then LAKE.level else SEA_LEVEL
			if h < waterTop then
				terrain:FillBlock(CFrame.new(x, (h + waterTop) / 2, z), Vector3.new(CELL, waterTop - h, CELL), Enum.Material.Water)
			end
		end
	end
	if RunService:IsRunning() then
		task.wait() -- in a running game, one row per frame so the server does not stall
	end
end
print("TerrainGenerator: done")

RoCode builds it in the place you have open in Roblox Studio, on Windows or macOS with a desktop browser; on a phone the button sends the prompt and the link to your computer. Free to start, with free credits every day. More Roblox Studio scripts

Where to put it

Explorer: one Script, or paste it into the Command Bar
ServerScriptService
  TerrainGenerator Script new

Settings you can change

SEEDIn the script
Any number. Each seed gives different hills and a different coastline. Default: 42.
HILL_HEIGHTIn the script
Roughly how tall the highest hills get, in studs. Snow starts at 60. Default: 70.
CLEAR_FIRSTIn the script
Set it to true to wipe all existing terrain before building. It cannot be undone in a running game. Default: false.

Islands go in ISLANDS (a centre and a radius in studs) and the lake in LAKE. Keep the lake inside an island.

Type-checked with luau-lsp strict against Roblox's API definitions on 25 October 2026. Not playtested in Studio yet: press Play and watch the Output window before you publish.

Prompt for RoCode

Generate terrain in my place with a script: a large island and a small one with sandy beaches, rolling grass hills, rocky snowy peaks, a lake with a bank on the big island and the sea around them, using Terrain:FillBlock, with settings for the seed and hill height.

On this page
  1. Get the script
  2. How it works
  3. 1. The script
  4. 2. Your own map
  5. 3. Editor tools
  6. Common errors
  7. With RoCode
  8. Questions
  9. Sources

How the generator works

The script asks one question for every 4-stud spot on the map: how high is the ground here? Everything else follows from the answer.

From a height to a landscape
StepWhat it does
Island mask1 minus the squared distance from the island centre, divided by its radius: 1 in the middle, 0 at the coast, so islands are round with soft edges
HillsTwo layers of math.noise: a broad one every 180 studs for big shapes and a small one every 45 studs for bumps
HeightThe mask times the hills, so hills are tallest inland and flatten into beaches
LakeInside the lake radius, the ground becomes a bowl up to 8 studs deep; just outside, a bank holds the water
MaterialSand below 3 studs, grass up to 45, rock up to 60, snow above
WaterOne slab of sea from the sea floor to sea level, then water up to the lake level inside the lake

math.noise(x, y, z) returns a smooth value, mostly between -0.5 and 0.5, that changes gently as its inputs change. Dividing the position by 180 makes it change slowly, which gives wide hills. Using the seed as the third input picks a different slice of the same noise.

Step 1: Run the generator

You can run the same code two ways:

  • Command Bar, once (recommended): open View, then Command Bar, paste the whole script and press Enter. Studio builds the terrain in edit mode, and it stays in your place when you save. Studio pauses while it works, which can take a minute for a big map.
  • Server Script, every start: insert a Script in ServerScriptService named TerrainGenerator. The terrain appears when you press Play, built one row per frame. The same seed gives the same island every time.
Script in ServerScriptService named TerrainGenerator
-- Script in ServerScriptService named TerrainGenerator (or paste it into the Command Bar,
-- View > Command Bar, to build the terrain once in edit mode and save it with the place).
-- Islands with hills, beaches, a lake and the sea around them, made of real Terrain.
local SEED = 42 -- change it for a different coastline and hills
local CELL = 4 -- studs per column; Terrain voxels are 4 studs
local SEA_LEVEL = 0
local SEA_FLOOR = -24
local HILL_HEIGHT = 70 -- the tallest hills reach about this high
local ISLANDS = {
	{ center = Vector2.new(0, 0), radius = 260 },
	{ center = Vector2.new(330, 190), radius = 90 },
}
local LAKE = { center = Vector2.new(-60, 40), radius = 50, level = 14 }
local CLEAR_FIRST = false -- true wipes ALL existing terrain first

local RunService = game:GetService("RunService")
local terrain = workspace.Terrain

-- 0 at the coast, 1 in the middle of the nearest island, with a soft edge
local function islandMask(x: number, z: number): number
	local best = 0
	for _, island in ISLANDS do
		local d = (Vector2.new(x, z) - island.center).Magnitude / island.radius
		best = math.max(best, 1 - d * d)
	end
	return best
end

-- Height of the ground at (x, z): island shape + two layers of noise for hills
local function heightAt(x: number, z: number): number
	local mask = islandMask(x, z)
	if mask <= 0 then
		return SEA_FLOOR
	end
	local broad = math.noise(x / 180, z / 180, SEED) * 0.5 + 0.5
	local detail = math.noise(x / 45, z / 45, SEED + 10) * 0.25
	local h = SEA_LEVEL - 6 + mask * (12 + (broad + detail) * HILL_HEIGHT * mask)
	-- the lake: a bowl up to 8 studs deep, with a bank around it so the water stays in
	local lakeDist = (Vector2.new(x, z) - LAKE.center).Magnitude / LAKE.radius
	if lakeDist < 1 then
		h = LAKE.level - 8 * (1 - lakeDist * lakeDist)
	elseif lakeDist < 1.4 then
		h = math.max(h, LAKE.level + 2)
	end
	return h
end

local function surfaceMaterial(h: number): Enum.Material
	if h < SEA_LEVEL + 3 then
		return Enum.Material.Sand
	elseif h < SEA_LEVEL + 45 then
		return Enum.Material.Grass
	elseif h < SEA_LEVEL + 60 then
		return Enum.Material.Rock
	end
	return Enum.Material.Snow
end

if CLEAR_FIRST then
	terrain:Clear()
end

-- The extent to cover: every island plus a margin of sea
local minX, maxX, minZ, maxZ = math.huge, -math.huge, math.huge, -math.huge
for _, island in ISLANDS do
	minX = math.min(minX, island.center.X - island.radius - 60)
	maxX = math.max(maxX, island.center.X + island.radius + 60)
	minZ = math.min(minZ, island.center.Y - island.radius - 60)
	maxZ = math.max(maxZ, island.center.Y + island.radius + 60)
end

-- The sea first: one slab of water, then the land replaces it where it rises
local seaSize = Vector3.new(maxX - minX, SEA_LEVEL - SEA_FLOOR, maxZ - minZ)
terrain:FillBlock(CFrame.new((minX + maxX) / 2, (SEA_LEVEL + SEA_FLOOR) / 2, (minZ + maxZ) / 2), seaSize, Enum.Material.Water)
terrain:FillBlock(CFrame.new((minX + maxX) / 2, SEA_FLOOR - 4, (minZ + maxZ) / 2), Vector3.new(seaSize.X, 8, seaSize.Z), Enum.Material.Sand)

for x = minX, maxX, CELL do
	for z = minZ, maxZ, CELL do
		local h = heightAt(x, z)
		if h > SEA_FLOOR then
			local bottom = SEA_FLOOR - 8
			local top = math.max(h - 4, bottom + 1)
			-- the body of the column, then 4 studs of surface on top
			terrain:FillBlock(CFrame.new(x, (bottom + top) / 2, z), Vector3.new(CELL, top - bottom, CELL), Enum.Material.Rock)
			terrain:FillBlock(CFrame.new(x, top + 2, z), Vector3.new(CELL, 4, CELL), surfaceMaterial(h))
			-- land below sea level stays under water; inside the lake, water up to its level
			local waterTop = if (Vector2.new(x, z) - LAKE.center).Magnitude < LAKE.radius then LAKE.level else SEA_LEVEL
			if h < waterTop then
				terrain:FillBlock(CFrame.new(x, (h + waterTop) / 2, z), Vector3.new(CELL, waterTop - h, CELL), Enum.Material.Water)
			end
		end
	end
	if RunService:IsRunning() then
		task.wait() -- in a running game, one row per frame so the server does not stall
	end
end
print("TerrainGenerator: done")

Step 2: Shape your own map

Settings and what they change
To getChange
One big islandKeep one entry in ISLANDS and raise its radius
An archipelagoAdd more entries to ISLANDS, each with its own centre and radius
Flatter land for buildingLower HILL_HEIGHT to 20 or 30
MountainsRaise HILL_HEIGHT to 120 and change the 180 in the broad noise to 260
No lakeMove LAKE far outside every island, such as center = Vector2.new(5000, 5000)
A desertIn surfaceMaterial, return Enum.Material.Sand for grass heights and Sandstone for rock

Want a flat spot for a lobby or a base? After the loop, call terrain:FillBlock with a wide, thin block of Grass at the height you want, then the same block with Enum.Material.Air just above it to clear anything sticking up.

Step 3: The Terrain Editor's own tools

Studio can generate terrain without code too, with the Terrain Editor:

  • Generate (Create tab): move the selection box over the area, pick biomes such as Mountains, Hills, Plains, Dunes, Arctic and Water, and press Generate.
  • Import (Create tab): turn a heightmap image, where brighter means higher, into terrain, with an optional colormap for materials.
  • Sculpt, Smooth, Flatten and Paint (Edit tab): touch up whatever the script or the Generate tool made.

A script is worth it when you want exact control: a lake in a set place, flat pads for buildings, the same island in every server, or a new map each round.

Common errors and fixes

If the terrain looks wrong or nothing appears, check these.

Nothing appears

Look at the Output for "TerrainGenerator: done". If you used a Script, the terrain appears only after you press Play. If you used the Command Bar, check you pasted the whole script.

Water floats in the air at the lake

The lake is too close to the coast, where the land is lower than the lake. Move LAKE further inland or lower its level.

Studio freezes for a long time

The map is big. Lower the island radiuses, or run it as a Script, which builds one row per frame.

The island is all snow

HILL_HEIGHT is high for the snow line at 60 studs. Lower HILL_HEIGHT, or raise the heights in surfaceMaterial.

Build it with RoCode

If you would rather describe the map than tune a generator, RoCode can add one to the place you have open. RoCode is an independent AI agent for Roblox Studio, not made by Roblox: you describe the change in a web chat, and its Studio plugin creates the scripts in your Explorer.

You type

Make the map a tropical island with sandy beaches, a few grass hills, one rocky mountain, a small lake and sea all round, with a flat grass area in the middle for the lobby.

RoCode does
  • Writes a terrain script like this one with your islands, hills and water, and adds it to ServerScriptService
  • Can read your place first so the flat areas fit what is already built
  • Is told to compile-check each script with Check Script, and sets undo points around each batch of changes

Terrain from a server script appears when you press Play. Walk around it in a playtest and ask for changes. How RoCode builds a game.

Questions

How do you generate terrain in Roblox Studio?

Use the Terrain Editor's Generate tool, which builds biomes from a seed, or a script that calls Terrain:FillBlock for every 4-stud column, as this guide does. The script gives you exact control over islands, hills and water.

How do I make an island in Roblox Studio?

Make the ground height depend on the distance from a centre point: high in the middle, below sea level at the edge. Fill the sea with water first, then fill land columns to that height. Step 1 has the full script.

How do I make a lake in Roblox terrain?

Lower the ground into a bowl inside the lake radius, raise a bank around it, and fill Water from the bowl floor up to the lake level.

Does scripted terrain save with my place?

Terrain built in edit mode, from the Command Bar, saves with the place. Terrain built by a Script during play is made again each time a server starts.

Sources and further reading

How the code was checked: every script on this page passes Luau's strict type checker against Roblox's API definitions (luau-lsp, 25 October 2026). A type check catches misspelt APIs and wrong types, not game logic, so playtest in Studio before you publish.

Try RoCode on your own place.

Free every day, no card required. Install the plugin, describe the feature, and review what lands in Studio.