Random map generation

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.
    parts

  • 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.
13 Likes