Reduce lag on Voxel-Based game

I have been working on a Voxel-Based game, using a tutorial slightly. However, I have tried turning on many things to no avail. There are no meshes, no unions, no ui (not much that is, only the loading screen), and the sheer amount of blocks with textures. I need to keep the textures, or the blocks will look like solid slime. I also added fog, which bearly interferes with anything, whilst keeping everything intact. The worst part is… If I am to use anything other than these blocks, it will ruin everything, and I must restart. I am trying to go for a specific style, whilst keeping the 90,601 blocks.
Game link:

6 Likes

since your game is a voxel game if every voxel is the same size you can implement greedy meshing but it is complicated
another thing is that you can implement Occlusion Culling i think that the Documentation have smt abt it
there are alot of yt tutorials on these and they need alot of math

but why there are alot of voxels with the loading screen when i joined? i saw less than 5k blocks and the loading screen showed me a very large number of voxels loading

4 Likes

That is because I have StreamingEnabled checked.
It looks like a small number, but really, It is big.

3 Likes

The game is utilizing some good features for a game like this

Good feature: Preloading system

Good feature: Streaming enabled

However, one VERY important feature that you have missed for games like this is called parallel luau

It’s heavily used in chunk-loading like your game does, roblox documentation specifically talks about a system like yours to ensure frame rates remain stable.

TL’DR: You are not using parallel luau

4 Likes

ig a good way to reduce that amount of voxels is to rander voxels when a player is near the end of a chunk like what mc does

2 Likes

From what i’ve seen in the game the lag spike occurs as soon as the chunk-loading system starts and ends. Everything else seems to be fine.

4 Likes

I do not know how to do any of these suggestions. Any easier ones perhaps?

2 Likes

Greedy Meshing Algorithm - Roblox Scripting Tutorial (youtube.com) – a great vid on greedy meshing
idk any easier solution but the terrain gen method that you used is not efficient a good way of generating generating a random seed when the server starts and generate terrain based of that seed on every client that reduce the part count and remove lag spikes since they are rendered on the client and every client will generate the same terrain i know a vid on doing this i will search it up rn – i didnot find it but there are alot of tutorials on yt

2 Likes

This will definitely break the loading screen. I will not try this.

2 Likes

yeah your right i didnot notice that

3 Likes

just a question does your game have a limit of 90,601 generated blocks?

3 Likes

No, however, for some reason it always stops there. It might just be an error however.
Edit: It has to be an error. I calculated 5,192 from selecting children. This may however be client sided.

2 Likes

ok your game lag spikes are because of streaming enabled they arnot really lag spikes
when u get near the end of a chunk streaming enabled will load it so your game will pausesimulations until you get that info back from the server
you can click ctrl f7 / f5
you will see that your fps willnot decrease when new chuncs are being loaded
and you will see that your NetWork Recive will increase alot

2 Likes

Nonnonoo, I mean the lag inbetween those. Not spikes.

2 Likes

yeah itsnot lag its your game paused waiting for the info to be transferred to the client

i think you can change that withen the streaming setting

3 Likes

You could try loading all of the blocks into a 3D array ( {{},{},{}} ). When rendering the parts, you can greedy-mesh the parts in range and load them in. You will need to mark the parts that got “greedy-meshed” in another array. When unloading, you should unload the entire meshed part, so make sure you’re out of distance. You will need to start spawning the parts as soon as you are in a corner of the part

You could try implementing it with the chunk system, where you greedy-mesh every chunk by itself and load / unload it all at once. That would be more inefficient tho, because you could render bigger parts if the chunk borders weren’t there (or the chunks were way bigger, what would kinda defeat the whole purpose of chunks)
Red: Assumed Chunks Blue: Optimal meshed part

2 Likes

Sorry for bumping this, but I still need help. I don’t want my modeling to look ugly, so no greedy meshing.
Script:

--Variables
local seed = math.random(1,10000)

local MapSize = 300

local MapScale = MapSize/2

local BlockSize = 3

local GeneratedTreePositions = {}

local World = workspace.World

local Biome = math.random(1, 4)

local Foliage1

local BiomeLength = 0
--Terrain (Foliage)

