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 (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
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
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.
| Step | What it does |
|---|---|
| Island mask | 1 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 |
| Hills | Two layers of math.noise: a broad one every 180 studs for big shapes and a small one every 45 studs for bumps |
| Height | The mask times the hills, so hills are tallest inland and flatten into beaches |
| Lake | Inside the lake radius, the ground becomes a bowl up to 8 studs deep; just outside, a bank holds the water |
| Material | Sand below 3 studs, grass up to 45, rock up to 60, snow above |
| Water | One 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 (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
| To get | Change |
|---|---|
| One big island | Keep one entry in ISLANDS and raise its radius |
| An archipelago | Add more entries to ISLANDS, each with its own centre and radius |
| Flatter land for building | Lower HILL_HEIGHT to 20 or 30 |
| Mountains | Raise HILL_HEIGHT to 120 and change the 180 in the broad noise to 260 |
| No lake | Move LAKE far outside every island, such as center = Vector2.new(5000, 5000) |
| A desert | In 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.
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.
- 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
- Roblox Creator Docs: Terrain. voxels, the Generate and Import tools, and scripting terrain
- Roblox Creator Docs: Terrain class. FillBlock, Clear and WriteVoxels
- Roblox Creator Docs: math library. math.noise
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.