Here’s my simplified implementation of the Perlin noise method I described. You can find visual and more detailed representations of the systems described in my implementation link I posted earlier.
If you want the place file you can find it here:
ChunkTutorial.rbxlx (57.5 KB)
-
Before adding the scripts, make three folders in ReplicatedStorage
called Floors
, Walls
, and Ceilings
.
-
You can then add different models in those folders for their specific tiles.
-
Make sure the sizes of the models reflect the Grid Size that you want. By default, it’s 12, meaning a floor tile for example should be 12x1x12 in size. (you can change the grid size in the script below as you please by changing gen.gridScale
).
-
Make sure that each model has a PrimaryPart set that also aligns with the Grid Scale.
-
Place this inside a ModuleScript named “Generation” in ReplicatedStorage:
--# Sevices
local ReplicatedStorage = game:GetService("ReplicatedStorage")
--# Point
local gen = {}
--# Storage
local Floors = ReplicatedStorage:WaitForChild("Floors"):GetChildren()
local Walls = ReplicatedStorage:WaitForChild("Walls"):GetChildren()
local Ceilings = ReplicatedStorage:WaitForChild("Ceilings"):GetChildren()
gen.chunkStorage = {}
--# Placement Variables
gen.gridScale = 12 -- The grid size in studs.
gen.renderDistance = 5 -- How far we can render relative to a position.
gen.originHeight = 5 -- Height of the ground.
gen.ceilingHeight = 10 -- How high ceilings are placed (offset from the origin height).
gen.wallDensity = 2 -- The amount of walls that get generated.
--# Generation Variables
gen.terrainSmoothness = 3 -- How noisy the generation will be.
gen.wallHeight = 20 -- What is considered the height at which a wall will be placed instead of a floor tile.
gen.seed = 300
--# Functions
local function checkForOverlappingChunk(x, z)
for _, chunk in pairs(gen.chunkStorage) do
if chunk.PrimaryPart.Position.X == x and chunk.PrimaryPart.Position.Z == z then
return true
end
end
end
function gen.placeChunk(x, z, chunkType)
-- Don't place this chunk if a chunk already exists here.
if checkForOverlappingChunk(x, z) == true then
return true
end
-- We're placing either a floor or a roof depending on the type of chunk.
local chunkModel = nil
local ceilingModel = nil
if chunkType == "Floor" then
chunkModel = Floors[math.random(1, #Floors)]:Clone()
-- Place the ceiling above ground tiles.
ceilingModel = Ceilings[math.random(1, #Ceilings)]:Clone()
ceilingModel:SetPrimaryPartCFrame(CFrame.new(x, gen.originHeight + gen.ceilingHeight, z))
ceilingModel.Parent = workspace
else
chunkModel = Walls[math.random(1, #Walls)]:Clone()
end
-- We're setting the chunks position.
chunkModel:SetPrimaryPartCFrame(CFrame.new(x, gen.originHeight, z))
-- Saving the chunk in storage.
chunkModel.Parent = workspace
table.insert(gen.chunkStorage, chunkModel)
table.insert(gen.chunkStorage, ceilingModel)
end
function gen.removeChunk(x, z)
for i, chunk in pairs(gen.chunkStorage) do
if chunk.PrimaryPart.Position.X == x and chunk.PrimaryPart.Position.Z == z then
chunk:Destroy()
table.remove(gen.chunkStorage, i)
return nil
end
end
end
function gen.removeDistantChunks(x, z)
-- Delete all the chunks which are too far away.
for _, chunk in pairs(gen.chunkStorage) do
if (Vector2.new(chunk.PrimaryPart.Position.X, chunk.PrimaryPart.Position.Z) - Vector2.new(x, z)).Magnitude > (gen.renderDistance*gen.gridScale) then
gen.removeChunk(chunk.PrimaryPart.Position.X, chunk.PrimaryPart.Position.Z)
end
end
end
local function snapToGrid(x, z)
return (math.floor(x / gen.gridScale +.5 ) * gen.gridScale), (math.floor(z / gen.gridScale +.5 ) * gen.gridScale)
end
function gen.placeSurroundingChunks(x, z)
-- Get the values we'll need.
local scaledRenderDistance = gen.renderDistance*gen.gridScale
local halfScaledRenderDistance = (scaledRenderDistance)*0.5
for posX = 0, (gen.renderDistance) do
for posZ = 0, (gen.renderDistance) do
local scaledPosX = posX*gen.gridScale
local scaledPosZ = posZ*gen.gridScale
-- Make the chunks centered to our position.
local actualPos = Vector2.new(
x + (scaledPosX - halfScaledRenderDistance ),
z + (scaledPosZ - halfScaledRenderDistance )
)
actualPos = Vector2.new(snapToGrid(actualPos.X, actualPos.Y))
-- Generate the chunks within the render distance.
if (actualPos - Vector2.new(x, z)).Magnitude <= (gen.renderDistance*gen.gridScale) then
local chunkType = "Floor"
-- Generate a random height based on the chunks position.
local dividedX = actualPos.X/gen.gridScale
local dividedZ = actualPos.Y/gen.gridScale
local noise = math.noise( (posX + dividedX)/gen.gridScale, (posZ + dividedZ)/gen.gridScale, gen.seed)
if math.abs(noise) > (gen.wallDensity*0.1) then
chunkType = "Wall"
end
print(noise)
gen.placeChunk(actualPos.X, actualPos.Y, chunkType)
end
end
end
end
--# Finalize
return gen
- Place this inside a LocalScript in PlayerGui or StarterPlayerScripts:
--# Sevices
local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
--# Include
local gen = require(ReplicatedStorage:WaitForChild("Generation"))
--# Plr
local plr = Players.LocalPlayer
local char = plr.Character or plr.CharacterAdded:Wait()
local HRP = char:WaitForChild("HumanoidRootPart")
--# Loop
-- Process chunks every 0.2 seconds.
while wait(0.2) do
gen.placeSurroundingChunks(HRP.Position.X, HRP.Position.Z)
gen.removeDistantChunks(HRP.Position.X, HRP.Position.Z)
end
- The
placeSurroundingChunks
method in the module script describes how to generate chunks within a certain radius around the Player.
- Checking if a chunk is occupied is in the
checkForOverlappingChunk
method in the module script as well.
- Placing random tiles is in the
placeChunk
method of the same script as well.