workspace.BlocksMaxGenerated.Value = ((MapSize-MapScale-MapSize + MapScale) * (MapScale-MapScale-MapScale + MapScale))

for xAxis = MapSize-MapScale-MapSize,MapScale do
	for zAxis = MapScale-MapScale-MapScale,MapScale do
		local noise = math.noise(seed, xAxis/12, zAxis/12)*4.7
		if Biome == 1 then
			noise = math.noise(seed, xAxis/12, zAxis/12)*5.7
			Foliage1 = game.ReplicatedStorage.Objects.Grass:Clone()
			Foliage1:SetPrimaryPartCFrame(CFrame.new(Vector3.new(xAxis*BlockSize,math.floor(noise)*BlockSize,zAxis*BlockSize)))
			Foliage1.Parent = workspace["Foliage(Grass)"]
			for I = 1, 10 do
				Foliage1 = game.ReplicatedStorage.Objects.Grass:Clone()
				Foliage1:SetPrimaryPartCFrame(CFrame.new(Vector3.new(xAxis*BlockSize,(math.floor(noise)*BlockSize) - (BlockSize*I),zAxis*BlockSize)))
				Foliage1.Parent = workspace["Foliage(Grass)"]
			end
			workspace.BlocksGenerated.Value += 1
		elseif		Biome == 2 then
			noise = math.noise(seed, xAxis/12, zAxis/12)*3.7
			Foliage1 = game.ReplicatedStorage.Objects.Sand:Clone()
			Foliage1:SetPrimaryPartCFrame(CFrame.new(Vector3.new(xAxis*BlockSize,math.floor(noise)*BlockSize,zAxis*BlockSize)))
			Foliage1.Parent = workspace["Foliage(Grass)"]
			for I = 1, 10 do
				Foliage1 = game.ReplicatedStorage.Objects.Sand:Clone()
				Foliage1:SetPrimaryPartCFrame(CFrame.new(Vector3.new(xAxis*BlockSize,(math.floor(noise)*BlockSize) - (BlockSize*I),zAxis*BlockSize)))
				Foliage1.Parent = workspace["Foliage(Grass)"]
			end
			workspace.BlocksGenerated.Value += 1
		elseif		Biome == 3 then
			noise = math.noise(seed, xAxis/12, zAxis/12)*3.7
			Foliage1 = game.ReplicatedStorage.Objects.Water:Clone()
			Foliage1:SetPrimaryPartCFrame(CFrame.new(Vector3.new(xAxis*BlockSize,math.floor(noise)*BlockSize,zAxis*BlockSize)))
			Foliage1.Parent = workspace["Foliage(Grass)"]
			for I = 1, 10 do
				Foliage1 = game.ReplicatedStorage.Objects.Grass:Clone()
				Foliage1:SetPrimaryPartCFrame(CFrame.new(Vector3.new(xAxis*BlockSize,(math.floor(noise)*BlockSize) - (BlockSize*I),zAxis*BlockSize)))
				Foliage1.Parent = workspace["Foliage(Grass)"]
			end
			workspace.BlocksGenerated.Value += 1
		else
			noise = math.noise(seed, xAxis/12, zAxis/12)*5.7
			Foliage1 = game.ReplicatedStorage.Objects.Snow:Clone()
			Foliage1:SetPrimaryPartCFrame(CFrame.new(Vector3.new(xAxis*BlockSize,math.floor(noise)*BlockSize,zAxis*BlockSize)))
			Foliage1.Parent = workspace["Foliage(Grass)"]
			for I = 1, 10 do
				Foliage1 = game.ReplicatedStorage.Objects.Grass:Clone()
				Foliage1:SetPrimaryPartCFrame(CFrame.new(Vector3.new(xAxis*BlockSize,(math.floor(noise)*BlockSize) - (BlockSize*I),zAxis*BlockSize)))
				Foliage1.Parent = workspace["Foliage(Grass)"]
			end
			workspace.BlocksGenerated.Value += 1
			
		end
		
		--Terrain (Logs)

