Chunk generation wont save parts

I’m trying to make a server script where it generates blocks and what I have now is not saving the chunks, I am trying to make it save and generate ‘forever’. I have been trying multiple different strategies such as datastore but nothing quite fixed that. Once a part inside one of the folders is added or destroyed from the player destroying it or placing it, it should save that chunk once they leave the area and come back it should stay the same as it was. The generation should be like Minecraft, it saves and it has chunks. Here is the server script below:

local Players = game:GetService("Players")
local ServerStorage = game:GetService("ServerStorage")
local RunService = game:GetService("RunService")

local BlocksFolder = ServerStorage:WaitForChild("BlocksFolder")
local GrassBlock = BlocksFolder:WaitForChild("GrassBlock")
local WorkspaceBlocks = workspace:FindFirstChild("Blocks") or Instance.new("Folder", workspace)
WorkspaceBlocks.Name = "Blocks"

local CHUNK_SIZE = 1
local RENDER_DISTANCE = 25

local activeChunks = {}

local function findBlockAtPosition(position)
	local roundedPos = Vector3.new(math.round(position.X), math.round(position.Y), math.round(position.Z))
	local chunkX = math.floor(roundedPos.X / (CHUNK_SIZE * 3.5))
	local chunkZ = math.floor(roundedPos.Z / (CHUNK_SIZE * 3.5))

	local chunkFolder = activeChunks[chunkX] and activeChunks[chunkX][chunkZ]
	if chunkFolder then
		for _, child in ipairs(chunkFolder:GetChildren()) do
			if (child.Position - position).Magnitude < 1 then
				return child
			end
		end
	end
	return nil
end

local function updateBlockGUIs(block)
	local neighbors = {
		Vector3.new(block.Position.X + 3.5, 0, block.Position.Z),
		Vector3.new(block.Position.X - 3.5, 0, block.Position.Z),
		Vector3.new(block.Position.X, 0, block.Position.Z + 3.5),
		Vector3.new(block.Position.X, 0, block.Position.Z - 3.5),
		Vector3.new(block.Position.X, 3.5, block.Position.Z),
		Vector3.new(block.Position.X, -3.5, block.Position.Z),
	}

	local guis = {
		"RightGui", "LeftGui", "BackGui", "FrontGui", "TopGui", "BottomGui"
	}

	for i, pos in ipairs(neighbors) do
		local neighborBlock = findBlockAtPosition(pos)
		if neighborBlock then
			local myGui = block:FindFirstChild(guis[i])
			local neighborGui = neighborBlock:FindFirstChild(guis[i == 1 and 2 or (i == 2 and 1 or (i == 3 and 4 or (i == 4 and 3 or (i == 5 and 6 or 5))))])
			if myGui then
				myGui.Enabled = false
			end
			if neighborGui then
				neighborGui.Enabled = false
			end
		end
	end
end

local function updateNeighborsGUIs(block)
	local neighbors = {
		Vector3.new(block.Position.X + 3.5, 0, block.Position.Z),
		Vector3.new(block.Position.X - 3.5, 0, block.Position.Z),
		Vector3.new(block.Position.X, 0, block.Position.Z + 3.5),
		Vector3.new(block.Position.X, 0, block.Position.Z - 3.5),
		Vector3.new(block.Position.X, 3.5, block.Position.Z),
		Vector3.new(block.Position.X, -3.5, block.Position.Z),
	}

	local guis = {
		"LeftGui", "RightGui", "FrontGui", "BackGui", "BottomGui", "TopGui"
	}

	for i, pos in ipairs(neighbors) do
		local neighborBlock = findBlockAtPosition(pos)
		if neighborBlock then
			local neighborGui = neighborBlock:FindFirstChild(guis[i])
			if neighborGui then
				neighborGui.Enabled = true
			end
		end
	end
end

local function generateChunk(cx, cz)
	if activeChunks[cx] and activeChunks[cx][cz] then
		return
	end

	local newChunk = Instance.new("Folder")
	newChunk.Name = string.format("Chunk_%d_%d", cx, cz)
	newChunk.Parent = WorkspaceBlocks

	for x = 0, CHUNK_SIZE - 1 do
		for z = 0, CHUNK_SIZE - 1 do
			local clone = GrassBlock:Clone()
			clone.Parent = newChunk
			clone.Position = Vector3.new((cx * CHUNK_SIZE + x) * 3.5, 0, (cz * CHUNK_SIZE + z) * 3.5)
			clone.Orientation = Vector3.new(0, math.random(0,3)*90, 0)
			clone.CanCollide = true
			clone.CanTouch = false
			clone.CanQuery = false
		end
	end

	if not activeChunks[cx] then
		activeChunks[cx] = {}
	end
	activeChunks[cx][cz] = newChunk

	for _, block in ipairs(newChunk:GetChildren()) do
		updateBlockGUIs(block)
	end
