Connecting rooms together (dungeon generator)

So I’m trying to create a dungeon generator with segments (or rooms) that connect with each other.

Here’s what the layout for a room looks like:
k.PNG

(The red block represents the room’s primary part. The green block is the connector (it assists in connecting each room together if that makes sense.))

In the test dungeon that I’ve come up with, I envisioned that the dungeon layout would have 9 rooms and look something like this:

and not this:

I’ve also looked up topics such as this one, but I can’t figure out to connect the rooms perfectly without them spreading apart.

Code
function dungeon:createRoom(newRoom, previousRoom, parent)
	local b = newRoom:Clone()
	b.Parent = parent
	
	if previousRoom then
		for b, k in next, newRoom.Nodes:GetChildren() do
			if k ~= "Primary" then
				previousRoom:SetPrimaryPartCFrame(CFrame.new(newRoom.Nodes[b].Position + (newRoom.Nodes[b].CFrame.LookVector * 8), newRoom.Nodes[b].Position))	
			end
		end
	end
	return b
end


function dungeon:GenerateDungeon(roomModels, numRooms)
	
	local roomTypes = {
		finishPoint = {};
		spawnPoint = {};
		
	}
	--won't be using the above table (for now)
	
	
	--creating the spawn room
	local startArea = dungeon:createRoom(
		roomModels.Spawn, 
		nil,
		assert(workspace:WaitForChild("DungeonHolder"))
	)
	
	local nextRoom, previousRoom --dictates the new room and old room
	
	nextRoom = startArea
	previousRoom = nil --placeholder
	
	local rooms = {} --setting up a table for the rooms following the spawn area (will be using this later)
	local getRooms = roomModels:GetChildren() --getting all of the rooms
	
	for room = 1, numRooms do --repeats until it reaches the max amount of rooms
		local roomChoice
		coroutine.resume(coroutine.create(function()
			local randomRoom = table.remove(getRooms, math.random(#getRooms))
			
			if randomRoom.Name ~= "Spawn" then
				roomChoice = randomRoom

				previousRoom = nextRoom
				nextRoom = roomChoice

				--print("Previous Room: "..previousRoom.Name..' Next Room: '..nextRoom.Name)

				local room = dungeon:createRoom(
					nextRoom, 
					previousRoom, 
					assert(workspace:WaitForChild("DungeonHolder"))
					)
			end
		end))
	end
end

function dungeon:Init()
	local holder = Instance.new('Folder')
	holder.Name = "DungeonHolder"
	holder.Parent = workspace
	
	local rooms = dungeon:GenerateDungeon(
		game.ServerStorage.BasicDungeon.Rooms,
		8
	)

end

Thanks in advance.

3 Likes

The rooms you are trying to generate are basically like a maze.
Something I would suggest is using Prim’s Algorithm for this, you could go google it or watch some videos.

I could still try to explain it myself:
You mark all the rooms around the spawn room as something (you could do this by placing them in a table).
Take a random room from the table and connect it to a random (already existing) room around it.
Now you have new option(s) for place(s) you could generate a new room at, add those position(s) to the table.
Repeat.

By the way, you could do it how many times you want to get how many rooms you want.

Could you provide an example on how you would implement this? I’m kind of confused.

Any help would be appreciated.

You should store all the parts that could be connected next in a table, and take a random one, and connect it to one random neighbor that is a path.
That is a challenge for you, I never implemented this in Roblox but just in C++ text.
Take your time and I know you’ll make it!

I did something similar, but on a straight infinite line with random corners and paths.
image
I cant really explain a good procedure, cause I had many issues with it…
The overlapping rooms and boundary box were the hardest thing. (an visible boundary box that contains the whole structure is very helpul, and “checkers” parts to see if the structure is been build inside the boundary box)

Like @RedstonePlayz09 told you (tables). Just make “rooms” and store them inside a folder, use that folder to feed a loop that will set the Root part position of each room model into the choosen End Part position of the last room (exits).
In your case, because you want “9” rooms per “zone”. You need to check the “exits” of each room, and place another random room into each “exit” (using the entrance CFrame root part). You have rooms with 4 exits, maybe 3 too, 2 exits, and 1 exit… Create different tables for each version of exits, and populate each exit with a room, and analize the new room to check if has exits too, using “checking” parts to check that you are not overlapping rooms…

… Its very complicated for me to explain clearly… Im sorry if Im confusing more than helping.
I like your idea, so I think Im gonna create that random layout you are making too, I like it!
If I have some clearear ideas, I will tell you :3

So basically, I think you are trying to make something like this
Roblox DungeonCreator Video.wmv (3.4 MB)

So I will just write the script that you will need

local roomsModel = game.Workspace.Rooms
local roomModel = game.ReplicatedStorage.Room

local function makeRooms(room, startRoomPos, roomsInX, roomsInZ)
	local startRoom
	local finishRoom
	
	local xShift = 0
	local zShift = 0
	
	local xFloorSize = room.Floor.Size.X
	local zFloorSize = room.Floor.Size.Z
	
	for i = 1, roomsInZ do
		xShift = 0
		local roomForLaterPurpose
		
		for i = 1, roomsInX do
			local newRoom = room:Clone()
			
			local newRoomCFrame = CFrame.new(startRoomPos + Vector3.new(-xShift, 0, -zShift))
			newRoom:SetPrimaryPartCFrame(newRoomCFrame)
			
			newRoom.Parent = roomsModel
			
			roomForLaterPurpose = newRoom
			
			if xShift == 0 and zShift == 0 then
				startRoom = newRoom
			end
			
			xShift = xShift + xFloorSize
			
		end
		
		zShift = zShift + zFloorSize
		
		if xShift >= roomsInX * xFloorSize and zShift >= roomsInZ * zFloorSize then
			finishRoom = roomForLaterPurpose
		end
	end
	
	return startRoom, finishRoom
end

local function openDoors(room1, room2)
	local direction = room2.Center.Position - room1.Center.Position
	
	local floorXSize = room1.Floor.Size.X
	local floorZSize = room1.Floor.Size.Z
	
	local doorToOpen1
	local doorToOpen2

	if direction == Vector3.new(0, 0, floorZSize) then
		doorToOpen1 = room1.Doors:WaitForChild("Z")
		doorToOpen2 = room2.Doors:WaitForChild("-Z")

	elseif direction == Vector3.new(0, 0, -floorZSize) then
		doorToOpen1 = room1.Doors:WaitForChild("-Z")
		doorToOpen2 = room2.Doors:WaitForChild("Z")

	elseif direction == Vector3.new(floorXSize, 0, 0) then
		doorToOpen1 = room1.Doors:WaitForChild("X")
		doorToOpen2 = room2.Doors:WaitForChild("-X")

	elseif direction == Vector3.new(-floorXSize, 0, 0) then
		doorToOpen1 = room1.Doors:WaitForChild("-X")
		doorToOpen2 = room2.Doors:WaitForChild("X")
		
	else
		warn("Error in opening doors")
		return
	end
	
	doorToOpen1.Transparency = 1
	doorToOpen2.Transparency = 1
	
	doorToOpen1.CanCollide = false
	doorToOpen2.CanCollide = false
end

local function checkCenterPartAndOpen(position)
	local roomReturn
	local roomEligible = false
	
	local size = Vector3.new(2, 2, 2)
	
	local regionPoint1 = position - (size/2)
	local regionPoint2 = position + (size/2)
	
	local region = Region3.new(regionPoint1, regionPoint2)
	
	local partsInRegion = workspace:FindPartsInRegion3(region)
	
	if partsInRegion then
		-- found room
		for i, partInRegion in pairs(partsInRegion) do
			if partInRegion:IsA("BasePart") and partInRegion.Name:find("Center") then
				local room = partInRegion.Parent
				
				if room.Closed.Value == false then
					roomEligible = true
					roomReturn = room
				end
				
			end
		end
	else
		-- edge of maze
	end
	
	return roomEligible, roomReturn
end

local function findClosedRandomRoom()
	local closedRooms = {}
	local eligibleRooms = {}
	
	-- find all closed rooms first
	for i, room in pairs(roomsModel:GetChildren()) do
		if room.Closed.Value == true then
			table.insert(closedRooms, room)
		end
	end
	
	-- check each side and make sure that there is atleast 1 open area
	for i, room in pairs(closedRooms) do
		local roomChoosable = false
		
		local roomXSize = room.Floor.Size.X
		local roomZSize = room.Floor.Size.Z
		
		local checkPoint1 = room.Center.Position + Vector3.new(0, 0, roomZSize)
		local checkPoint2 = room.Center.Position + Vector3.new(0, 0, -roomZSize)
		local checkPoint3 = room.Center.Position + Vector3.new(roomXSize, 0, 0)
		local checkPoint4 = room.Center.Position + Vector3.new(-roomXSize)
		
		local room1Eligible = checkCenterPartAndOpen(checkPoint1)
		local room2Eligible = checkCenterPartAndOpen(checkPoint2)
		local room3Eligible = checkCenterPartAndOpen(checkPoint3)
		local room4Eligible = checkCenterPartAndOpen(checkPoint4)
		
		if room1Eligible == true or room2Eligible == true or room3Eligible == true or room4Eligible == true then
			table.insert(eligibleRooms, room)
		end
		
	end
	
	
	if #eligibleRooms == 0 then
		print("No closed room found which has an open neighbour room")
		return
	end
	
	-- now choose a random room to return
	local randomNumber = math.random(1, #eligibleRooms)
	local randomClosedRoom = eligibleRooms[randomNumber]
	
	return randomClosedRoom
end

local function findRandomOpenNeighbourRoom(room)
	local eligibleRooms = {}
	
	local roomXSize = room.Floor.Size.X
	local roomZSize = room.Floor.Size.Z

	local checkPoint1 = room.Center.Position + Vector3.new(0, 0, roomZSize)
	local checkPoint2 = room.Center.Position + Vector3.new(0, 0, -roomZSize)
	local checkPoint3 = room.Center.Position + Vector3.new(roomXSize, 0, 0)
	local checkPoint4 = room.Center.Position + Vector3.new(-roomXSize)

	local room1Eligible, room1 = checkCenterPartAndOpen(checkPoint1)
	local room2Eligible, room2 = checkCenterPartAndOpen(checkPoint2)
	local room3Eligible, room3 = checkCenterPartAndOpen(checkPoint3)
	local room4Eligible, room4 = checkCenterPartAndOpen(checkPoint4)
	
	if room1Eligible == true then
		table.insert(eligibleRooms, room1)
	end
	
	if room2Eligible == true then
		table.insert(eligibleRooms, room2)
	end
	
	if room3Eligible == true then
		table.insert(eligibleRooms, room3)
	end
	
	if room4Eligible == true then
		table.insert(eligibleRooms, room4)
	end
	
	
	if #eligibleRooms == 0 then
		print("No open neighbour rooms found")
		return
	end
	
	local randomNumber = math.random(1, #eligibleRooms)
	local randomOpenNeighbourRoom = eligibleRooms[randomNumber]
	
	return randomOpenNeighbourRoom
end

local function reset()
	for i, room in pairs(roomsModel:GetChildren()) do
		room:Destroy()
	end
end


local function makeDungeon(room, startRoomPos, roomsInX, roomsInZ)
	reset()
	
	local startRoom, finishRoom = makeRooms(room, startRoomPos, roomsInX, roomsInZ)
	
	startRoom.Closed.Value = true
	
	repeat
		local closedRoom = findClosedRandomRoom()
		
		if closedRoom then
			local openNeighbourRoom = findRandomOpenNeighbourRoom(closedRoom)
			openDoors(closedRoom, openNeighbourRoom)
			
			openNeighbourRoom.Closed.Value = true
			
			wait()
		end
		
	until closedRoom == nil
	
	-- open all outside of finish
	for i, door in pairs(finishRoom.Doors:GetChildren()) do
		door.Transparency = 1
		door.CanCollide = false
	end
	
end



local roomsModel = game.Workspace.Rooms
local roomModel = game.ReplicatedStorage.Room

local roomsInX = 5
local roomsInZ = 5

wait(15)

makeDungeon(roomModel, roomModel.Center.Position, roomsInX, roomsInZ)

Now that is a really long script. So I will explain it slowly.

First of all, I’ll just tell that I used Prim’s algorithm for this.
So you will need to know prim’s algorithm first.

Prim’s algorithm is just an algorithm for making mazes and stuff like that. So how does it work?

Well, you will first have a few nodes connected randomly for example:

Now here’s what you have to do:
First, close the start node.

(I’m representing closed nodes as green and open nodes as red)

Now the next step is the most important it goes like this

repeat

find a random closed node which has an open neighbour
(for example in the above example, A is the closed node. And B and D are its neighbours.
So you just check whether any one of its neighbours(B or D) is open or not. if it’s not open then
choose another closed node)

Then you just choose any one of its open neighbours and close it
After closing its neighbour, open the doors between A and B
(I’m representing opening the door with a green line.)

until no more open nodes are nodes found

Example:

In the above A was the only closed node. So I chose that.
Then I chose the neighbour D and closed it. Then I opened the doors of A and D

And then I had to choose a node that had an open neighbour.
I chose A as it had an open neighbour
Then I chose the open neighbour B and closed it and opened the doors

And I will continue on like that

Then I chose the closed node D and chose the open neighbour G and closed G and made doors.

Then I chose closed node D and chose E as the open neighbour.

Then I chose the closed node E and chose open neighbour F

Then I chose the closed node F and open neighbour C

Then I chose the closed node F and open neighbour I


then I chose closed node I and open node H.

Now here’s the final result

Now that was just the explanation of the prim’s algorithm

Now I will briefly explain the script.
First, there are 6 functions -

  1. makeRooms(room, startRoomPos, roomsInX, roomsInZ)
    Parameters -
    room = You just have to give it an example of the room you want to duplicate
    startRoomPos = It is just the starting room Position. (Note that the start room is always in the top left)
    roomsInX = Just how many rooms you want in x Direction
    roomsInZ = Just how many rooms you want in z Direction

Returns - startRoom and finishRoom

  1. openDoors(room1, room2)
    Parameters -
    You just have to give it the two adjacent rooms and it will open the doors for both the rooms

  2. checkCenterPartAndOpen(position)
    Parameters -
    position

This is a function that you just need for some calculation. Basically, it just checks if there is a room in the position and it also checks if the room is open

  1. findClosedRandomRoom()
    Parameters - none
    So this function just finds a closed room which has an open room near it
    Returns - the found node

  2. findRandomOpenNeighbourRoom(room)
    Parameters = room
    So this function just selects a random open neighbour room of the parameter
    Returns - the found node

  3. reset()
    Just resets some value to make another dungeon and delete the existing one

And then using all these functions, you combine them to make the main algorithm

local function makeDungeon(room, startRoomPos, roomsInX, roomsInZ)
	reset()
	
	local startRoom, finishRoom = makeRooms(room, startRoomPos, roomsInX, roomsInZ)
	
	startRoom.Closed.Value = true
	
	repeat
		local closedRoom = findClosedRandomRoom()
		
		if closedRoom then
			local openNeighbourRoom = findRandomOpenNeighbourRoom(closedRoom)
			openDoors(closedRoom, openNeighbourRoom)
			
			openNeighbourRoom.Closed.Value = true
			
			wait()
		end
		
	until closedRoom == nil
	
	-- open all outside of finish
	for i, door in pairs(finishRoom.Doors:GetChildren()) do
		door.Transparency = 1
		door.CanCollide = false
	end
	
end

Well, if you don’t understand how the main function works, just look at this pseudo code

-- this is pseudo-code so do attempt to use this in scripts

local function makeDungeon(room, startRoomPos, roomsInX, roomsInZ)
	reset()
	
	makeRooms(roomModel, roomModel.Center.Position, roomsInX, roomsInZ)
	
	repeat
		local closedNode = findClosedRandomRoom()
		
		if closedRoom then
			local openNeighbourRoom = findRandomOpenNeighbourRoom(closedRoom)
			open the doors of the closed room and open neighbour room
			
			close the new neighbour node
			
			wait()
		end
		
	until no more closed rooms are there that have an open neighbour

    open all doors of the finishNode
end

And that’s the algorithm

If you want to know how to the other functions work, then you could try to understand them yourself or ask me

Setup of Dungeon

So now there are a few things to do before executing the code.

So first make the room you want to copy and put it in replicatedStorage

Then, it MUST HAVE the following as a CHILD and NOT ANY DESCENDANT

  1. A boolean value set to false

  2. A rectangular/square part Named (“Floor”) (the floor of the room)

  3. A PrimaryPart named (“Center”) and it should be in the center of the rooms

  4. It should also have a model named (“Doors”) with the same exact format of names

And also make sure that you have the doors placed exactly as given in the picture

Then, make sure that there is an empty model named (“Rooms”) in the workspace

So basically that’s it.

Sorry for making this post so long and if you have any questions, please do ask

8 Likes

Update

I finally figured a way to connect the rooms together, but some of the rooms overlap each other.


Am I doing something wrong?

Code
function dungeon:createRoom(newRoom, parent, previousRoom)
	local room = newRoom:Clone()

	if previousRoom then
		print("New Room: "..newRoom.Name.." Previous Room:"..previousRoom.Name)
		local nodes = {}
		
		for _, node in ipairs(previousRoom.Nodes:GetChildren()) do
			table.insert(nodes, node)
		end
		
		local randomNode = nodes[math.random(#nodes)]
		print("Random Node: "..randomNode.Name)

		room:SetPrimaryPartCFrame(CFrame.new(randomNode.Position + (randomNode.CFrame.LookVector * (room.Floor.Size.X)/2), randomNode.Position))
	end
	
	room.Parent = parent
	return room
end
1 Like

So this random maze making is quite complicated and may be very hard to understand and make.
So I have made this module for you that will help you significantly. :slightly_smiling_face:
The module is just a modified version of the script that I wrote in the previous post.

Module - DungeonCreatorModule - Roblox

So just download the module. then go to studio → toolbox → my models → DungeonCreatorModule

Make you can put the module into game.ServerScriptService

Now I will explain how to use the module.

First of all, you need to have a proper room. The room must be a square or a rectangle.
The room should also have a part named (“Floor”) which is to be placed down as the floor(obviously)
And all of its doors must be in the middle of the wall.
The room should also have a primary part named (“Center”) and it should be placed in the center of the room (Y-axis doesn’t matter)
Examples-

In the above picture, the thin black line represents the center of the walls.

I’m really sorry if I have confused you badly. If I have confused you then you could just use this model and resize it to your liking.

Model of the room - Room - Roblox
Put the room into game.ReplicatedStorage

Now I will get into the actual scripting part you have to do.
Just make a script and put it somewhere.

Then inside of that script type

local dungeonModule = require(game.ServerScriptService.DungeonCreatorModule)

local room = game.ReplicatedStorage.Rooms
local startRoomCFrame = -- wherever you want the start room to be 
--(eg. CFrame.new(Vector3.new(0, 10, 0) or room.Center.CFrame)

local roomsInX = -- no. of rooms you want in horizontal direction (eg. 3)
local roomsInY = -- no. of rooms you want in vertical direction (eg. 3)

-- And basically, that's it

-- Whenever you want to execute the dungeonMakingFunction, just do
dungeonModule.makeDungeon(room, startRoomCFrame, roomsInX, roomsInY)

I really hope I was clear to you because doing all of this took a lot of hard work and effort. (It was also a fun challenge for me)

If you’re still confused, just check out this Roblox place.

Place - Dungen module.rbxl (25.3 KB)

If the place isn’t opening, just right-click on the link → open in new tab

I hope I helped you with making this. :slight_smile::

2 Likes

Your generator creates a maze based on one specific room layout, which isn’t what I’m looking for. I want mine to be based off on different room layouts, like the ones shown below:

I’ve figured out a way to spawn them, the only issue I’m facing is the rooms overlapping each other.

Here’s the code of what I got:

I’ve been thinking about using rays to determine if a room is placed where it’s should be, but I don’t know where to go with it. I’m runnin’ out of ideas at this point :joy:

Thank you for helping me. Your generator helps and is slightly what I’m looking for; it’s just that I want to see if I could get it on my own instead of having to copy-n-paste as a last resort.

Right now what I’m doing for collisions is having each room occupy cells on a grid, and to detect you find out which cells a new room will occupy if spawned, and if none of the cells are occupied you can place the room there and set the cells to occupied

local Cell = require(script.Parent.Cell)

local Grid = {}
Grid.__index = Grid


local function testPartsAtPositions(...)
	for i, v in ipairs({...}) do
		local part = game.ReplicatedStorage.TestPart:Clone()
		part.Position = v
		part.Parent = workspace
	end
end

function Grid.new(cframe, countX, countY, cellSize)
	local self = setmetatable({}, Grid)
	
	self.Cells = {}
	self.CFrame = cframe
	self.CountX = countX
	self.CountY = countY
	self.XWidth = countX * cellSize / 2
	self.YWidth = countY * cellSize / 2
	
	for x = 1, countX do
		self.Cells[x] = {}
		for y = 1, countY do
			local worldCFrame= cframe + (Vector3.new(x, 0, y) * cellSize) - Vector3.new(self.XWidth, 0, self.YWidth)
			local cell = Cell.new(x, y, worldCFrame, cellSize)
			cell:Display()
			self.Cells[x][y] = cell
		end
	end
	
	return self
end

function Grid:GetCellFromWorldPosition(worldPosition)
	for x = 1, self.CountX do
		for y = 1, self.CountY do
			local cell = self.Cells[x][y]
			if cell.WorldPosition.X == worldPosition.X and cell.WorldPosition.Z == worldPosition.Z then
				return cell
			end
		end
	end	
end

function Grid:GetCellsBetween(cellA, cellB)
	local cells = {}
	local diffX = math.abs(cellA.X - cellB.X)
	local diffY = math.abs(cellA.Y - cellB.Y)
	

	
	local lesserX = math.min(cellA.X, cellB.X)
	local lesserY = math.min(cellA.Y, cellB.Y)
	
	for x = 1, diffX+1 do
		for y = 1, diffY+1 do
			local cell = self.Cells[lesserX+x-1][lesserY+y-1]
			table.insert(cells, cell)
		end	
	end
	
	return cells
end

function Grid:GetCellRegion(cframe, size)
	local halfCellSize = 5
	local halfSizeX = (size.X/2) - halfCellSize
	local halfSizeZ = (size.Z/2) - halfCellSize
	
	
	local cornerCFA = cframe * CFrame.new(-halfSizeX, 0, -halfSizeZ)
	local cornerCFB = cframe * CFrame.new(halfSizeX, 0, halfSizeZ)

	--testPartsAtPositions(cframe.Position, cornerCFA.Position, cornerCFB.Position)
	
	local cellA = self:GetCellFromWorldPosition(cornerCFA.Position)
	local cellB = self:GetCellFromWorldPosition(cornerCFB.Position)
	
	
	if cellA and cellB then
		return self:GetCellsBetween(cellA, cellB)
	end
	
end

function Grid:IsCellRegionOccupied(cellRegion)
	for _, cell in ipairs(cellRegion) do
		if cell.Occupied then
			return true
		end
	end
	return false
end


return Grid

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local wireCube = ReplicatedStorage.WireCube

local Cell = {}
Cell.__index = Cell

function Cell.new(x, y, worldCFrame, size)
	local self = setmetatable({}, Cell)
	
	self.X = x
	self.Y = y
	self.Occupied = false
	self.Size = size
	
	self.WorldCFrame = worldCFrame
	self.WorldPosition = worldCFrame.Position
	
	return self
end

function Cell:Display()
	local cube = wireCube:Clone()
	self.WireCube = cube
	cube.Size = Vector3.new(self.Size, 0, self.Size)
	cube.SurfaceGui.TextLabel.Text = "(" .. self.X .. ", " .. self.Y .. ")"
	cube.CFrame = self.WorldCFrame
	cube.Parent = workspace
end

return Cell

Not all of the code is shown because I’m still working on it

4 Likes

I didn’t quite get what you meant there.

Because my random maze generator can also make mazes that have those kinds of layouts of some rooms.

Example:



Sorry for the bad quality.

The only limitation in my design is that the dungeon can be a rectangle or square.

I have a video on a maze generated with Prim’s algorithim here.
You can also copy the source code and replace the Wall base part with some model, although you would have to do the orientation yourself.

1 Like

I made a good dungeon creator, look! proceduraldungeon.rbxl (31.6 KB)

1 Like


RobloxScreenShot20210706_154030493 (2)

I dont know if you have solved your issue yet but I made a quite complex generation system that allows for any size rooms tunnels corners and whatever

First I need to say the blue ones are the holographic version of the dungeon cause I was too lazy to actually take good pictures of it when I was working on this project

Anyway each “room” or tunnel or something has a few parts in it.
It always has a “BOUNDS” part. that is a part that encompesis the whole structure, and in code before finalizing creating a room you check to make sure its not in the bounds of another

and at least 1 “SNAP” part

The snap parts are where other rooms and tunnels can connect together
You can put the snaps anywhere as long as any other room can connect to them and not touch the bounds

One thing you might be wondering is how to deal with rotating stuff to make sure that it is correct
The way you do that is when you find a snap you want to attach smthn to the code picks a random room/tunnel/corner/whatever and sets its primary part to one of its snaps then sets its primary part cframe to the cframe of the other snap it is trying to connect to
then it checks if it is in bounds, if it is, rotate it 90 deg
if its not then it must be oriented correctly
if it rotates more than 4 times than that means it cant fit there so try to place some other thing and if it continues to fail place an end cap
an end cap is just like a room or whatever but it is really thin and when connecting on to another snap it will block the doorway but it wont really add any depth to the end of the hallway so that’s good if that hallway has buted up against smthn else

And final note
I did not take the time to do this cause I started working on other things but I (think) you could also merge hallways if you used unions to create holes in stuff

(this is my first post I think so sorry if it is incomplete / not helpful / hard to understand)

2 Likes

Have you any updates on this? I really like the idea you were going with. Because the way I thought of approaching this problem before finding this topic was using a chunk based system, then on each chunk randomly generate a room model from ReplicatedStorage though I couldn’t find a way on how actually to implement it

1 Like