if Biome == 1 then
	if math.random(1, 800) < 2 then
		--[[local Rig = game.ReplicatedStorage.Objects.Rig:Clone()
		Rig.Humanoid.Script.Enabled = true
		Rig.Parent = workspace
				Rig:PivotTo(CFrame.new(Foliage1.PrimaryPart.Position + Vector3.new(0, 1 * BlockSize, 0)))]]
	end
			if math.random(1, 800) < 2 and not GeneratedTreePositions[xAxis.." "..zAxis] then
				GeneratedTreePositions[xAxis.." "..zAxis] = true
				local Foliage2 = game.ReplicatedStorage.Objects.Log:Clone()
				Foliage2:SetPrimaryPartCFrame(Foliage1.PrimaryPart.CFrame + Vector3.new(0, BlockSize, 0))
				Foliage2.Parent = workspace["Foliage(Logs)"]
				for i = 1, math.random(5, 10) do
					Foliage2 = game.ReplicatedStorage.Objects.Log:Clone()
					Foliage2:SetPrimaryPartCFrame(Foliage1.PrimaryPart.CFrame + Vector3.new(0, BlockSize * (i + 1), 0))
					Foliage2.Parent = workspace["Foliage(Logs)"]
				end
				local function GenerateLeaf(i, Offset)
					Foliage1 = game.ReplicatedStorage.Objects.Grass:Clone()
					Foliage1:SetPrimaryPartCFrame(Foliage2.PrimaryPart.CFrame + Offset * Vector3.new(BlockSize, BlockSize, BlockSize) - Vector3.new(0, 0 - (i * (BlockSize * -2))))
					Foliage1.Parent = workspace["Foliage(Leaves)"]
					Foliage1.PrimaryPart.CanCollide = false
				end
				GenerateLeaf(0, Vector3.new(0, 1, 0))
				for i = 1, math.random(2, 4) do
					GenerateLeaf(i - 1, Vector3.new(0, 0, 1))
					GenerateLeaf(i - 1, Vector3.new(-0, 0, -1))
					GenerateLeaf(i - 1, Vector3.new(1, 0, 0))
					GenerateLeaf(i - 1, Vector3.new(-1, 0, -0))
					GenerateLeaf(i - 1, Vector3.new(0, 0, 2))
					GenerateLeaf(i - 1, Vector3.new(-0, 0, -2))
					GenerateLeaf(i - 1, Vector3.new(2, 0, 0))
					GenerateLeaf(i - 1, Vector3.new(-2, 0, -0))
					GenerateLeaf(i - 1, Vector3.new(1, 0, 1))
					GenerateLeaf(i - 1, Vector3.new(-1, 0, -1))
					GenerateLeaf(i - 1, Vector3.new(1, 0, -1))
					GenerateLeaf(i - 1, Vector3.new(-1, 0, 1))
				end
			end

			if math.random(1, 1800) < 2 and not GeneratedTreePositions[xAxis.." "..zAxis] then
				GeneratedTreePositions[xAxis.." "..zAxis] = true
				local Foliage2 = game.ReplicatedStorage.Objects.Rock:Clone()
				Foliage2:SetPrimaryPartCFrame(Foliage1.PrimaryPart.CFrame + Vector3.new(0, BlockSize, 0))
				local function GenerateLeaf(i, Offset)
					Foliage1 = game.ReplicatedStorage.Objects.Rock:Clone()
					Foliage1:SetPrimaryPartCFrame(Foliage2.PrimaryPart.CFrame + Offset * Vector3.new(BlockSize, BlockSize, BlockSize) - Vector3.new(0, 0 - (i * (BlockSize * -2))))
					Foliage1.Parent = workspace["Foliage(Rocks)"]
					Foliage1.Parent = workspace
				end
				if math.random(1, 2) == 1 then
					GenerateLeaf(0, Vector3.new(0, 1, 0))
				end
				if math.random(1, 2) == 1 then
					GenerateLeaf(0, Vector3.new(0, 0, 1))
				end
				if math.random(1, 2) == 1 then
					GenerateLeaf(0, Vector3.new(-0, 0, -1))
				end
				if math.random(1, 2) == 1 then
					GenerateLeaf(0, Vector3.new(1, 0, 0))
				end
				if math.random(1, 2) == 1 then
					GenerateLeaf(0, Vector3.new(-1, 0, -0))
				end
			end
			elseif Biome == 3 then
				if math.random(1, 400) < 2 and not GeneratedTreePositions[xAxis.." "..zAxis] then
					GeneratedTreePositions[xAxis.." "..zAxis] = true
					local Foliage2 = game.ReplicatedStorage.Objects.Log:Clone()
					Foliage2:SetPrimaryPartCFrame(Foliage1.PrimaryPart.CFrame + Vector3.new(0, BlockSize, 0))
					Foliage2.Parent = workspace["Foliage(Logs)"]
					for i = 1, math.random(5, 10) do
						Foliage2 = game.ReplicatedStorage.Objects.Log:Clone()
						Foliage2:SetPrimaryPartCFrame(Foliage1.PrimaryPart.CFrame + Vector3.new(0, BlockSize * (i + 1), 0))
						Foliage2.Parent = workspace["Foliage(Logs)"]
					end
					local function GenerateLeaf(i, Offset)
						Foliage1 = game.ReplicatedStorage.Objects.Snow:Clone()
						Foliage1:SetPrimaryPartCFrame(Foliage2.PrimaryPart.CFrame + Offset * Vector3.new(BlockSize, BlockSize, BlockSize) - Vector3.new(0, 0 - (i * (BlockSize * -2))))
						Foliage1.Parent = workspace["Foliage(Leaves)"]
						Foliage1.PrimaryPart.CanCollide = false
					end
					GenerateLeaf(0, Vector3.new(0, 1, 0))
					for i = 1, math.random(2, 4) do
						GenerateLeaf(i - 1, Vector3.new(0, 0, 1))
						GenerateLeaf(i - 1, Vector3.new(-0, 0, -1))
						GenerateLeaf(i - 1, Vector3.new(1, 0, 0))
						GenerateLeaf(i - 1, Vector3.new(-1, 0, -0))
						GenerateLeaf(i - 1, Vector3.new(0, 0, 2))
						GenerateLeaf(i - 1, Vector3.new(-0, 0, -2))
						GenerateLeaf(i - 1, Vector3.new(2, 0, 0))
						GenerateLeaf(i - 1, Vector3.new(-2, 0, -0))
						GenerateLeaf(i - 1, Vector3.new(1, 0, 1))
						GenerateLeaf(i - 1, Vector3.new(-1, 0, -1))
						GenerateLeaf(i - 1, Vector3.new(1, 0, -1))
						GenerateLeaf(i - 1, Vector3.new(-1, 0, 1))
					end
				end

				if math.random(1, 100) < 2 and not GeneratedTreePositions[xAxis.." "..zAxis] then
					GeneratedTreePositions[xAxis.." "..zAxis] = true
					local Foliage2 = game.ReplicatedStorage.Objects.Snow:Clone()
					Foliage2:SetPrimaryPartCFrame(Foliage1.PrimaryPart.CFrame + Vector3.new(0, BlockSize, 0))
					Foliage2.Parent = workspace
					local function GenerateLeaf(i, Offset)
						Foliage1 = game.ReplicatedStorage.Objects.Snow:Clone()
						Foliage1:SetPrimaryPartCFrame(Foliage2.PrimaryPart.CFrame + Offset * Vector3.new(BlockSize, BlockSize, BlockSize) - Vector3.new(0, 0 - (i * (BlockSize * -2))))
						Foliage1.Parent = workspace["Foliage(Rocks)"]
					end
					if math.random(1, 2) == 1 then
						GenerateLeaf(0, Vector3.new(0, 1, 0))
					end
					if math.random(1, 2) == 1 then
						GenerateLeaf(0, Vector3.new(0, 0, 1))
					end
					if math.random(1, 2) == 1 then
						GenerateLeaf(0, Vector3.new(-0, 0, -1))
					end
					if math.random(1, 2) == 1 then
						GenerateLeaf(0, Vector3.new(1, 0, 0))
					end
					if math.random(1, 2) == 1 then
						GenerateLeaf(0, Vector3.new(-1, 0, -0))
					end
				end
end
	end
	if math.random(1, 10) == 1 and BiomeLength > MapSize / 20 then
		Biome = math.random(1, 3)
		BiomeLength = 0
	end
	BiomeLength += 1
	wait()
end

Did you atleast fix the lag spike?

^ Don’t think so

Have you not started using parallel luau?

Also nice code, I don’t think I could ever make a terrain generation system thing.