Questions on how to make an infinite terrain generator

Before I begin I want to make clear that I know how octaves and Perlin noise work. The problem isn’t with rendering per se, more of finding out the logic in when to render a new “chunk” of terrain.

So far I can check if the player is 20 studs away from the edge with a little bit of modified AABB collision. However I stem a new problem, I don’t exactly know how I can make the “chunk” load in front of me. It seems to always clip inside the terrain. Here is my script, there are no references outside of it, everything rendered and processed is in this server script.

local resolution = 1
local size = 500

for x = 0, size, resolution do
	for z = 0, size, resolution do
		local height = (math.noise(x / 50, z / 50)) * 50
		local cframeData = CFrame.new(x - size/2, height - 250, z - size/2)
		workspace.Terrain:FillBlock(cframeData, Vector3.new(resolution, resolution, resolution), Enum.Material.Grass)
	end
end

game.Players.PlayerAdded:Connect(function(plr)
	local char = plr.Character or plr.CharacterAdded:Wait()
	while true do
		if char.PrimaryPart.Position.X > 230 or char.PrimaryPart.Position.Z > 230 or char.PrimaryPart.Position.X < -230 or char.PrimaryPart.Position.Z < -230 then
			for x = 0, size, resolution do
				for z = 0, size, resolution do
					local height = (math.noise(x / 50, z / 50)) * 50
					local cframeData = CFrame.new(char.PrimaryPart.Position.X - x, height - 250, char.PrimaryPart.Position.Z - z)
					workspace.Terrain:FillBlock(cframeData, Vector3.new(resolution, resolution, resolution), Enum.Material.Grass)
				end
			end
		end
		wait()
	end
end)

I am not asking you to rewrite my script, you are willing to by the way :stuck_out_tongue_winking_eye: I just want an explanation on the logic I need to do to achieve something similar to this Roblox Procedural Terrain Part 2 - Plains - YouTube

Here is the place if you want to check it out.

5 Likes

Based on what i read from your code, The for loops start from the x and z position of 0,0. If that is the case then couldn’t you start the chunk being generated at say 2,5? If so then whenever the player is close to the edge with your detection method, Find the x and z values of wherever you want it to generate (side of the already generated stuff but ahead of it) or something. Just an idea

2 Likes

Here is one, that I use, works good and is easily modifiable, you don’t have to use it, you can just look over it and find anything interesting that might help with yours

local Players = game:GetService("Players")

------------------------------------------------------------------------------------------------------------------------------------------------
 
local BASE_HEIGHT 		= 10				-- The main height factor for the terrain.
local CHUNK_SCALE 		= 1					-- The grid scale for terrain generation. Should be kept relatively low if used in real-time.
local RENDER_DISTANCE 	= 25				    -- 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 roundToOdd(n)
	--spawn(function()
	return math.floor(n - n % 3);
	--end)
end

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 * 3 + 1, roundToOdd((beginY + endY) * 3 / 1), z * 3 + 1)
	local size = Vector3.new(3, (endY - beginY) * 3, 3)
    local p = Instance.new("Part", workspace)
	p.Anchored = true
	p.CFrame = cframe
	p.Size = Vector3.new(3, 3, 3)
	p.Material = Enum.Material.Grass
	p.BrickColor = BrickColor.new("Forest green")
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 / 3 / CHUNK_SCALE), math.floor(location.Z / 3 / CHUNK_SCALE)
	local range = math.max(1, RENDER_DISTANCE / CHUNK_SCALE)
	for x = -range, range do
		for z = -range, range do
			local cx = chunkX + x
			local cz = chunkZ + z
			if not chunkExists(cx, cz) then
				makeChunk(cx, cz)
			end
		end
	end
end
 
game:GetService("RunService").Heartbeat:Connect(function()
	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
end)
11 Likes

Thanks! I was looking into something like this when I was brainstorming with my buddy on Discord. He didn’t really know how the equations worked, could you explain your process a bit?

1 Like