Hello, I am trying to make a voxel building game with procedurally generated terrain (a.k.a. Minecraft Classic). The world is made out of 4x4x4 blocks, which means that there are going to be a lot of blocks. So I split each 16x128x16 section of the world into chunks. And I am making it so that the client can only see nearby chunks, so I can make the world bigger without making it too laggy.
How it works:
- Chunks on the client farther than the threshold get deleted
- The client asks the server for the nearby chunks
- Each request is put into a queue that gets interpreted every 0.1 seconds. The latest request is prioritized over others.
- The server gets the chunk model for the requested chunk. If it doesn’t exist, then the chunk model is created and parented to the server camera (so it doesn’t replicate). A clone of the chunk is created for the player.
- The cloned chunk gets parented to the player’s StarterGear (so it only replicates to the targeted client).
- The client knows that it has received the chunk and gets parented to the Workspace.
But for some reason when I test it, some of the server chunk models get deleted, even though there are no :Destroy() or :Remove() calls done on a chunk.
Server
replicatedStorage.GetChunk.OnServerInvoke = function(player, cx, cy)
table.insert(chunkProviderQueue, {player, cx, cy})
while chunkProviderReturn[player] == nil do wait() end
local model = chunkProviderReturn[player]
if model == false then
chunkProviderReturn[player] = nil
return nil
else
model.Parent = player.StarterGear --kinda hacky lol
chunkProviderReturn[player] = nil
return model.Name
end
end
--provide chunks for client
while wait(0.1) do
if #chunkProviderQueue > 0 then
local chunkPos = chunkProviderQueue[#chunkProviderQueue] --prioritize clients who asked last
local player = chunkPos[1]
local chunkX = chunkPos[2]
local chunkY = chunkPos[3]
table.remove(chunkProviderQueue, #chunkProviderQueue)
if (
chunkX < 0 or chunkX > globals.WORLD_WIDTH - 1 or
chunkY < 0 or chunkY > globals.WORLD_HEIGHT - 1
)
then
chunkProviderReturn[player] = false
continue
end
--print("chunk " .. chunkPos[2] .. " " .. chunkPos[3])
local chunkModel = chunkModels:FindFirstChild("chunk " .. chunkX .. " " .. chunkY)
if chunkModel == nil then
createChunk(chunkX, chunkY)
--craete neighbor chunks to fix chunk edges
if chunkX > 0 then createChunk(chunkX - 1, chunkY) end
if chunkX < globals.WORLD_WIDTH - 1 then createChunk(chunkX + 1, chunkY) end
if chunkY > 0 then createChunk(chunkX, chunkY - 1) end
if chunkY < globals.WORLD_HEIGHT - 1 then createChunk(chunkX, chunkY + 1) end
local serverChunkModel = createChunkModel(chunkX, chunkY)
serverChunkModel.Parent = chunkModels
chunkModel = serverChunkModel:Clone()
end
chunkProviderReturn[player] = chunkModel
end
end
Client:
while wait(0.5) do
if character ~= nil and character.Parent == workspace and character:FindFirstChild("Head") ~= nil then
local head = character.Head
local charPosX = math.floor(head.Position.X / (globals.CHUNK_SIZE_X * globals.BLOCK_SIZE))
local charPosY = math.floor(head.Position.Z / (globals.CHUNK_SIZE_Z * globals.BLOCK_SIZE))
print(charPosX, charPosY)
--delete chunks farther than render dist
--for now, find/create chunks in a square area. will make it a circle area later.
for x in pairs(loadedChunks) do
for y in pairs(loadedChunks) do
local newx = math.abs(x - charPosX)
local newy = math.abs(y - charPosY)
local v = loadedChunks[x][y]
if newx < -RENDER_DIST / 2 and newx > RENDER_DIST / 2 and newy < -RENDER_DIST / 2 and newy > RENDER_DIST / 2 then
--v:Destroy()
--loadedChunks[x][y] = nil
end
end
end
--create chunks inside render dist
for x=-RENDER_DIST / 2, RENDER_DIST / 2 do
for y=-RENDER_DIST / 2, RENDER_DIST / 2 do
if loadedChunks[x] == nil or loadedChunks[x][y] == nil then
local rx = math.floor(x) + charPosX
local ry = math.floor(y) + charPosY
--if outside world, do not create chunk
if rx < 0 or rx > globals.WORLD_WIDTH - 1 or ry < 0 or ry > globals.WORLD_HEIGHT - 1 then
continue
end
local modelName = replicatedStorage.GetChunk:InvokeServer(rx, ry)
if modelName ~= nil then
local model = player.StarterGear:WaitForChild(modelName) --it takes some time to replicate
model.Parent = workspace
if loadedChunks[x] == nil then
loadedChunks[x] = {}
end
loadedChunks[x][y] = model
end
end
end
end
end
end
EDIT:
MinecraftClone.rbxl (60.5 KB)