end

local function despawnChunk(cx, cz)
	if activeChunks[cx] and activeChunks[cx][cz] then
		activeChunks[cx][cz]:Destroy()
		activeChunks[cx][cz] = nil
	end
end

RunService.Stepped:Connect(function()
	local playersInChunks = {}

	for _, player in ipairs(Players:GetPlayers()) do
		if player and player.Character and player.Character:FindFirstChild("HumanoidRootPart") then
			local playerPos = player.Character.HumanoidRootPart.Position

			local currentChunkX = math.floor(playerPos.X / (CHUNK_SIZE * 3.5))
			local currentChunkZ = math.floor(playerPos.Z / (CHUNK_SIZE * 3.5))

			playersInChunks[string.format("%d,%d", currentChunkX, currentChunkZ)] = true

			for x = -RENDER_DISTANCE, RENDER_DISTANCE do
				for z = -RENDER_DISTANCE, RENDER_DISTANCE do
					generateChunk(currentChunkX + x, currentChunkZ + z)
				end
			end
		end
	end

	for cx, zTable in pairs(activeChunks) do
		for cz, chunkFolder in pairs(zTable) do
			local isNearAnyPlayer = false
			for _, player in ipairs(Players:GetPlayers()) do
				if player and player.Character and player.Character:FindFirstChild("HumanoidRootPart") then
					local playerPos = player.Character.HumanoidRootPart.Position
					local currentChunkX = math.floor(playerPos.X / (CHUNK_SIZE * 3.5))
					local currentChunkZ = math.floor(playerPos.Z / (CHUNK_SIZE * 3.5))

					local distanceX = math.abs(cx - currentChunkX)
					local distanceZ = math.abs(cz - currentChunkZ)

					if distanceX <= RENDER_DISTANCE and distanceZ <= RENDER_DISTANCE then
						isNearAnyPlayer = true
						break
					end
				end
			end

			if not isNearAnyPlayer then
				despawnChunk(cx, cz)
			end
		end
	end
end)
WorkspaceBlocks.DescendantAdded:Connect(function(child)
	if child:IsA("BasePart") then
		updateBlockGUIs(child)
	end
end)

WorkspaceBlocks.DescendantRemoving:Connect(function(child)
	if child:IsA("BasePart") then
		updateNeighborsGUIs(child)
	end
end)
1 Like

are the chunks like actual blocks or like randomly generated terrain?

They are parts with decals on all sides per block, the chunks have multiple blocks

Sorry, if thats the case I can’t help with that.

Though I don’t know how wide your chunk is, I have an idea. Assigning a number to each sort of block and save the string in datastore.
For example, thinking of 3×3×3 chunk, it can be saved like this: 332412521112342411335222245
This may be not efficient.

1 Like

I don’t have time to help you in more depth, but take a look at “random.new(seed)” as we usually create procedural generation, so we only need to save one seed, minecraft for example uses seeds

1 Like

I’ve worked on my own infinitely-generating chunk-based map, so I can definitely give some advice here.

What I did for object permanence, which is what you’re talking about, is I added a modifier table to the main chunk generation script. When a chunk is generated, it checks if that chunk coordinate has a key in the modifiers table, and if it does, then it applies a modification to the chunk post-generation.

Modifications would be set server-side, and all it entails is having some bool, number, or string value that corresponds to a certain function in the chunk generation script to do some stuff. For example, since my game supports structures, I would have a structure script add a modification via the chunk generation ModuleScript, which gave a structureID number value to a certain chunk coordinate. When that modification is set, I have the chunk generation script reload the chunk with the changes, adding the structure with the structureID.

The benefit of this method is that your changes are totally reversible, if that’s useful to you. If you’re doing a game like Minecraft where each block can be changed, then I would have an array of each block coordinate relative to the chunk, pointing to a certain block ID (for example, wood at 2,2,2 would be [2,2,2] = "wood". For even better optimization, you can avoid creating that array for unmodified chunks, and instead, you can only treat that array as a modifications table, where each key is a modification applied post-generation. Changing blocks would be as easy as replacing whatever block is at 2,2,2 with “wood”.

Be careful with memory leaks and anything that can infinitely compound. Even though my game has an infinitely-generating map that can theoretically go on forever, it has a set threshold on how many chunks can be loaded before the furthest are trimmed. I’d highly recommend setting a limit on how many loaded chunks you can have in your game. This doesn’t mean you have to delete any saved modification data; rather, you can unload any instances and load them later, and set a bigger threshold for modification data since it’s lightweight.

Hope this helps. Feel free to ask me questions since I’ve already gone through the trouble of making these systems.