This is my fourth iteration of my voxel world generating script. The goal with this iteration is to be able to make it generate infinitely instead of being limited to a certain world size. (Within Roblox limits of course.)
There is a crash that happens when it’s doing the final generation. Everything else from the Perlin worms and the height maps seems to work fine but when it’s trying to actually create the world (Or the visual test part for now) it seems to crash. I don’t imagine it being because it’s trying to generate a lot at once because for now it’s only trying to generate one chunk. And even my previous iterations have generated more at once than that.
Another thing I did notice is it does not place a single block so something must be going wrong on the first attempt it’s trying to place a block. Is it some weird loop that’s happening that I am not seeing? Is it something else that I am just looking at the wrong areas for?
Here is the script I have so far.
wait(6)
--Settings--
local Settings = {
BlockSize = 6,
NoiseScale = 14,
ChunkSize = 16,--Size in blocks
WorldMaxHight = 20--100,
}
--Settings--
--Services--
local RaritySytem = require(game.ServerScriptService.RaritySystem)
local AdvancedPerlinNoise = require(game.ServerScriptService.PerlinNoise)
--Services--
--Constants--
local Blocks = game.Lighting.Blocks
local MaxOpperations = 128000
local worldseed = math.random(-1000000,1000000)
--Constants--
--Values--
local WorldChunks = {}
local Opperations = MaxOpperations
--Values--
--Functions--
function RunServerCooldown()--Special thingy that lets the server catch up after running a large opperation. May eventurally remove this if it is no longer needed as optimisations are made.
if Opperations > 0 then
Opperations = Opperations - 1
--print(Opperations)
else
Opperations = MaxOpperations
game:GetService("RunService").Heartbeat:Wait()
--print("Cooldown")
end
end
function ChangeMessage(MessageText)
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)
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)
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 "..LookingFor.."!")
return true
end
if Grid[xPos][zPos][yPos-1] ~= nil and Grid[xPos][zPos][yPos-1].BlockName == TargetBlockType then--Down
--print("Found "..LookingFor.."!")
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 "..LookingFor.."!")
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 "..LookingFor.."!")
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 "..LookingFor.."!")
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 "..LookingFor.."!")
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)
if FindNeighborBlock(xGridPos,zGridPos,xPos,zPos,yPos,"Air") == true then
return true
elseif FindNeighborBlock(xGridPos,zGridPos,xPos,zPos,yPos,"Water") == true then
return true
elseif FindNeighborBlock(xGridPos,zGridPos,xPos,zPos,yPos,"Greedy") == true then
return true
end
return false
end
function GenerateChunk(xGridPos,zGridPos)
local ChunkData = {
Position = Vector2.new(xGridPos,zGridPos),
Grid = 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,{
--Also known as the Ypos but it keeps all the main data
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.
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 / Settings.NoiseScale
local perlinZ = Zpos / 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 + (20 + 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 / Settings.NoiseScale
local perlinZ = Zpos / Settings.NoiseScale
local perlinY = Ypos / Settings.NoiseScale
local noiseValue = AdvancedPerlinNoise.new({perlinX,perlinY,perlinZ,worldseed})
if noiseValue > 0.5 then
Zgrid[Ypos].BlockName = "Air"
end
end
end
end
print("Perlin worms done.")
----Perlin Worms----
ChunkData.Grid = Grid
table.insert(WorldChunks,ChunkData)
----Generation Test----
print("Final generation...")
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 = script.Parent.Terrain
block.Size = Vector3.new(Settings.BlockSizes, Settings.BlockSizes, Settings.BlockSizes)
block.Position = Vector3.new(Xpos, Ypos, Zpos) * Settings.BlockSizes
block:SetAttribute("GridPos",Vector3.new(Xpos,Ypos,Zpos))
--Zgrid[Ypos].BlockName = "Updated"
end
end
end
end
print("Final generation done.")
----Generation Test----
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)
----Chunks test----
GenerateChunk(0,0)
----Chunks test----
Also, if you have extra tips or criticism of the script feel free to tell me. I even encourage it because I want to know if my script is going in a good direction or if I am unknowingly making a very inefficient script in some shape or form. Or just help me with some good tips for making a good procedurally generated voxel world.