wait(6)--idk i let my studio load first before everything runs.
--Settings--
local Settings = {
BlockSize = 6,--Size of blocks measured in studs.
NoiseScale = 14,--Noise scale thing... Idk how to explain this.
ChunkSize = 16,--Size of chunks measured in blocks.
WorldMaxHight = 100,--Max world height in blocks that will be allowed to generate.
WorldBaseHight = 50,--Average world height in blocks that the terrain will be.
}
--Settings--
--Services--
local RaritySytem = require(game.ServerScriptService.RaritySystem)--Rarity module that will be used for ore rarities.
--Services--
--Constants--
local Blocks = game.ReplicatedStorage.Blocks--Blocks that will be used.
local MaxOpperations = 128000--Thing that I used in previous iterations to not crash the server on worldgen... I want to make it so I dont need this anymore.
local worldseed = math.random(-1000000,1000000)--World seed for unique world generations.
--Constants--
--Values--
local WorldChunks = {}--All generated chunks are stored in this table.
local Opperations = MaxOpperations--Current opperations left before cooldown.
--Values--
--Functions--
function RunServerCooldown()--Runs the cooldowns system thing mentioned before.
if Opperations > 0 then
Opperations = Opperations - 1
--print(Opperations)
else
Opperations = MaxOpperations
game:GetService("RunService").Heartbeat:Wait()
--print("Cooldown")
end
end
function ChangeMessage(MessageText)--Unused at the moment.
for index,child in pairs (game.Players:GetChildren()) do
if child:IsA("Player") then
if child.PlayerGui:FindFirstChild("MessageGui") ~= nil then child.PlayerGui:FindFirstChild("MessageGui"):Destroy() end
if MessageText ~= false and MessageText ~= nil then
local gui = script.MessageGui:Clone()
gui.Parent = child.PlayerGui
gui.TextLabel.Text = MessageText
gui.Enabled = true
end
end
end
end
function GetChunk(Xpos:number,Zpos:number)--Gets a spesific chunk with the cordinates.
--print(WorldChunks)
for index = 1,#WorldChunks do
local Chunk = WorldChunks[index]
if Chunk.Position == Vector2.new(Xpos,Zpos) then
return Chunk
end
end
return nil
end
function FindNeighborBlock(xGridPos,zGridPos,xPos,zPos,yPos,TargetBlockType:string)--Checks if there is a spesific block in contact with the targeted block.
local Chunk = GetChunk(xGridPos,zGridPos)
if Chunk then
local Grid = Chunk.Grid
--print("Pos = "..tostring(xPos)..", "..tostring(yPos)..", "..tostring(zPos))
--print(Grid)
local TargetLocation = Grid[xPos][zPos][yPos]
if Grid[xPos][zPos][yPos+1] ~= nil and Grid[xPos][zPos][yPos+1].BlockName == TargetBlockType then--Up
--print("Found "..TargetBlockType.."!")
return true
end
if Grid[xPos][zPos][yPos-1] ~= nil and Grid[xPos][zPos][yPos-1].BlockName == TargetBlockType then--Down
--print("Found "..TargetBlockType.."!")
return true
end
if zPos >= Settings.ChunkSize then--Back
local NextChunk = GetChunk(xGridPos,zGridPos + 1)
if NextChunk then
local NextGrid = NextChunk.Grid
if NextGrid then
if NextGrid[xPos][1][yPos] ~= nil and NextGrid[xPos][1][yPos].BlockName == TargetBlockType then
--print("Found "..LookingFor.."!")
return true
end
else
--error("Tried to find a chunk that does not exist yet or has an invalid position!")
warn("Tried to find a chunk that does not exist yet or has an invalid position!")
end
end
else
if Grid[xPos][zPos + 1][yPos] ~= nil and Grid[xPos][zPos + 1][yPos].BlockName == TargetBlockType then
--print("Found "..TargetBlockType.."!")
return true
end
end
if zPos <= 1 then--Forward
local NextChunk = GetChunk(xGridPos,zGridPos - 1)
if NextChunk then
local NextGrid = NextChunk.Grid
if NextGrid then
if NextGrid[xPos][Settings.ChunkSize][yPos] ~= nil and NextGrid[xPos][Settings.ChunkSize][yPos].BlockName == TargetBlockType then
--print("Found "..LookingFor.."!")
return true
end
else
--error("Tried to find a chunk that does not exist yet or has an invalid position!")
warn("Tried to find a chunk that does not exist yet or has an invalid position!")
end
end
else
if Grid[xPos][zPos - 1][yPos] ~= nil and Grid[xPos][zPos - 1][yPos].BlockName == TargetBlockType then
--print("Found "..TargetBlockType.."!")
return true
end
end
if xPos >= Settings.ChunkSize then--Left
local NextChunk = GetChunk(xGridPos + 1,zGridPos)
if NextChunk then
local NextGrid = NextChunk.Grid
if NextGrid then
if NextGrid[1][zPos][yPos] ~= nil and NextGrid[1][zPos][yPos].BlockName == TargetBlockType then
--print("Found "..LookingFor.."!")
return true
end
else
--error("Tried to find a chunk that does not exist yet or has an invalid position!")
warn("Tried to find a chunk that does not exist yet or has an invalid position!")
end
end
else
if Grid[xPos + 1][zPos][yPos] ~= nil and Grid[xPos + 1][zPos][yPos].BlockName == TargetBlockType then
--print("Found "..TargetBlockType.."!")
return true
end
end
if xPos <= 1 then--Right
local NextChunk = GetChunk(xGridPos - 1,zGridPos)
if NextChunk then
local NextGrid = NextChunk.Grid
if NextGrid then
if NextGrid[Settings.ChunkSize][zPos][yPos] ~= nil and NextGrid[Settings.ChunkSize][zPos][yPos].BlockName == TargetBlockType then
--print("Found "..LookingFor.."!")
return true
end
else
--error("Tried to find a chunk that does not exist yet or has an invalid position!")
warn("Tried to find a chunk that does not exist yet or has an invalid position!")
end
end
else
if Grid[xPos - 1][zPos][yPos] ~= nil and Grid[xPos - 1][zPos][yPos].BlockName == TargetBlockType then
--print("Found "..TargetBlockType.."!")
return true
end
end
else
--error("Tried to find a chunk that does not exist yet or has an invalid position!")
warn("Tried to find a chunk that does not exist yet or has an invalid position!")
end
return false
end
function IsExposedBlock(xGridPos,zGridPos,xPos,zPos,yPos)--Checks if a block is exposed to a defined transparent block. Used for rendering.
if FindNeighborBlock(xGridPos,zGridPos,xPos,zPos,yPos,"Air") == true then
--print("Exposed to Air.")
return true
end
if FindNeighborBlock(xGridPos,zGridPos,xPos,zPos,yPos,"Water") == true then
--print("Exposed to Water.")
return true
end
--print("Not exposed block.")
return false
end
function GenerateChunk(xGridPos,zGridPos)--The main chunk generation function.
local ChunkData = {
Position = Vector2.new(xGridPos,zGridPos),
Grid = nil,
Rendered = true,
ChunkModel = nil,
}
local Grid = {}
for index = 1,Settings.ChunkSize do
table.insert(Grid,{})--X
end
for index = 1,#Grid do
local Xpos = Grid[index]
for secondindex = 1,Settings.ChunkSize do
table.insert(Xpos,{})--Z
end
end
for index = 1,#Grid do
local Xpos = Grid[index]
for secondindex = 1,Settings.ChunkSize do
local Zpos = Xpos[secondindex]
for thirdindex = 1,Settings.WorldMaxHight do
RunServerCooldown()
table.insert(Zpos,{--Y
--Greedy value things are going to be used for when I attempt to use Greedy Meshing again.
BlockName = "",
IsGreedy = false,
GreedyXsize = 1,
GreedyYsize = 1,
GreedyZsize = 1}
)
end
end
end
----Height Map----
--Generates the basic heights of the map using various noise values.
--Types of noise:
--Main noise - Primary noise value that creates the basic hills and valeys that will further be modified later.
--Sharpness noise - Secondary noise that will amplify or dull the intensity of the hills generated by the main noise based on the value given by this noise.
--Height noise - A subtle noise value that is much larger in size but it controls the main height of certain areas allowing for high areas and low areas such as oceans and high lands.
local extraXPos = xGridPos * Settings.ChunkSize
local extraZPos = zGridPos * Settings.ChunkSize
for Xpos = 1,#Grid do
--print("X")
local Xgrid = Grid[Xpos]
for Zpos = 1,Settings.ChunkSize do
--print("Z")
--game:GetService("RunService").Heartbeat:Wait()
local Zgrid = Xgrid[Zpos]
for Ypos = 1,Settings.WorldMaxHight do
--print("Y")
RunServerCooldown()
local Ygrid = Zgrid[Ypos]
--wait(0.5)
local perlinX = (Xpos + extraXPos) / Settings.NoiseScale
local perlinZ = (Zpos + extraZPos) / Settings.NoiseScale
local perlinY = Ypos / Settings.NoiseScale
local Noise = math.noise(perlinX,worldseed,perlinZ)--Main noise
Noise = Noise * (2 + math.noise(perlinX * 0.1,worldseed,perlinZ * 0.1))--Sharpness noise
Noise = Noise + (Settings.WorldBaseHight + math.noise(perlinX * 0.05,worldseed,perlinZ * 0.05) * 30)--Height noise
--print("Noise = "..noiseValue)
if Ypos <= Noise then
Zgrid[Ypos].BlockName = "Noise"
else
Zgrid[Ypos].BlockName = "Air"
end
end
end
end
----Height Map----
----Perlin Worms----
print("Generating perlin worms...")
for Xpos = 1,#Grid do
--print("X")
local Xgrid = Grid[Xpos]
for Zpos = 1,Settings.ChunkSize do
--print("Z")
--game:GetService("RunService").Heartbeat:Wait()
local Zgrid = Xgrid[Zpos]
for Ypos = 1,Settings.WorldMaxHight do
--print("Y")
RunServerCooldown()
local Ygrid = Zgrid[Ypos]
--wait(0.5)
local perlinX = (Xpos + extraXPos) / Settings.NoiseScale
local perlinZ = (Zpos + extraZPos) / Settings.NoiseScale
local perlinY = Ypos / Settings.NoiseScale
local noiseValue = math.noise(perlinX,perlinY + worldseed,perlinZ)
--print(noiseValue)
if noiseValue > (0.5 * (Ypos/Settings.WorldBaseHight)) then
Zgrid[Ypos].BlockName = "Air"
end
end
end
end
print("Perlin worms done.")
----Perlin Worms----
ChunkData.Grid = Grid
table.insert(WorldChunks,ChunkData)
----Final generation----
print("Final generation...")
local ChunkModel = Instance.new("Model")
ChunkModel.Name = tostring(xGridPos,zGridPos)
for Xpos = 1,#Grid do
--print("X")
local Xgrid = Grid[Xpos]
for Zpos = 1,Settings.ChunkSize do
--print("Z")
--game:GetService("RunService").Heartbeat:Wait()
local Zgrid = Xgrid[Zpos]
for Ypos = 1,Settings.WorldMaxHight do
--print("Y")
RunServerCooldown()
local Ygrid = Zgrid[Ypos]
--wait(0.5)
if Zgrid[Ypos].BlockName == "Noise" and IsExposedBlock(xGridPos,zGridPos,Xpos,Zpos,Ypos) == true then
local block = Blocks.Stone:Clone()
block.Parent = ChunkModel
block.Size = Vector3.new(Settings.BlockSize, Settings.BlockSize, Settings.BlockSize)
block.Position = Vector3.new((Xpos + extraXPos), Ypos, (Zpos + extraZPos)) * Settings.BlockSize
block:SetAttribute("GridPos",Vector3.new(Xpos,Ypos,Zpos))
--Zgrid[Ypos].BlockName = "Updated"
end
end
end
end
ChunkModel.Parent = script.Parent.Terrain
print("Final generation done.")
----Final generation----
ChunkData.ChunkModel = ChunkModel
ChunkData.Grid = Grid
--table.insert(WorldChunks,ChunkData)
end
--Functions--
script.Parent.Loaded.Value = false
local findexistingworld = game.Workspace:FindFirstChild("Tfolder")
if findexistingworld ~= nil then
findexistingworld:Destroy()
end
for _,v in pairs (script.Parent.Terrain:GetChildren()) do
if v:IsA("BasePart") then
v:Destroy()
end
end
for _,v in pairs (script.Parent.Resources:GetChildren()) do
if v:IsA("Model") then
v:Destroy()
end
end
script.Parent.Baseplate.Position = Vector3.new(0, 10, 0)
----Chunk rendering----
--A large majority of this is generated by Chat GPT.
local RenderDistance = 5 -- You can increase this for larger render distances
local UnloadDistance = RenderDistance + 1
local PlayerRenderedChunks = {} -- Track which chunks each player has loaded
while true do
wait(1) -- Adjust the wait time for better performance if needed
for _,Player in pairs(game.Players:GetChildren()) do
if Player:IsA("Player") then
local Character = Player.Character
if Character then
local Humanoid = Character:FindFirstChildOfClass("Humanoid")
if Humanoid then
local RootPart = Humanoid.RootPart
if RootPart then
-- Calculate chunk position based on player position
local ChunkPosX = math.floor(RootPart.Position.X / (Settings.BlockSize * Settings.ChunkSize))
local ChunkPosZ = math.floor(RootPart.Position.Z / (Settings.BlockSize * Settings.ChunkSize))
-- Create an entry for this player if it doesn't exist
if not PlayerRenderedChunks[Player] then
PlayerRenderedChunks[Player] = {}
end
-- Iterate over all chunks within render distance for this player
for Xindex = -RenderDistance, RenderDistance do
for Zindex = -RenderDistance, RenderDistance do
local TargetChunkX = ChunkPosX + Xindex
local TargetChunkZ = ChunkPosZ + Zindex
local ExistingChunk = GetChunk(TargetChunkX, TargetChunkZ)
-- If the chunk does not exist, generate it
if not ExistingChunk then
--print("Generating chunk at: ", TargetChunkX, TargetChunkZ)
GenerateChunk(TargetChunkX, TargetChunkZ)
-- Keep track of loaded chunk for this player
PlayerRenderedChunks[Player][TargetChunkX .. "," .. TargetChunkZ] = true
elseif ExistingChunk.Rendered == false then
-- Reload chunk if it's not rendered
ExistingChunk.ChunkModel.Parent = script.Parent.Terrain
ExistingChunk.Rendered = true
PlayerRenderedChunks[Player][TargetChunkX .. "," .. TargetChunkZ] = true
end
end
end
end
end
end
end
end
-- Unload chunks that no players are nearby
for index, chunk in pairs(WorldChunks) do
local chunkX, chunkZ = chunk.Position.X, chunk.Position.Y
local isChunkNeeded = false
-- Check if any player still needs this chunk
for _,Player:Player in pairs(game.Players:GetChildren()) do
if Player and Player.Character and Player.Character:FindFirstChildOfClass("Humanoid") and Player.Character:FindFirstChildOfClass("Humanoid").RootPart then
if PlayerRenderedChunks[Player] then
local ChunkPosX = math.floor(Player.Character.HumanoidRootPart.Position.X / (Settings.BlockSize * Settings.ChunkSize))
local ChunkPosZ = math.floor(Player.Character.HumanoidRootPart.Position.Z / (Settings.BlockSize * Settings.ChunkSize))
local distanceX = math.abs(ChunkPosX - chunkX)
local distanceZ = math.abs(ChunkPosZ - chunkZ)
if distanceX <= UnloadDistance and distanceZ <= UnloadDistance then
isChunkNeeded = true
break -- If one player needs the chunk, stop checking
end
end
end
end
-- If no player needs the chunk, unload it
if not isChunkNeeded and chunk.Rendered == true then
chunk.Rendered = false
chunk.ChunkModel.Parent = game.ServerStorage
--print("Unloading chunk at: ", chunkX, chunkZ)
end
end
end
----Chunk rendering----
This is my Procedural block terrain generator that I plan to use for a larger project. It’s the fourth iteration so far with the goal to make it generate an infinite world instead of the limited world it was before. I aimed to do this using Chunks to generate things.
I managed to get a working form of this with some help from Chat GPT specifically with the rendering part. However one primary problem I see is Roblox does not seem to be able to keep up enough to allow for large enough world to be generated. This is before I even add any advanced world generation features like biomes and ore gen, so this is a primary issue that needs to be fixed.
Ultimately, I am not too sure how to make things much better, but I have had thoughts of trying to have rendering done on the client side instead although that might be out of my skill set. I will still try it if it’s my only option.
Basically, I just need optimizations or just better ways to run the code in general. I am open to anything.
Provide an overview of:
- What does the code do and what are you not satisfied with?
- What potential improvements have you considered?
- How (specifically) do you want to improve the code?