Terrain Island Generator

This script will generate a basic island using terrain for you, with easy configuration to help you create a basic starting place for any terrain based map. This script isnt going to give you a state of the art map and is in no way as detailed as it could be but it does give you a quick and easy way to get started.

Example generations:

Default Settings



Increased Hill Density
Random Configerations

The code:


-- SERVICES
local TERRAIN = workspace.Terrain

-- ##   CONFIGURATION

-- GENERATION OFFSET (Change these to place a new island without deleting old ones)
local OFFSET_X = 0
local OFFSET_Z = 0

-- MAP SIZE (Must be a multiple of 4, 1024 XZ is maximum)
local MAP_SIZE_XZ = 1024
local MAP_MAX_HEIGHT = 256

-- SEED (Set a specific number for the same island every time, or leave random)
local SEED = math.random(1, 100000)

-- WATER & SHORE
local WATER_LEVEL = 64      -- The height of the water in studs.
local SAND_BEACH_HEIGHT = 4 -- How many studs high the sand beaches are above the water.
local WATER_BORDER_WIDTH = 32 -- How wide the water channel around the map edge should be.
local SHORE_WIDTH = 150     -- The width of the shores for a gentle slope.

-- ISLAND SHAPE & BASE
local ISLAND_ROUNDNESS = 4    -- Controls the island shape. 2 is a perfect circle, higher numbers are more square.
local ISLAND_BASE_HEIGHT = 70 -- The base height of the flat parts of the island.

-- HILL GENERATION
local HILL_DENSITY = 0.4      -- How much of the island is hilly (0 = all flat, 1 = all rugged).
local HILL_MASK_SCALE = 450   -- The size of the hilly/flat regions. Larger = bigger patches.
local NOISE_SCALE = 80       -- The size of the individual hills within the rugged patches.
local NOISE_AMPLITUDE = 250    -- How high the hills are.

-- COASTLINE DETAILING
local COASTLINE_NOISE_SCALE = 90  -- The size of the coastal features.
local COASTLINE_NOISE_INTENSITY = 25 -- How much the noise affects the coast.

-- ##  GENERATION LOGIC

print(string.format("Starting new island generation at offset (%d, %d) with seed: %d", OFFSET_X, OFFSET_Z, SEED))


local RESOLUTION = 4
local resX, resY, resZ = MAP_SIZE_XZ / RESOLUTION, MAP_MAX_HEIGHT / RESOLUTION, MAP_SIZE_XZ / RESOLUTION

local materials, occupancies = {}, {}
for x = 1, resX do
	materials[x], occupancies[x] = {}, {}
	for y = 1, resY do
		materials[x][y], occupancies[x][y] = {}, {}
	end
end

local islandCenterX = (MAP_SIZE_XZ / 2) + OFFSET_X
local islandCenterZ = (MAP_SIZE_XZ / 2) + OFFSET_Z

local outerRadius = (MAP_SIZE_XZ / 2) - WATER_BORDER_WIDTH
local plateauRadius = outerRadius - SHORE_WIDTH

for x = 1, resX do
	for z = 1, resZ do
		-- Apply the offset to get the correct world coordinates
		local worldX, worldZ = (x * RESOLUTION) + OFFSET_X, (z * RESOLUTION) + OFFSET_Z

		local dx = math.abs(worldX - islandCenterX)
		local dz = math.abs(worldZ - islandCenterZ)
		local baseDist = (dx^ISLAND_ROUNDNESS + dz^ISLAND_ROUNDNESS)^(1/ISLAND_ROUNDNESS)

		local coastlineNoiseX = worldX / COASTLINE_NOISE_SCALE + SEED
		local coastlineNoiseZ = worldZ / COASTLINE_NOISE_SCALE + SEED
		local coastlineNoise = math.noise(coastlineNoiseX, coastlineNoiseZ) * COASTLINE_NOISE_INTENSITY

		local finalDist = baseDist - coastlineNoise

		local shore_alpha = math.clamp((finalDist - plateauRadius) / SHORE_WIDTH, 0, 1)
		local falloff = 1 - shore_alpha

		local baseIslandHeight = ISLAND_BASE_HEIGHT * falloff

		local hillMaskNoiseX = worldX / HILL_MASK_SCALE + (SEED * 2)
		local hillMaskNoiseZ = worldZ / HILL_MASK_SCALE + (SEED * 2)
		local hillMask = (math.noise(hillMaskNoiseX, hillMaskNoiseZ) + 1) / 2

		local hillHeight = 0
		if hillMask > (1 - HILL_DENSITY) then
			local terrainNoiseCoordX, terrainNoiseCoordZ = (worldX / NOISE_SCALE) + SEED, (worldZ / NOISE_SCALE) + SEED
			local terrainNoise = (math.noise(terrainNoiseCoordX, terrainNoiseCoordZ) + 1) / 2

			local hillIntensity = math.clamp((hillMask - (1 - HILL_DENSITY)) / HILL_DENSITY, 0, 1)
			hillHeight = terrainNoise * NOISE_AMPLITUDE * falloff * hillIntensity
		end

		local finalHeight = baseIslandHeight + hillHeight

		for y = 1, resY do
			local worldY = y * RESOLUTION
			local material, occupancy = Enum.Material.Air, 0

			if worldY <= finalHeight then
				occupancy = 1
				local depth = finalHeight - worldY
				if finalHeight > WATER_LEVEL and finalHeight < WATER_LEVEL + SAND_BEACH_HEIGHT then
					material = Enum.Material.Sand
				elseif finalHeight > WATER_LEVEL and depth < 8 then
					material = Enum.Material.Grass
				elseif finalHeight > WATER_LEVEL - 5 and depth < 40 then
					material = Enum.Material.Ground
				else
					material = Enum.Material.Rock
				end
			elseif worldY <= WATER_LEVEL then
				occupancy = 1
				material = Enum.Material.Water
			end

			materials[x][y][z] = material
			occupancies[x][y][z] = occupancy
		end
	end
	if x % math.floor(resX / 20) == 0 then
		print(string.format("Generating data... %.0f%%", (x / resX) * 100))
	end
end

print("Writing voxels...")
-- Define the generation region using the offset
local regionStart = Vector3.new(OFFSET_X, 0, OFFSET_Z)
local regionSize = Vector3.new(MAP_SIZE_XZ, MAP_MAX_HEIGHT, MAP_SIZE_XZ)
local region = Region3.new(regionStart, regionStart + regionSize)

TERRAIN:WriteVoxels(region, RESOLUTION, materials, occupancies)

print("New island generated successfully!")

General info:
The max size is X+Z of 2048, so the 1024 it is currently set to is the maximum, though you can simply change the offset by at least the size of your island (in the default case, 1024) to generate another next to it, select that new terrain and move it with the terrain tools to combine the islands as can be seen here:

The code is intended to be run in the command bar, simply copy and paste the script into there and press enter, ctrl z will undo the generation, allowing you to retry until you get a seed you like.

3 Likes

You might also want to try