Chunk Generation is really laggy

Hi devs, I have had alot of problems with my chunk generation that I have made yesterday and I am calling out for help from you all.

  1. What do you want to achieve? Keep it simple and clear!
  • I want to achieve a performance boost in my chunk generation.
  1. What is the issue? Include screenshots / videos if possible!
  • No screenshots because there’s no need
  1. What solutions have you tried so far? Did you look for solutions on the Developer Hub?
  • Yes but they didn’t contain produceral generation, only chunks of buildings and stuff.
  • I also have tried parallel lua but nothing changed maybe there’s something in my script that causes the game to lag even with parallel lua

These are my scripts so far.

MODULE (not the entire thing):

local GeneratedTreePositions = {}
local DestroyedBlocks = {}

local NoiseGenerator = {
	CHUNK_SIZE = CHUNK_SIZE,
	BLOCK_SIZE = BLOCK_SIZE
}

function NoiseGenerator.BlockCulling(block)
	local Directions = {
		TOP = {name = "TOP"; dir = block.CFrame.UpVector * 3},
		BOTTOM = {name = "BOTTOM"; dir = block.CFrame.UpVector * -3},
		LEFT = {name = "LEFT"; dir = block.CFrame.RightVector * -3},
		RIGHT = {name = "RIGHT"; dir = block.CFrame.RightVector * 3},
		FRONT = {name = "FRONT"; dir = block.CFrame.LookVector * 3},
		BACK = {name = "BACK"; dir = block.CFrame.LookVector * -3},
	}

	local function destroyIfRaycastHit(direction)
		local raycastResult = workspace:Raycast(block.Position, direction.dir)
		if raycastResult then
			local part = block:FindFirstChild(direction.name)
			if part then
				part:Destroy()
			end
		end
	end

	for _, direction in pairs(Directions) do
		destroyIfRaycastHit(direction)
	end

	local blockCulled = Instance.new("BoolValue")
	blockCulled.Name = "IsCulled"
	blockCulled.Parent = block
end

