Minecraft Terrain Generation ModuleScript (INEFFICIENT DO NOT USE)

(DO NOT USE THIS CODE, THIS CODE LOOPS THROUGH EVERY BLOCK OF A CHUNK WHICH IS 98304 BLOCKS FOR 1 CHUNK. I AM CURRENTLY MAKING A NEW ONE)

I made a really basic Minecraft Terrain Generation ModuleScript so that anyone could use it.
Note that it doesn’t include any biomes, structures or trees… Just ground and water.

Preparation

Create 4 main blocks in the workspace: GRASS_BLOCK, DIRT, STONE and WATER
Their names have to be like that.

Create a folder in ServerStorage and rename it “Blocks”.
Select the 4 blocks you created and parent them to that new folder.

Create a new ModuleScript in ServerScriptService and call it “TerrainGenerator”.
Create a new ModuleScript inside that one and call it “Blocks”.

Copy this script and paste it in the “Blocks” ModuleScript:

local folder = game.ServerStorage.Blocks

local blocks = {
	AIR = {
		transparent = true,
		noClip = true
	},
	DIRT = {
		transparent = false,
		noClip = false,
		block = folder.DIRT
	},
	GRASS_BLOCK = {
		transparent = false,
		noClip = false,
		block = folder.GRASS_BLOCK
	},
	STONE = {
		transparent = false,
		noClip = false,
		block = folder.STONE
	},
	WATER = {
		transparent = true,
		noClip = true,
		block = folder.WATER
	},
}

return blocks

And now copy this script and paste it in the “TerrainGenerator” ModuleScript:

local TerrainGenerator = {}

function TerrainGenerator.generate(SEED: number, octaves: number, amplitude: number, frequency: number, sizex: number, sizey: number, sizez: number, startx: number, starty: number, startz: number, blocksize: number, surfacelevel: number, sealevel: number): number
	SEED = SEED or Random.new():NextNumber(1, 100000)
	octaves = octaves or 4
	amplitude = amplitude or 20
	frequency = frequency or 50
	
	local startX = startx or 100
	local startY = starty or 100
	local startZ = startz or 100
	
	startx = startx or 100
	starty = starty or 100
	startz = startz or 100
	
	sizex = sizex or 16
	sizey = sizey or 384
	sizez = sizez or 16
	
	blocksize = blocksize or 3
	
	surfacelevel = surfacelevel or 112
	sealevel = sealevel or 109
	
	local surfaceY = surfacelevel
	local seaY = sealevel
	
	local blocks = require(script.Blocks)
	
	local lowestY = starty
	local highestY = starty + sizey * blocksize
	
	local function getBlock(x: number, y: number, z: number): table
		surfacelevel = surfaceY
		for i = 1, octaves do
			surfacelevel += math.noise(x / (frequency / i), z / (frequency / i), SEED) * (amplitude / i)
		end
		sealevel = seaY
		
		if y-blocksize < surfacelevel and y+blocksize > surfacelevel and y >= surfacelevel then
			return blocks.GRASS_BLOCK
		elseif y-blocksize < sealevel and y+blocksize > sealevel and y >= sealevel and y > surfacelevel then
			blocks.WATER.block.Size = Vector3.new(blocksize, (blocksize / 16) * 14, blocksize)
			
			return blocks.WATER
		end
		return blocks.AIR
	end
	
	local function placeBlock(x: number, y: number, z: number, block: table): Instance
		local clone = block.block:Clone()
		clone.Position = Vector3.new(x, y, z)
		clone.Parent = workspace
		
		return clone
	end
	
	local function checkBlocksAround(x: number, y: number, z: number): bool
		return getBlock(x + blocksize, y, z).transparent or getBlock(x - blocksize, y, z).transparent or getBlock(x, y, z + blocksize).transparent or getBlock(x, y, z - blocksize).transparent
	end
	
	local fillerBlocks = {}
	
	local function fillAir(x: number, y: number, z: number, block: table): nil
		if y + blocksize <= highestY then
			if not getBlock(x, y + blocksize, z).transparent then
				table.insert(fillerBlocks, {x, y, z, blocks.DIRT})
				for i, v in fillerBlocks do
					placeBlock(v[1], v[2], v[3], v[4])
				end
				table.clear(fillerBlocks)
			else
				if (not getBlock(x, y + (blocksize * 3), z).transparent) or (not getBlock(x, y + (blocksize * 2), z).transparent) then
					table.insert(fillerBlocks, {x, y, z, blocks.DIRT})
					fillAir(x, y + blocksize, z, blocks.DIRT)
					return
				end
				table.insert(fillerBlocks, {x, y, z, blocks.STONE})
				fillAir(x, y + blocksize, z, blocks.STONE)
			end
		else
			table.clear(fillerBlocks)
		end
	end
	
	local function fillWater(x: number, y: number, z: number): nil
		if y - blocksize >= lowestY then
			if getBlock(x, y - blocksize, z) == blocks.AIR then
				placeBlock(x, y - blocksize, z, blocks.WATER)
				
				fillWater(x, y - blocksize, z)
			end
		end
	end
	
	for x = 1, sizex do
		for z = 1, sizez do
			for y = 1, sizey do
				local block = getBlock(startx, starty, startz)
				if block == blocks.AIR then
					starty += blocksize
					continue
				end
				
				placeBlock(startx, starty, startz, block)
				blocks.WATER.block.Size = Vector3.new(blocksize, blocksize, blocksize)
				
				if block == blocks.GRASS_BLOCK then
					fillAir(startx + blocksize, starty + blocksize, startz, blocks.DIRT)
					fillAir(startx - blocksize, starty + blocksize, startz, blocks.DIRT)
					fillAir(startx, starty + blocksize, startz + blocksize, blocks.DIRT)
					fillAir(startx, starty + blocksize, startz - blocksize, blocks.DIRT)
				elseif block == blocks.WATER then
					fillWater(startx, starty, startz)
				end
				
				starty += blocksize
			end
			starty = startY
			startz += blocksize
		end
		startz = startZ
		startx += blocksize
	end
	
	return SEED
