How to avoid lag when spawning lots of parts?

  1. What do you want to achieve? Keep it simple and clear!
    Hey guys, I am trying to make a voxel game with procedurally generated terrain, I want the map to be really big though so I need to spawn a lot of parts.

  2. What is the issue? Include screenshots / videos if possible!
    I am having a major issue with the fact that spawning in a lot of parts has drastic effects on the performance, here’s a video showing what happens: https://youtu.be/zCTt5wnAB3A
    and here’s a video of how the terrain itself looks: https://youtu.be/R3fKmUjC0Z4 , so I need some way of optimizing the blocks.

  3. What solutions have you tried so far? Did you look for solutions on the Developer Hub?
    I tried throttling the generation and changing the properties of the blocks (no shadows, set canTouch to false). I also tried switching to a system from this thread: Procedurally generated voxel world but that seemed rather slow and not quite what I was looking for

Here’s my generating script, since that’s probably useful

local Chunks = -1
local Tunnels = 5
local Seed = tick()
local ReplicatedStorage = game.ReplicatedStorage

function GenChunk (Boundx1, Boundy1, Boundz1, Boundx2, Boundy2, Boundz2)
	local Model = Instance.new("Model")
	Chunks += 1
	Model.Name = tostring(Chunks)
	
	Model.Parent = workspace.Map
	for i=Boundx1, Boundx2, 4 do
		for j=Boundy1, Boundy2, 4 do
			for k=Boundz1, Boundz2, 4 do
				local Part = ReplicatedStorage.Blocks.TestPart:Clone()
				Part.Anchored = true
				Part.Size = Vector3.new(4, 4, 4)
				Part.Position = Vector3.new(i, j, k)
				Part.Parent = Model
			end
		end
	end
end



local Resolution = 4
function GenWorm(len, x, y, z)
	Tunnels += 1
	local sX = math.noise(Tunnels/Resolution+.1,Seed)
	local sY = math.noise(Tunnels/Resolution+sX+.1,Seed)
	local sZ = math.noise(Tunnels/Resolution+sY+.1,Seed)
	local WormCF = CFrame.new(sX*500+x,sY*500+y,sZ*500+y)
	print("Worm "..Tunnels.." spawning at "..WormCF.X..", "..WormCF.Y..", "..WormCF.Z)
	local Dist = (math.noise(Tunnels/4+WormCF.p.magnitude,Seed)+.5)*2500
	for i = 1,Dist do
		local X,Y,Z = math.noise(WormCF.X/Resolution+.1,Seed),math.noise(WormCF.Y/Resolution+.1,Seed),math.noise(WormCF.Z/Resolution+.1,Seed)
		WormCF = WormCF*CFrame.Angles(X*2,Y*2,Z*2)*CFrame.new(0,0,-Resolution)
		local Touching = workspace:GetPartBoundsInBox(CFrame.new(WormCF.Position), Vector3.one*math.random(9,15), OverlapParams.new())
		for i = 1, #Touching-1 do
			Touching[i]:Remove()
		end

	end
	
end





local xc = 16
local yc = 16
local zc = 16
local rc = 16
wait(1)
while wait(.1) do
	if math.random(1,100+math.floor(math.sqrt(rc))*10) == 50 and rc > 50 then
		GenWorm(math.random(500,4000), (math.random(0,1)*2-1)*xc+8, (math.random(0,1)*2-1)*yc+8, (math.random(0,1)*2-1)*zc+8)
	end
	GenChunk(xc, yc, zc, xc+8, yc+8, zc+8)
	GenChunk(-xc, yc, zc, -xc+8, yc+8, zc+8)
	GenChunk(xc, -yc, zc, xc+8, -yc+8, zc+8)
	GenChunk(-xc, -yc, zc, xc+8, -yc+8, zc+8)
	
	GenChunk(xc, yc, -zc, xc+8, yc+8, -zc+8)
	GenChunk(-xc, yc, -zc, -xc+8, yc+8, -zc+8)
	GenChunk(xc, -yc, -zc, xc+8, -yc+8, -zc+8)
	GenChunk(-xc, -yc, -zc, xc+8, -yc+8, -zc+8)
	
	GenChunk(-zc, yc, xc, -zc+8, yc+8, xc+8)
	GenChunk(zc, yc, xc, zc+8, yc+8, xc+8)
	GenChunk(-zc, -yc, xc, -zc+8, -yc+8, xc+8)
	GenChunk(zc, -yc, xc, zc+8, -yc+8, xc+8)

	GenChunk(-zc, yc, -xc, -zc+8, yc+8, -xc+8)
	GenChunk(zc, yc, -xc, zc+8, yc+8, -xc+8)
	GenChunk(-zc, -yc, -xc, -zc+8, -yc+8, -xc+8)
	GenChunk(zc, -yc, -xc, zc+8, -yc+8, -xc+8)
	
	GenChunk(yc, zc, xc, yc+8, zc+8, xc+8)
	GenChunk(-yc, zc, xc, -yc+8, zc+8, xc+8)
	GenChunk(yc, -zc, xc, yc+8, -zc+8, xc+8)
	GenChunk(-yc, -zc, xc, yc+8, -zc+8, xc+8)
	
	GenChunk(yc, zc, -xc, yc+8, zc+8, -xc+8)
	GenChunk(-yc, zc, -xc, -yc+8, zc+8, -xc+8)
	GenChunk(yc, -zc, -xc, yc+8, -zc+8, -xc+8)
	GenChunk(-yc, -zc, -xc, yc+8, -zc+8, -xc+8)
	
	--GenChunk(xc, yc, -zc, xc+4, yc+4, -zc+4)
	xc += 12
	if(zc >= rc) then
		rc += 12
	end
	if(yc >= rc) then
		yc = 0
		zc += 12
	end
	if(xc >= rc) then
		xc = 0
		yc += 12
	end
	print("xc : "..tostring(xc).."yc : "..tostring(yc).."zc : "..tostring(zc))
