How can I optimize a procedurally generated game?

You can write your topic however you want, but you need to answer these questions:

  1. *I am working on a game that is procedurally generated where the players are able to freely excavate and build in, and also be able to save their builds within CreatePlaceAsync().

  2. **I cannot find a way how I can make my game have less parts for their devices to be able to play without lagging

  3. **I have tried to union the parts, but I can’t quite figure out how I can let the players excavate trough the terrain

The game needs some sort of optimizing to allow any device run it on at least level 3 graphics

mapxsize = script.Parent.CFrame.X + 16
mapysize = script.Parent.CFrame.Y + 100
mapzsize = script.Parent.CFrame.Z + 16
seed = workspace.SEED.Value
noisescale = 30
amplitude = 20
psize = 3.054


for x=script.Parent.CFrame.X,mapxsize do
	for z=script.Parent.CFrame.Z,mapzsize do
		for y=script.Parent.CFrame.Y,mapysize do
			xnoise = math.noise(y/mapysize, z/mapzsize, seed)*amplitude
			znoise = math.noise(x/mapxsize, y/mapysize, seed)*amplitude
			ynoise = math.noise(x/mapxsize, z/mapzsize, seed)*amplitude

			density = xnoise + ynoise + znoise + y
			if density < 20 and density > 10 then
				p = Instance.new("Part", workspace.TerrainFolder)
				p.Anchored = true
				p.Size = Vector3.new(psize, psize, psize)
				p.CFrame = CFrame.new(x*psize, y*psize, z*psize)
				if p.CFrame.Y > 30 and p.CFrame.Y < 70 then
					local c = game.ReplicatedStorage.Blocks.Nature.Grass:Clone()
					c.Parent = workspace.TerrainFolder
					c:SetPrimaryPartCFrame(p.CFrame) 
					p:Destroy()
				elseif p.CFrame.Y > 70 and p.CFrame.Y < 100 then 
					local c = game.ReplicatedStorage.Blocks.Nature.Stone:Clone()
					c.Parent = workspace.TerrainFolder
					c:SetPrimaryPartCFrame(p.CFrame)
					p:Destroy()
				elseif p.CFrame.Y > 100 then
					local c = game.ReplicatedStorage.Blocks.Nature.Snow:Clone()
					c.Parent = workspace.TerrainFolder
					c:SetPrimaryPartCFrame(p.CFrame)
					p:Destroy()
				elseif p.CFrame.Y < 70 then
					local c = game.ReplicatedStorage.Blocks.Nature.Sand:Clone()
					c.Parent = workspace.TerrainFolder
					c:SetPrimaryPartCFrame(p.CFrame) 
					p:Destroy()

				end
			end
		end
	end
	wait()
end

NOTE: This code belongs in a normal script parented to a part.

1 Like

Here’s what I got using the code you posted:

One thing you can do to reduce part count is to only build the “surface” parts, since players will only ever be able to see or touch those.

Here’s a different example:

If players will only be walking on top of the terrain, there’s no point generating all the blocks below that. This also works if you’re not using straight up 2D heightmaps. The only blocks that need to be generated are the ones on the “surface” between inside and outside the terrain.

You can check if a position is on the boundary by seeing if it’s both solid and one or more of its neighbors is non-solid. That looks like so:

Boundary is in red.

Removing the non-boundary blocks we get

When a block gets “digged” or otherwise destroyed, update all the surrounding blocks and build them if they’ve become surface blocks as a result of the block being destroyed.

Here's the code I used to generate it:
mapxsize = 16
mapysize = 64
mapzsize = 64
seed = 0
noisescale = 1/64
amplitude = 10
psize = 3.054

function get_density(v3)
	return math.noise(v3.X * noisescale, v3.Y * noisescale, v3.Z * noisescale + seed)*amplitude
end

function is_on_boundary(v3)
	if get_density(v3) <= 0 then
		return false
	end
	
	for _, dirEnum in pairs(Enum.NormalId:GetEnumItems()) do
		local dir = Vector3.FromNormalId(dirEnum)
		local neighbor_pos = v3 + dir * psize
		local neighbor_density = get_density(neighbor_pos)
		
		if neighbor_density <= 0 then
			return true
		end
	end
	
	return false
end

