Room Generation unoptimized and barely works [SOLVED!]

Good day (or night)! It seems like I’m on here every single day :thinking:. I’m working on a Backrooms game (not Sonic this time) and I’m trying to make generation for the levels so that they’re not always the same. I’ve created a horrendously unoptimized system and it only generates in 4 directions (like a plus +) and in 2 opposite corners. Here is the code:

local availableChunks = workspace.AvailableChunks
local generatedChunks = workspace.GeneratedChunks

local startRoom = generatedChunks.Start
local prevRoom1 = generatedChunks.Start
local prevRoom2 = generatedChunks.Start
local prevRoom3 = generatedChunks.Start
local prevRoom4 = generatedChunks.Start
local prevRoom5 = generatedChunks.Start
local prevRoom6 = generatedChunks.Start
local prevRoom7 = generatedChunks.Start
local prevRoom8 = generatedChunks.Start

--CONFIG--
local boardSize = 16

local function generate(vector, prevRoom)
	local room = availableChunks:GetChildren()[math.random(1,#availableChunks:GetChildren())]:Clone()
	local connectorNum = math.random(1,#room.Connect:GetChildren())
	local connectorNum2 = math.random(1,#prevRoom.Connect:GetChildren())
	room.PrimaryPart = room:FindFirstChild("Connect"):FindFirstChild("C"..connectorNum)
	room:PivotTo(prevRoom:FindFirstChild("Connect"):FindFirstChild("C"..connectorNum2).CFrame + Vector3.new(vector))
	room.Parent = generatedChunks
	prevRoom = room
end

for i=1,boardSize do
	local room = availableChunks:GetChildren()[math.random(1,#availableChunks:GetChildren())]:Clone()
	local connectorNum = math.random(1,#room.Connect:GetChildren())
	local connectorNum2 = math.random(1,#prevRoom1.Connect:GetChildren())
	room.PrimaryPart = room:FindFirstChild("Connect"):FindFirstChild("C"..connectorNum)
	room:PivotTo(prevRoom1:FindFirstChild("Connect"):FindFirstChild("C"..connectorNum2).CFrame + Vector3.new(16,0,0))
	room.Parent = generatedChunks
	prevRoom1 = room
end

for i=1,boardSize do
	local room = availableChunks:GetChildren()[math.random(1,#availableChunks:GetChildren())]:Clone()
	local connectorNum = math.random(1,#room.Connect:GetChildren())
	local connectorNum2 = math.random(1,#prevRoom2.Connect:GetChildren())
	room.PrimaryPart = room:FindFirstChild("Connect"):FindFirstChild("C"..connectorNum)
	room:PivotTo(prevRoom2:FindFirstChild("Connect"):FindFirstChild("C"..connectorNum2).CFrame + Vector3.new(-16,0,0))
	room.Parent = generatedChunks
	prevRoom2 = room
end

for i=1,boardSize do
	local room = availableChunks:GetChildren()[math.random(1,#availableChunks:GetChildren())]:Clone()
	local connectorNum = math.random(1,#room.Connect:GetChildren())
	local connectorNum2 = math.random(1,#prevRoom3.Connect:GetChildren())
	room.PrimaryPart = room:FindFirstChild("Connect"):FindFirstChild("C"..connectorNum)
	room:PivotTo(prevRoom3:FindFirstChild("Connect"):FindFirstChild("C"..connectorNum2).CFrame + Vector3.new(0,0,16))
	room.Parent = generatedChunks
	prevRoom3 = room
end

for i=1,boardSize do
	local room = availableChunks:GetChildren()[math.random(1,#availableChunks:GetChildren())]:Clone()
	local connectorNum = math.random(1,#room.Connect:GetChildren())
	local connectorNum2 = math.random(1,#prevRoom4.Connect:GetChildren())
	room.PrimaryPart = room:FindFirstChild("Connect"):FindFirstChild("C"..connectorNum)
	room:PivotTo(prevRoom4:FindFirstChild("Connect"):FindFirstChild("C"..connectorNum2).CFrame + Vector3.new(0,0,-16))
	room.Parent = generatedChunks
	prevRoom4 = room
end

for i=1,boardSize do
	local room = availableChunks:GetChildren()[math.random(1,#availableChunks:GetChildren())]:Clone()
	local connectorNum = math.random(1,#room.Connect:GetChildren())
	local connectorNum2 = math.random(1,#prevRoom5.Connect:GetChildren())
	room.PrimaryPart = room:FindFirstChild("Connect"):FindFirstChild("C"..connectorNum)
	room:PivotTo(prevRoom5:FindFirstChild("Connect"):FindFirstChild("C"..connectorNum2).CFrame + Vector3.new(16,0,16))
	room.Parent = generatedChunks
	prevRoom5 = room
end

for i=1,boardSize do
	local room = availableChunks:GetChildren()[math.random(1,#availableChunks:GetChildren())]:Clone()
	local connectorNum = math.random(1,#room.Connect:GetChildren())
	local connectorNum2 = math.random(1,#prevRoom6.Connect:GetChildren())
	room.PrimaryPart = room:FindFirstChild("Connect"):FindFirstChild("C"..connectorNum)
	room:PivotTo(prevRoom6:FindFirstChild("Connect"):FindFirstChild("C"..connectorNum2).CFrame + Vector3.new(-16,0,-16))
	room.Parent = generatedChunks
	prevRoom6 = room
end

And here is a picture of the generation and explorer:

1 Like

Why don’t you create two loops, one in another, one loop for the X coordinate, one loop for the Y coordinate? That way it’ll generate in a square form. And also, you declare a generate function but never use it and instead duplicate your code.

2 Likes

I forgot to remove the function. I tried to use it but it gave me errors. So you’re saying to add one for i loop inside another?

If it’s giving you errors then you probably set something up wrong. Please send how you call the function and what error occurs.

As for the loop:

for x = 1, boardSize do
   for y = 1, boardSize do
       --generation code here
   end
end
1 Like

I run the function like this:

generate(16,0,0, prevRoom1)

and it gives me this error:

  22:54:13.972  ServerScriptService.Generation:20: attempt to index number with 'Connect'  -  Server - Generation:20

You aren’t wrapping it in Vector3 and it appears that the function is being sent with 4 arguments. Do this:
generate(Vector3.new(16,0,0), prevRoom1)

1 Like

Okay, I’ll try that next because I did this with the code:

for x = 1, boardSize do
	for y = 1, boardSize do
		local room = availableChunks:GetChildren()[math.random(1,#availableChunks:GetChildren())]:Clone()
		local connectorNum = math.random(1,#room.Connect:GetChildren())
		local connectorNum2 = math.random(1,#prevRoom.Connect:GetChildren())
		room.PrimaryPart = room:FindFirstChild("Connect"):FindFirstChild("C"..connectorNum)
		room:PivotTo(prevRoom:FindFirstChild("Connect"):FindFirstChild("C"..connectorNum2).CFrame + Vector3.new(0,0,0))
		room.Parent = generatedChunks
		prevRoom = room
	end
end

and it either generates it all in the same location when the 3 vector3 numbers are 0, straight when one of them are 16 and diagonally when 2 of them are 16.

1 Like

I assume that your room size is 16, so just adjust as so:

for x = 1, boardSize*16, 16 do
	for y = 1, boardSize*16, 16 do
		local room = availableChunks:GetChildren()[math.random(1,#availableChunks:GetChildren())]:Clone()
		local connectorNum = math.random(1,#room.Connect:GetChildren())
		local connectorNum2 = math.random(1,#prevRoom.Connect:GetChildren())
		room.PrimaryPart = room:FindFirstChild("Connect"):FindFirstChild("C"..connectorNum)
		room:PivotTo(prevRoom:FindFirstChild("Connect"):FindFirstChild("C"..connectorNum2).CFrame + Vector3.new(x, 0, y))
		room.Parent = generatedChunks
		prevRoom = room
	end
end

If I understand your code correctly, this should generate a new room every 16 studs.

1 Like

I don’t… understand this.


And yes, the chunks (or rooms) are 16x16.

1 Like

And the generation still does not work how much I tweak it.

I saw this post and remembered one of my old projects that used this random maze generation code.
Only issue it’s from a couple years ago and I don’t actually remember how it fully works, and I honestly can’t be to bothered trying to make it easier to understand (Sorry)

Hopefully it isn’t too hard to understand, quote any parts you don’t understand and I should be able to explain.

local maze = {}

--CONFIG
local pathWd = 10
 
local wallHt = 10
local wallThick = 4

local floorHt = 1

local cols = 12
local rows = 12

--Probably don't change this
local wallHtPos = wallHt/2
local wallLen = pathWd + 2*wallThick
local wallOffset = pathWd/2+wallThick/2
 
local floorHtPos = floorHt/2
local floorTileDist = pathWd + wallThick
 
local stack = {}
table.insert(stack, { zVal=0, xVal=0 })
local rnd = Random.new()
 
local function createPart(x, y, z, px, py, pz)
    local part = Instance.new("Part", workspace)
    --Change stuff to do with the part here
    part.Anchored = true
    part.Size = Vector3.new(x, y, z)
    part.Position = Vector3.new(px, py, pz)
    part.TopSurface = Enum.SurfaceType.Smooth
    return part
end
 
local function createFloor()
    for z=0, rows-1, 1 do
        maze[z] = {}
        for x=0, cols-1, 1 do
            local posX = x*floorTileDist
            local posZ = z*floorTileDist
            local part = createPart(pathWd, floorHt, pathWd, posX, floorHtPos, posZ)
            maze[z][x] = { tile=part }
        end
    end
end
 
local function createWalls()
    for z=0, rows-1, 1 do
        for x=0, cols-1, 1 do
            -- east walls
            local posX = x*floorTileDist+wallOffset
            local posZ = z*floorTileDist
            local part = createPart(wallThick, wallHt, wallLen, posX, wallHtPos, posZ)
            maze[z][x].eastWall = part
            if maze[z][x+1] then
                maze[z][x+1].westWall = part
            end
            -- south walls
            local posX = x*floorTileDist
            local posZ = z*floorTileDist+wallOffset
            local part = createPart(wallLen, wallHt, wallThick, posX, wallHtPos, posZ)
            maze[z][x].southWall = part
            if maze[z+1] then
                maze[z+1][x].northWall = part
            end
            -- edge along west
            if x==0 then
                local posX = -wallOffset
                local posZ = z*floorTileDist
                createPart(wallThick, wallHt, wallLen, posX, wallHtPos, posZ)   
            end
            -- edge along north
            if z==0 and x~=0 then
                local posX = x*floorTileDist
                local posZ = -wallOffset
                createPart(wallLen, wallHt, wallThick, posX, wallHtPos, posZ)               
            end
        end
    end
end
 
local function removeWall(wall)
    local s = wall.Size
    local p = wall.Position
    wall.Size = Vector3.new(s.X, floorHt, s.Z)
    wall.Position = Vector3.new(p.X, floorHtPos, p.Z)
    wall.BrickColor = BrickColor.White()
end
 
local function redrawMaze()
    for z=0, rows-1, 1 do
        for x=0, cols-1, 1 do
            local cell = maze[z][x]
            if cell.visited then
                cell.tile.BrickColor = BrickColor.White()
                if cell.northPath then
                    removeWall(cell.northWall)
                end
                if cell.eastPath then
                    removeWall(cell.eastWall)
                end
                if cell.southPath then
                    removeWall(cell.southWall)
                end
                if cell.westPath then
                    removeWall(cell.westWall)
                end
            end
        end 
    end
end
 
local function getUnVisitedNeighbor(z, x)
    local neighbors = {} -- north:0, east:1, south:2, west:3
    -- north
    if maze[z-1] and not maze[z-1][x].visited then
        table.insert(neighbors, 0)
    end
    -- east
    if maze[z][x+1] and not maze[z][x+1].visited then
        table.insert(neighbors, 1)
    end
    -- south
    if maze[z+1] and not maze[z+1][x].visited then
        table.insert(neighbors, 2)
    end
    -- west
    if maze[z][x-1] and not maze[z][x-1].visited then
        table.insert(neighbors, 3)
    end
    return neighbors
end
 
local function searchPath()
    if stack==nil or #stack==0 then
        return false
    end
    local stackCell = stack[#stack]
    local x = stackCell.xVal
    local z = stackCell.zVal
    
    maze[z][x].tile.BrickColor = BrickColor.Green()
    wait()
    local neighbors = getUnVisitedNeighbor(z, x)
    if #neighbors > 0 then
        local idx = rnd:NextInteger(1, #neighbors)
        local nextCellDir = neighbors[idx]
        if nextCellDir == 0 then --north
            maze[z][x].northPath = true
            maze[z-1][x].southPath = true
            maze[z-1][x].visited = true
            table.insert(stack, { zVal=z-1, xVal=x })
        elseif nextCellDir == 1 then --east
            maze[z][x].eastPath = true
            maze[z][x+1].westPath = true
            maze[z][x+1].visited = true
            table.insert(stack, { zVal=z, xVal=x+1 })
        elseif nextCellDir == 2 then --south
            maze[z][x].southPath = true
            maze[z+1][x].northPath = true
            maze[z+1][x].visited = true
            table.insert(stack, { zVal=z+1, xVal=x })
        elseif nextCellDir == 3 then --west
            maze[z][x].westPath = true
            maze[z][x-1].eastPath = true
            maze[z][x-1].visited = true
            table.insert(stack, { zVal=z, xVal=x-1 })
        end
    else
        table.remove(stack, #stack)
    end
    return true
    
end
 
createFloor()
createWalls()
maze[0][0].visited = true
--10 Second Delay with Dev Console Countdown 
--[[for i=10, 1, -1 do
    print("starting in ", i)
    wait(1)
end]]
while searchPath() do
    redrawMaze()
    wait()
end
print("Finished!")

And I honestly can’t be bothered to tweak this to make the Backrooms. I’ll just try having the area of the chunks loaded and then replace that with the chosen chunk. And this way will be better for levels like Level 1 which some parts need to be randomized.

Yeah, this method works. May be time consuming placing each chunk block but it doesn’t matter.

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.