end
1 Like

First of all, try avoiding Unions and make sure workspace.StreamingEnabled is on, you could also try checking the player’s position every so often and only creating the parts that are near the player.

1 Like

Yep thats what the first issue in your script @Booch0000test you should only do near player and not far the player to avoid lags and high cpu usage, and use streamingenabled as what @5smokin said could help alots.

1 Like

im gonna copy and paste another post since it also applies to this one lol

  • Are you spawing in individual large 16 stud Parts or a bunch of smaller Parts to fill each of those chunks? If they are large Parts it doesn’t look like there are that many being added.
  • At a rough guess how many Parts are you spawning in around a 100 stud square block around the player? If you are creating large chunks of small Parts I can see it being a lag issue.

In the video it almost looks like the script is placing overlapping Parts.

Thanks everyone for the replies! I ended up figuring out how to solve it.
-I started generating only a small area around the player
-I enabled streamingservice and set it to a low radius

Here’s my updated code in case anyone in the future ever has a similar issue and stumbles upon this thread:

local Chunks = -1
local Tunnels = 5
local Seed = tick()
local ReplicatedStorage = game.ReplicatedStorage
local ChunkFlag = ReplicatedStorage.Flags.MarkChunk
function GenChunk (Boundx1, Boundy1, Boundz1, Boundx2, Boundy2, Boundz2)
	local Model = Instance.new("Model")
	Chunks += 1
	Model.Parent = workspace.Map
	Model.Name = tostring(Chunks)
	
	local Flag = ReplicatedStorage.Flags.MarkChunk:Clone()
	Flag.Position = Vector3.new(Boundx1, Boundy1, Boundz1)
	Flag.Parent = Model
	--print(Flag)
	
	
	for i=Boundx1, Boundx2, 4 do
		for j=Boundy1, Boundy2, 4 do
			for k=Boundz1, Boundz2, 4 do
				local Touching = workspace:GetPartBoundsInBox(CFrame.new(Vector3.new(i, j, k)), Vector3.one, OverlapParams.new())
				--print("AAAA")
				--print(tablefindbutgood(Touching, "AntiBlock"))
				--print("BBBB")
				if tablefindbutgood(Touching, "AntiBlock") == nil then
					local Part = ReplicatedStorage.Blocks.TestPart:Clone()
					Part.Position = Vector3.new(i, j, k)
					Part.Parent = Model
				end	
				
			end
		end
	end
end

local Resolution = 4
function GenWorm(len, x, y, z)
	Tunnels += 1
	local sX = math.noise(Tunnels/Resolution+.1,Seed)
	local sY = math.noise(Tunnels/Resolution+sX+.1,Seed)
	local sZ = math.noise(Tunnels/Resolution+sY+.1,Seed)
	local WormCF = CFrame.new(sX*500+x,sY*500+y,sZ*500+y)
	print("Worm "..Tunnels.." spawning at "..WormCF.X..", "..WormCF.Y..", "..WormCF.Z)
	local Dist = (math.noise(Tunnels/4+WormCF.p.magnitude,Seed)+.5)*500*len
	for i = 1,Dist do
		print(i)
		local X,Y,Z = math.noise(WormCF.X/Resolution+.1,Seed),math.noise(WormCF.Y/Resolution+.1,Seed),math.noise(WormCF.Z/Resolution+.1,Seed)
		WormCF = WormCF*CFrame.Angles(X*2,Y*2,Z*2)*CFrame.new(0,0,-Resolution)
		local part = ReplicatedStorage.Blocks.AntiBlock:Clone()
		part.Parent = workspace.Map.WhiteSpace
		part.Position = WormCF.Position
		local randint = math.random(9,15)
		part.Size = Vector3.new(randint, randint, randint)
	end

end

function tablefindbutgood(t, target)
	for i = 1, #t-1 do
		if t[i].Name == target then
			return i
		end
	end 
	return nil
end

function CheckAndGenChunk (Boundx1, Boundy1, Boundz1, Size)
	local Touching = workspace:GetPartBoundsInBox(CFrame.new(Vector3.new(Boundx1, Boundy1, Boundz1)), Vector3.one*4, OverlapParams.new())
	--print("AAAA")
	--print(tablefindbutgood(Touching, "MarkChunk"))
	--print("BBBB")
	if tablefindbutgood(Touching, "MarkChunk") == nil then
		GenChunk(Boundx1, Boundy1, Boundz1, Boundx1 + Size, Boundy1 + Size, Boundz1 + Size)
	end	
	
