Hello, I am wondering how I could make biomes generate correctly with my procedural world generation.
I’ve got the basics (with biome gen) but it seems to just be a mess with biomes.
Here is my code:
local RepStorage = game:GetService("ReplicatedStorage")
local RunService = game:GetService("RunService")
local Players = game:GetService("Players")
local DEBUG_MODE = false
local EXPERIMENTAL_MODE = false
local Plr = Players.LocalPlayer
local chunks = {}
local BLOCKS_GENERATED = 0
local BASE_HEIGHT = nil
local CHUNK_SCALE = 16
local RENDER_DISTANCE = 6
local SCALE = 90
local BLOCK_GEN_WAIT = 125*RENDER_DISTANCE
local GENERATION_SEED = math.random()
local LAST_BIOME = "Mountains"
local BEDROCK_LEVELS = {-63, -66}
local BIOME_TYPES = {
["DESERT_GRASS"] = {
["TEMP_MIN"] = 0.25,
["TEMP_MAX"] = 0.75,
},
["MOUNTAINS"] = {
["TEMP_MIN"] = 0.00,
["TEMP_MAX"] = 0.24,
},
}
local BIOMES = {
["Plains"] = {
["BIOME_NAME"] = "Plains",
["HEIGHT_LIMIT"] = 5,
["BIOME_TYPE"] = "DESERT_GRASS",
["MIN_NOISE"] = 0,
["MAX_NOISE"] = .34,
},
["LowMountains"] = {
["BIOME_NAME"] = "LowMountains",
["HEIGHT_LIMIT"] = 15,
["BIOME_TYPE"] = "MOUNTAINS",
["MIN_NOISE"] = .35,
["MAX_NOISE"] = .54,
},
["MidMountains"] = {
["BIOME_NAME"] = "MidMountains",
["HEIGHT_LIMIT"] = 23,
["BIOME_TYPE"] = "MOUNTAINS",
["MIN_NOISE"] = .55,
["MAX_NOISE"] = .74,
},
["Mountains"] = {
["BIOME_NAME"] = "Mountains",
["HEIGHT_LIMIT"] = 35,
["BIOME_TYPE"] = "MOUNTAINS",
["MIN_NOISE"] = .75,
["MAX_NOISE"] = 1,
},
}
local function roundToOdd(n, RoundTo)
local RoundTo = RoundTo or 3
return math.floor(n - n % RoundTo);
end
local function chunkExists(chunkX, chunkZ)
if not chunks[chunkX] then
chunks[chunkX] = {}
end
return chunks[chunkX][chunkZ]
end
local function GET_CHUNK(POS_X, POS_Z)
local POS_Y = 0
local CHUNK = nil
for I, CHUNK_FOUND in pairs(workspace.Chunks:GetChildren()) do
if CHUNK_FOUND:GetAttribute("CHUNK_POS") == tostring(POS_X..", "..POS_Y..", "..POS_Z) then
CHUNK = CHUNK_FOUND
end
end
for I, CHUNK_FOUND in pairs(RepStorage.UnloadedChunks:GetChildren()) do
if CHUNK_FOUND:GetAttribute("CHUNK_POS") == tostring(POS_X..", "..POS_Y..", "..POS_Z) then
CHUNK = CHUNK_FOUND
end
end
return CHUNK
end
local function LOAD_BLOCK(x, endY, z, BLOCK_NAME, CHUNK)
local beginY = 0
local cframe = CFrame.new(x * 3 + 1, roundToOdd((beginY - endY) * 3 / 1), z * 3 + 1)
local size = Vector3.new(3, (beginY+(endY+BASE_HEIGHT)) * 3, 3)
local p = Instance.new("Part", CHUNK)
p.Anchored = true
p.CFrame = cframe
p.Size = Vector3.new(3, 3, 3)
if BLOCK_NAME == "Grass" then
p.Material = Enum.Material.Grass
p.BrickColor = BrickColor.new("Forest green")
elseif BLOCK_NAME == "Dirt" then
p.Material = Enum.Material.Slate
p.Color = Color3.new(0.356863, 0.254902, 0.192157)
elseif BLOCK_NAME == "Stone" then
p.Material = Enum.Material.Slate
p.Color = Color3.new(0.458824, 0.458824, 0.458824)
elseif BLOCK_NAME == "Bedrock" then
p.Material = Enum.Material.Slate
p.Color = Color3.new(0.239216, 0.239216, 0.239216)
p.Position += Vector3.new(0, BEDROCK_LEVELS[math.random(1,2)], 0)
end
return p
end
local function GET_NOISE(noise, cx, cz)
if noise == "HEIGHTMAP" then
return math.noise(GENERATION_SEED, cx / SCALE, cz / SCALE)
elseif noise == "TEMPRATURE" then
return math.noise(GENERATION_SEED, -1+(cx/100), 1+(cz/100))
end
end
function makeChunk(Xchunk, Zchunk)
local chunkX, chunkZ = math.floor(Xchunk), math.floor(Zchunk)
local rootPosition = Vector3.new(chunkX * CHUNK_SCALE, 0, chunkZ * CHUNK_SCALE)
local CHUNK = Instance.new("Model", workspace.Chunks)
CHUNK.Name = "Chunk"
local cx = (chunkX * CHUNK_SCALE)
local cz = (chunkZ * CHUNK_SCALE)
local HEIGHTMAP = GET_NOISE("HEIGHTMAP", cx, cz) -- math.noise(GENERATION_SEED, cx / SCALE, cz / SCALE)
local BIOME_TEMP = GET_NOISE("TEMPRATURE", cx, cz) -- math.noise(GENERATION_SEED, 0, 1)
local REAL_BIOME = nil
for I, NOISE_BIOME in pairs(BIOMES) do
local BIOME_TYPE = BIOME_TYPES[NOISE_BIOME["BIOME_TYPE"]]
if BIOME_TYPE["TEMP_MIN"] <= BIOME_TEMP and BIOME_TYPE["TEMP_MAX"] >= BIOME_TEMP and NOISE_BIOME["MIN_NOISE"] <= HEIGHTMAP and NOISE_BIOME["MAX_NOISE"] >= HEIGHTMAP then
REAL_BIOME = NOISE_BIOME["BIOME_NAME"]
end
end
if REAL_BIOME == nil then
REAL_BIOME = LAST_BIOME
else
LAST_BIOME = REAL_BIOME
end
if DEBUG_MODE then
warn("HEIGHTMAP - "..HEIGHTMAP)
warn("BIOME TEMP - "..BIOME_TEMP)
warn(REAL_BIOME)
end
CHUNK:SetAttribute("BIOME", REAL_BIOME)
CHUNK:SetAttribute("CHUNK_POS", chunkX..", "..rootPosition.Y..", "..chunkZ)
chunks[chunkX][chunkZ] = {
["Chunk"] = CHUNK,
["Biome"] = REAL_BIOME,
} -- Acknowledge the chunk's existance.
local BIOME_INFO = BIOMES[REAL_BIOME]
for x = 1, CHUNK_SCALE do
-- for Y=0,-64,-1 do
for z = 1, CHUNK_SCALE do
BLOCKS_GENERATED += 1
local BIOME_HEIGHT = BIOME_INFO["HEIGHT_LIMIT"]
if HEIGHTMAP < .35 and BIOME_HEIGHT >= 35 then
BIOME_HEIGHT = 20
end
if BASE_HEIGHT ~= nil then
if BASE_HEIGHT ~= BIOME_HEIGHT then
if BASE_HEIGHT < BIOME_HEIGHT then
BASE_HEIGHT += 1
elseif BASE_HEIGHT > BIOME_HEIGHT then
BASE_HEIGHT -= 1
end
end
else
BASE_HEIGHT = BIOME_HEIGHT
end
local cy = HEIGHTMAP * BASE_HEIGHT
local BLOCK_NAME = "Grass"
if EXPERIMENTAL_MODE then
if Y <= BEDROCK_LEVELS[math.random(1,2)] then
BLOCK_NAME = "Bedrock"
else
BLOCK_NAME = "Grass"
end
end
LOAD_BLOCK(cx, cy, cz, BLOCK_NAME, CHUNK)
cx = (chunkX * CHUNK_SCALE) + x
cz = (chunkZ * CHUNK_SCALE) + z
HEIGHTMAP = GET_NOISE("HEIGHTMAP", cx, cz) -- math.noise(GENERATION_SEED, cx / SCALE, cz / SCALE)
if BLOCKS_GENERATED > BLOCK_GEN_WAIT then
wait()
BLOCKS_GENERATED = 0
end
end
-- end
end
-- warn("CHUNK GENERATED AT - "..chunkX..", 0, "..chunkZ)
return CHUNK
end
local function GET_CHUNK_STANDING(PLR_POS)
local POS_X, POS_Z = math.floor(PLR_POS.X / 3 / CHUNK_SCALE), math.floor(PLR_POS.Z / 3 / CHUNK_SCALE)
-- warn("POSSIBLE CHUNK POS - "..POS_X..", "..POS_Z)
local REAL_CHUNK = GET_CHUNK(POS_X, POS_Z)
if REAL_CHUNK ~= nil then
-- warn("FOUND CHUNK!")
Plr.CurrentChunk.Value = REAL_CHUNK
end
end
function checkSurroundings(location)
local chunkX, chunkZ = math.floor(location.X / 3 / CHUNK_SCALE), math.floor(location.Z / 3 / CHUNK_SCALE)
local range = math.max(1, RENDER_DISTANCE)
local CHUNKS_LOADED = {}
for x = -range, range do
for z = -range, range do
BLOCKS_GENERATED += 1
local cx = math.floor(chunkX + x)
local cz = math.floor(chunkZ + z)
local EXISTING_CHUNK = chunkExists(cx, cz)
local REAL_CHUNK
if not EXISTING_CHUNK then
REAL_CHUNK = makeChunk(cx, cz)
else
EXISTING_CHUNK = EXISTING_CHUNK["Chunk"]
EXISTING_CHUNK.Parent = workspace.Chunks
REAL_CHUNK = EXISTING_CHUNK
end
CHUNKS_LOADED[REAL_CHUNK] = REAL_CHUNK
if BLOCKS_GENERATED > BLOCK_GEN_WAIT then
wait()
BLOCKS_GENERATED = 0
end
end
end
for I, CHUNK_FOUND in pairs(workspace.Chunks:GetChildren()) do
if not CHUNKS_LOADED[CHUNK_FOUND] then
CHUNK_FOUND.Parent = RepStorage.UnloadedChunks
end
end
end
local CurChunk = Instance.new("ObjectValue", Plr)
CurChunk.Name = "CurrentChunk"
repeat wait() until Plr.Character ~= nil
Plr.Character:WaitForChild("Humanoid")
Plr.Character.Humanoid.WalkSpeed = 72
checkSurroundings(Plr.Character.HumanoidRootPart.Position)
RunService.Heartbeat:Connect(function()
if Plr.Character ~= nil then
GET_CHUNK_STANDING(Plr.Character.HumanoidRootPart.Position)
end
end)
Plr.CurrentChunk.Changed:Connect(function()
if Plr.Character ~= nil then
checkSurroundings(Plr.Character.HumanoidRootPart.Position)
end
end)
NOTE: This code goes in a localscript in "StarterPlayerScripts"
Any help on this will be appreciated!
Thank you, Zonix.
EDIT: The biomes are mountains and plains for testing!
Feel free to add more for your own testing to fix my issue!
If you have found this in research on how you can make a system like this, feel free to use it BUT this is just bare bones of a Minecraft like world gen script
EDIT 2: Improved code for easier use