function NoiseGenerator.GenerateChunk(chunkX, chunkZ, heartbeat)
	local chunkFolder = Instance.new("Folder")
	chunkFolder.Parent = workspace.Map
	chunkFolder.Name = "Chunk[" .. chunkX .. "-" .. chunkZ .. "]"

	local random = Random.new(SEED + chunkX * 31 + chunkZ * 17)

	NoiseGenerator.RemoveDestroyedBlock()

	for x = 1, CHUNK_SIZE do
		for z = 1, CHUNK_SIZE do
			local realX = (chunkX - 1) * CHUNK_SIZE + x
			local realZ = (chunkZ - 1) * CHUNK_SIZE + z

			local noise = math.noise(SEED, realX / 12, realZ / 12) * AMPLITUDE
			local blockPosition = Vector3.new(realX, math.floor(noise), realZ)
			local block = game.ReplicatedStorage.Items["Grass Block"]:Clone()

			workspace.DescendantRemoving:Connect(function(descendant)
				if descendant == block and not NoiseGenerator.IsBlockDestroyed(block) then
					NoiseGenerator.AddDestroyedBlock(block)
				end
			end)

			if block then
				block.Parent = chunkFolder
				block.Position = Vector3.new(realX * BLOCK_SIZE, math.floor(noise) * BLOCK_SIZE, realZ * BLOCK_SIZE)
				
				-- Place trees based on noise value and deterministic random
				if random:NextNumber() > 0.995 then
					local treeStem = game.ReplicatedStorage.Items.Trees:GetChildren()[random:NextInteger(1, #game.ReplicatedStorage.Items.Trees:GetChildren())]:Clone()  -- Clone a random tree from ReplicatedStorage
					
					local treePosition = Vector3.new(realX * BLOCK_SIZE, math.floor(noise) * BLOCK_SIZE + BLOCK_SIZE, realZ * BLOCK_SIZE)  -- Calculate the position of the tree
					
					treeStem:PivotTo(CFrame.new(treePosition))  -- Set the position of the tree
					treeStem.Parent = chunkFolder  -- Set the parent of the tree to the chunk folder

					local Parameters = RaycastParams.new()  -- Create a new RaycastParams object
					Parameters.FilterType = Enum.RaycastFilterType.Exclude  -- Set the filter type to Exclude
					Parameters.FilterDescendantsInstances = {treeStem}  -- Exclude the tree itself from raycast

					
					local HitRaycast = workspace:Raycast(treeStem:GetPivot().Position, Vector3.new(0, -5, 0), Parameters)  -- Perform a raycast to check for ground
					
					if HitRaycast then
						treeStem:PivotTo(CFrame.new(HitRaycast.Position))  -- Set the position of the tree to the ground
					end
				end

				-- Place trees based on noise value and deterministic random
				if random:NextNumber() > 0.8 then
					local grass = game.ReplicatedStorage.Items.Grass:Clone()  -- Clone a random tree from ReplicatedStorage
					
					local grassPosition = Vector3.new(realX * BLOCK_SIZE, math.floor(noise) * BLOCK_SIZE + BLOCK_SIZE, realZ * BLOCK_SIZE)  -- Calculate the position of the tree
					
					grass:PivotTo(CFrame.new(grassPosition))  -- Set the position of the tree
					grass.Parent = chunkFolder  -- Set the parent of the tree to the chunk folder

					local Parameters = RaycastParams.new()  -- Create a new RaycastParams object
					Parameters.FilterType = Enum.RaycastFilterType.Exclude  -- Set the filter type to Exclude
					Parameters.FilterDescendantsInstances = {grass}  -- Exclude the tree itself from raycast

					
					local HitRaycast = workspace:Raycast(grass:GetPivot().Position, Vector3.new(0, -5, 0), Parameters)  -- Perform a raycast to check for ground
					
					if HitRaycast then
						grass:PivotTo(CFrame.new(HitRaycast.Position))  -- Set the position of the tree to the ground
					end
				end
			end

			if heartbeat then
				coroutine.yield()
			end
		end
		
	end

	for _, v in pairs(chunkFolder:GetChildren()) do
		if v.Name == "Grass Block" then
			if not v:FindFirstChild("IsCulled") then
				NoiseGenerator.BlockCulling(v)
			end
		end
	end

	return chunkFolder
end

return NoiseGenerator

CLIENT:

local player = game.Players.LocalPlayer
local character = player.Character or player.CharacterAdded:Wait()
local rootPart = character:WaitForChild("HumanoidRootPart")

local chunkRadius = 2  -- Number of chunks to load around the player
local unloadDistance = chunkRadius  -- Distance to unload chunks

local NoiseGenerator = require(game.ReplicatedStorage.Modules:WaitForChild("NoiseGenerator"))

local loadedChunks = {}

local function getChunkCoords(position)
	local chunkX = math.floor(position.X / (NoiseGenerator.CHUNK_SIZE * NoiseGenerator.BLOCK_SIZE))
	local chunkZ = math.floor(position.Z / (NoiseGenerator.CHUNK_SIZE * NoiseGenerator.BLOCK_SIZE))
	return chunkX, chunkZ
end

-- Coroutine to load a chunk
local function loadChunkAsync(chunkX, chunkZ, Heartbeat)
	if not loadedChunks[chunkX] then
		loadedChunks[chunkX] = {}
	end

	if not loadedChunks[chunkX][chunkZ] then
		loadedChunks[chunkX][chunkZ] = NoiseGenerator.GenerateChunk(chunkX, chunkZ, Heartbeat)
	end
end

-- Coroutine to unload a chunk
local function unloadChunkAsync(chunkX, chunkZ)
	if loadedChunks[chunkX] and loadedChunks[chunkX][chunkZ] then
		loadedChunks[chunkX][chunkZ]:Destroy()
		loadedChunks[chunkX][chunkZ] = nil
	end
end

local function updateChunks()
	local currentChunkX, currentChunkZ = getChunkCoords(rootPart.Position)

	for chunkX, chunks in pairs(loadedChunks) do
		for chunkZ, _ in pairs(chunks) do
			if math.abs(chunkX - currentChunkX) > unloadDistance or math.abs(chunkZ - currentChunkZ) > unloadDistance then
				unloadChunkAsync(chunkX, chunkZ)
			end
		end
	end

	for x = -chunkRadius, chunkRadius do
		for z = -chunkRadius, chunkRadius do
			local chunkX = currentChunkX + x
			local chunkZ = currentChunkZ + z
			loadChunkAsync(chunkX, chunkZ, false)
		end
	end
end

game:GetService("RunService").Stepped:Connect(updateChunks)

-- Initial load
updateChunks()

I really need help.

A video of the script running and some more information would be helpful. What are the values of CHUNK_SIZE and BLOCK_SIZE?

One thing I see is that you are setting the block’s parent before setting the position. This causes the engine to calculate the physics for it twice. Another thing is that there is a memory leak in this script. You are creating a DescendantRemoving event connection but aren’t cleaning it up after the chunk is removed.

These are the Values

local SEED = workspace.SEED.Value
local MAP_SIZE = 25
local MAP_SCALE = MAP_SIZE / 2
local BLOCK_SIZE = 3
local CHUNK_SIZE = 10
local DIRT_LAYERS = 3
local STONE_LAYERS = 10
local AMPLITUDE = 6

and this is the video running it.
It just stops whenever I spawn and never resumes back
And I also have removed the Culling that’s why the blocks’ decals are weird looking.

It ran kind of well before I added some extra stuff like the culling and descendant (probably because of those stuff)

It’s useless If I’ll add that, I can’t even move so I don’t know If the chunks are loaded or unloaded.
I guess I’ll try some stuff around till I’ll find something or I’ll just wait for a fix from someone.

Are you testing your game outside of Roblox studio? The output should remain even if the game session is closed.

Roblox Studio but in the video i tested it in the Roblox Player

Tho i know the output will remain but it won’t print anything as i cannot move at all.

This might be your problem. You are using DescendantRemoving, one of the laggiest events, per block. Try using .Destroying instead.

Actually nevermind, the descendant stuff was the problem, i removed it and it works good again.
But could you help me to track the blocks that are getting destroyed so whenever i unload the chunk and load it back it in, the specific block wont get added back but get destroyed?

Yeah i’ll try that.

32342420 charssssss

The best method to use is to call NoiseGenerator.AddDestroyedBlock to whatever destroys the block rather than trying to detect it with events.

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.