end





function SnapToGrid(x, y, z)
	return Vector3.new(x-(x%24), y-(y%24), z-(z%24))
end



local xc = 12
local yc = 12
local zc = 12
local rc = 12
local rad = 20
GenChunk(4, 4, 4, 8, 8, 8)


GenWorm(5, 0, 0, 0)
GenWorm(5, 0, 0, 0)
GenWorm(5, 0, 0, 0)
GenWorm(5, 0, 0, 0)


wait(1)
while wait(.05) do
	if math.random(1,100+math.floor(math.sqrt(rc))*10) == 50 and rc > 50 then
		GenWorm(math.random(500,4000), (math.random(0,1)*2-1)*xc+8, (math.random(0,1)*2-1)*yc+8, (math.random(0,1)*2-1)*zc+8)
	end
	for i,v in pairs(game.Players:GetChildren()) do
		-- v = player
		if v.Character then 
			if v.Character:FindFirstChild("HumanoidRootPart") then
				local pos = v.Character.HumanoidRootPart.Position
				pos = SnapToGrid(pos.X, pos.Y, pos.Z)
				--print(pos)
				CheckAndGenChunk(pos.X, pos.Y-16, pos.Z+24, rad)
				CheckAndGenChunk(pos.X, pos.Y-16, pos.Z, rad)
				CheckAndGenChunk(pos.X, pos.Y-16, pos.Z-24, rad)
				
				CheckAndGenChunk(pos.X+24, pos.Y-16, pos.Z+24, rad)
				CheckAndGenChunk(pos.X+24, pos.Y-16, pos.Z, rad)
				CheckAndGenChunk(pos.X+24, pos.Y-16, pos.Z-24, rad)
				
				CheckAndGenChunk(pos.X-24, pos.Y-16, pos.Z+24, rad)
				CheckAndGenChunk(pos.X-24, pos.Y-16, pos.Z, rad)
				CheckAndGenChunk(pos.X-24, pos.Y-16, pos.Z-24, rad)
					
					
				CheckAndGenChunk(pos.X, pos.Y+8, pos.Z+24, rad)

				CheckAndGenChunk(pos.X, pos.Y+8, pos.Z-24, rad)

				CheckAndGenChunk(pos.X+24, pos.Y+8, pos.Z+24, rad)

				CheckAndGenChunk(pos.X+24, pos.Y+8, pos.Z-24, rad)

				CheckAndGenChunk(pos.X-24, pos.Y+8, pos.Z+24, rad)

				CheckAndGenChunk(pos.X-24, pos.Y+8, pos.Z-24, rad)
				
				
				CheckAndGenChunk(pos.X-24, pos.Y+8, pos.Z, rad)

				CheckAndGenChunk(pos.X+24, pos.Y+8, pos.Z, rad)
				
			
				CheckAndGenChunk(pos.X, pos.Y+32, pos.Z+24, rad)
				CheckAndGenChunk(pos.X, pos.Y+32, pos.Z, rad)
				CheckAndGenChunk(pos.X, pos.Y+32, pos.Z-24, rad)

				CheckAndGenChunk(pos.X+24, pos.Y+32, pos.Z+24, rad)
				CheckAndGenChunk(pos.X+24, pos.Y+32, pos.Z, rad)
				CheckAndGenChunk(pos.X+24, pos.Y+32, pos.Z-24, rad)

				CheckAndGenChunk(pos.X-24, pos.Y+32, pos.Z+24, rad)
				CheckAndGenChunk(pos.X-24, pos.Y+32, pos.Z, rad)
				CheckAndGenChunk(pos.X-24, pos.Y+32, pos.Z-24, rad)
				
				--local Part = Instance.new("Part")
				--Part.Parent = workspace
				--Part.Position = SnapToGrid(pos.X, pos.Y, pos.Z)
			end
		end
	end
	
	
	--GenChunk(xc, yc, -zc, xc+4, yc+4, -zc+4)
	--[[xc += 12
	if(zc >= rc) then
		rc += 12
	end
	if(yc >= rc) then
		yc = 0
		zc += 12
	end
	if(xc >= rc) then
		xc = 0
		yc += 12
	end
	print("xc : "..tostring(xc).."yc : "..tostring(yc).."zc : "..tostring(zc))]]
end


--wait(10)
--for i=0, 15, 1 do
--	GenWorm(0, 32, 0)
--end

--GenChunk(0, 0, 0, 16, 16, 16)

This requires your ReplicatedStorage to have folders “Blocks” and “Flags”. TestPart is just any sized part that you want to generate the chunks with. MarkChunk is just a really small part, it functions as a flag that there is a chunk generated at it’s location. AntiBlock is the same as TestPart, but it should be transparent (make sure it doesn’t collide, it should still query.), AntiBlock is basically whitespace and is used to generate caves. The workspace should have a model “Map” with another model as a child of it titled “WhiteSpace”. That should be all I think.