for x=0,mapxsize do
	for z=0, mapzsize do
		for y=-mapysize/2, mapysize/2 do
			local pos = Vector3.new(x, y, z) * psize
			density = get_density(pos)
			
			if density > 0 and is_on_boundary(pos) then
				local c
				if pos.Y > 30 and pos.Y < 70 then
					c = game.ReplicatedStorage.Block1:Clone()
				elseif pos.Y > 70 and pos.Y < 100 then 
					c = game.ReplicatedStorage.Block2:Clone()
				elseif pos.Y > 100 then
					c = game.ReplicatedStorage.Block3:Clone()
				elseif pos.Y < 70 then
					c = game.ReplicatedStorage.Block4:Clone()
				end
				
				c.Parent = workspace.TerrainFolder
				c:SetPrimaryPartCFrame(CFrame.new(x*psize, y*psize, z*psize)) 
			end
		end
	end
	wait()
end

1 Like

Oh my god, It works like a charm! Thank you so much, I will try to find a way to divide the terrain into chunks, to load them individually and optimize even further!

But there is one issue, the game generates like an entire cave system

Allow me to elaborate

what

This is what I got from your code within a 16 x 100 x 16 block sized generation

1 Like

Glad I could help! :slight_smile:

I can’t tell what the issue is from the screenshot.

1 Like

The player is supposed to spawn in the top of the world, having the surface look like an actual minecraft world over view, but instead it looks like the minecraft far lands when generated within a 100 x 100 x 100 world

Or maybe your code is advanced enough to simulate the aftermath of turning your server into an anarchy server

2 Likes

Well, that’s not to do with the “only generate boundary blocks” approach, it’s just because your generator isn’t made to do what you want.

If you want tips for generating natural-looking terrain, that’s a different issue.

I need my script to both do this similar thing while also having a somewhat normal surface

1 Like

One way to do that is to make the density fall as the Y coordinate increases. Using that approach you can get something like

You can get some really cool overhangs

But also floating islands

If you tune it right you won’t get floaties but there’s a limit to the mountain intensity you can have with this approach.

If you want more extreme terrain with no floaties, check out this post:

And here’s some info on Perlin worm cave generation:

… although I wouldn’t recommend the Perlin worm thing, it’s complicated and doesn’t work that great unless you spend a lot of time tuning it.

EDIT: Here’s the code


local BLOCK_SIZE = 4
local MAP_SIZE = Vector3.new(128, 64, 128)
local MOUNTAIN_INTENSITY = 8

seed = 0
noisescale = 1/64
amplitude = 10

function get_density(v3)
	local noise_value = math.noise(v3.X * noisescale, v3.Y * noisescale, v3.Z * noisescale + seed)*amplitude
	local height_bonus = -v3.Y / MOUNTAIN_INTENSITY + 8
	
	return noise_value + height_bonus
end

function is_on_boundary(v3)
	if get_density(v3) <= 0 then
		return false
	end

	for _, dirEnum in pairs(Enum.NormalId:GetEnumItems()) do
		local dir = Vector3.FromNormalId(dirEnum)
		local neighbor_pos = v3 + dir * BLOCK_SIZE
		local neighbor_density = get_density(neighbor_pos)

		if neighbor_density <= 0 then
			return true
		end
	end

	return false
end

for x = 0, MAP_SIZE.X do
	for y = 0, MAP_SIZE.Y do
		for z = 0, MAP_SIZE.Z do
			local block_pos = Vector3.new(x, y, z) * BLOCK_SIZE
			local density = get_density(block_pos)
			
			if density > 0 and is_on_boundary(block_pos) then
				local block = script.Block:Clone()
				block.Parent = game.Workspace
				block.CFrame = CFrame.new(block_pos)
				
				if y > 25 then
					block.BrickColor = BrickColor.White()
				elseif y < 23 then
					block.BrickColor = BrickColor.Green()
				else
					block.Color = Color3.fromRGB(108, 88, 75)
				end
			end
		end
	end
	wait()
end
4 Likes

Hey man, just wanted to say that I am a huge fan of you, I even did a small effect where there is a neon block placing every single block as it loads

also, How do you get the neighboring parts of a part?

1 Like

Haha alright thanks man :slight_smile:

Do you mean how to see if a part is on the boundary? That’s the is_on_boundary function.

Alright thanks, I will need it for generating dirt instead of grass, and I might need help with that

If you want to make your terrain a bit “smoother”, try looking at this: devforum.roblox.com/t/929695


It isnt optimized but it looks great and will probably work for you. And the test place is un-copy locked…