end

return TerrainGenerator

To use this generator, create a new script in ServerScriptService, and paste this in it:

local terrainGenerator = require(script.Parent.TerrainGenerator)

terrainGenerator.generate()

The terrainGenerator.generate() function wants 13 arguments:
SEED: number, octaves: number, amplitude: number, frequency: number, sizex: number, sizey: number, sizez: number, startx: number, starty: number, startz: number, blocksize: number, surfacelevel: number, sealevel: number
And returns the SEED: number

If one of these arguments is nil, it defaults to:

SEED: Random.new():NextNumber(1, 100000)
octaves: 4
amplitude: 20
frequency: 50
startx: 100
starty: 100
startz: 100
sizex: 16
sizey: 384
sizez: 16
blocksize: 3
surfacelevel: 112
sealevel: 109
3 Likes

You can try to utilize Parallel Lua to iterate through all the chunks faster. Also, you can use Octrees to speed up Magnitude distance checks.

2 Likes

What’s Parallel Lua and Octrees?

Parallel Lua is a system that Roblox made, which allows you to run multiple tasks at the same time, by running them on different CPU cores. This can be a bit complicated to figure out, but the performance benefit is real when the task is big.

Octree is a module by @Quenty, however the idea is probably not his. He just brought it to Roblox! It’s a recursive chunking system that makes searching for nearby objects much quicker at big scales.

Both of these fit a Minecraft style game!

2 Likes

Thanks! I’ll check on them later, they might be too hard for me to use but I’ll still try lol!

I actually think just a simple 3d grid would be more efficient. Octrees aren’t made for grid based games. Indexes would always be O(1), and data storage also O(1)

1 Like

I’m just gonna use a 2D grid cause I’m not really good with complex stuff.

I have a question: How would I use Parallel Lua for my script?