What I Want to Achieve
I’m creating a procedural Pac-Man style maze generator that creates a 25x25 grid maze with a central ghost house. The maze should have proper Pac-Man aesthetics (blue walls, black floor) and a ghost house with the characteristic pink gate.
The Issues
While my current script works, there are several problems that need to be addressed:
Ghost House Problems
- The ghost house floor isn’t level with the maze floor - there’s a noticeable height difference
- Sometimes maze walls are generated in front of the ghost house gate, blocking it
- The ghost house position can be slightly offset from the grid alignment
Performance Issues
- The maze generation can be slow on larger grids
- Wall and corner piece deletion might be inefficient
- There’s no pellet generation system yet
- Need better error handling
Here’s what the maze currently looks like
What I’ve Tried
I’ve implemented the following:
- Used a modified depth-first search algorithm with noise-based randomisation
- Implemented corner piece management
- Created a central ghost house system
- Added proper wall removal logic
- Attempted to fix ghost house elevation by adjusting the base position, but still having issues
- Tried to prevent walls from spawning in front of the gate by clearing the area, but walls still sometimes generate there
Here’s my current code:
-- Original procedural generated maze by GibbleDev
--[[
Pac-Man Style Maze Generator
Purpose: Generates a procedural Pac-Man-style maze with a central ghost house
Key Features:
- 25x25 grid maze generation
- Central ghost house with pink gate
- Perlin noise-based path randomization
- Depth-first search maze algorithm
Dependencies:
- Requires a Segment model in the workspace
- Segment model must contain: WallL, WallR, WallT, WallB, Floor parts
Known Issues:
- Performance could be improved with batched wall deletions
- No pellet/power pellet generation
- No spawn points for Pac-Man/ghosts
--]]
local Segment = script.Segment
local Workspace = game:GetService("Workspace")
-- Configuration
-- All major maze parameters and colors are defined here for easy modification
local Config = {
SEED = 1, -- Random seed for maze generation
SIZE_X = 25, -- Maze width in cells
SIZE_Y = 25, -- Maze height in cells
CONTROL = math.pi, -- Noise control parameter
COLORS = {
WALL = Color3.fromRGB(33, 33, 255), -- Pac-Man blue walls
FLOOR = Color3.fromRGB(0, 0, 0), -- Black floor
CORNER = Color3.fromRGB(33, 33, 255), -- Same as walls
GHOST_GATE = Color3.fromRGB(255, 192, 203) -- Pink gate
},
GHOST_HOUSE = {
WIDTH = 5, -- Width in cells
HEIGHT = 3, -- Height in cells
GATE_WIDTH = 2 -- Width of the pink gate in cells
}
}
-- Cache frequently used values for performance
local scale = Segment.WallL.Size.Y
local halfScaleX = Config.SIZE_X * scale / 2
local halfScaleY = Config.SIZE_Y * scale / 2
local wallThickness = Segment.WallL.Size.X
local wallHeight = scale
--[[
Creates a corner piece for maze cells
Returns: Instance (Part)
- Configured with proper size, material, and color
- Automatically anchored
--]]
local function createCornerPiece()
local corner = Instance.new("Part")
corner.Size = Vector3.new(wallThickness, scale, wallThickness)
corner.Anchored = true
corner.Material = Enum.Material.SmoothPlastic
corner.Color = Config.COLORS.CORNER
corner.TopSurface = Enum.SurfaceType.Smooth
corner.BottomSurface = Enum.SurfaceType.Smooth
return corner
end
--[[
Applies Pac-Man visual style to a maze segment
Parameters:
- segment: Model (The maze segment to style)
Applies proper colors and materials to walls and floor
--]]
local function applyPacManStyle(segment)
-- Color the walls
segment.WallL.Color = Config.COLORS.WALL
segment.WallR.Color = Config.COLORS.WALL
segment.WallT.Color = Config.COLORS.WALL
segment.WallB.Color = Config.COLORS.WALL
-- Set wall material
segment.WallL.Material = Enum.Material.SmoothPlastic
segment.WallR.Material = Enum.Material.SmoothPlastic
segment.WallT.Material = Enum.Material.SmoothPlastic
segment.WallB.Material = Enum.Material.SmoothPlastic
-- Color the floor
segment.Floor.Color = Config.COLORS.FLOOR
segment.Floor.Material = Enum.Material.SmoothPlastic
end
--[[
Removes grid cells where the ghost house will be placed
Parameters:
- grid: table (2D array of maze segments)
Clears the central area for ghost house placement
--]]
local function clearGhostHouseInterior(grid)
local centerX = math.floor(Config.SIZE_X / 2)
local centerY = math.floor(Config.SIZE_Y / 2)
local startX = centerX - math.floor(Config.GHOST_HOUSE.WIDTH / 2)
local startY = centerY - math.floor(Config.GHOST_HOUSE.HEIGHT / 2)
for y = startY, startY + Config.GHOST_HOUSE.HEIGHT - 1 do
for x = startX, startX + Config.GHOST_HOUSE.WIDTH - 1 do
if grid[y] and grid[y][x] then
grid[y][x]:Destroy()
grid[y][x] = nil
end
end
end
end
--[[
Creates the ghost house in the center of the maze
Parameters:
- mazeModel: Model (Parent model for the ghost house)
Returns: Model (The created ghost house)
Creates base, walls, and pink gate for ghost house
--]]
local function createGhostHouse(mazeModel)
local ghostHouse = Instance.new("Model")
ghostHouse.Name = "GhostHouse"
ghostHouse.Parent = mazeModel
local centerX = math.floor(Config.SIZE_X / 2)
local centerY = math.floor(Config.SIZE_Y / 2)
-- Create the base
local base = Instance.new("Part")
base.Name = "Base"
base.Size = Vector3.new(
Config.GHOST_HOUSE.WIDTH * scale,
wallThickness,
Config.GHOST_HOUSE.HEIGHT * scale
)
base.CFrame = CFrame.new(
(centerX * scale - halfScaleX) - scale/2,
0, -- Ground level
(centerY * scale - halfScaleY) - scale/2
)
base.Color = Config.COLORS.FLOOR
base.Material = Enum.Material.SmoothPlastic
base.Anchored = true
base.Parent = ghostHouse
-- Wall configuration for ghost house
local walls = {
-- Left wall
{
size = Vector3.new(wallThickness, wallHeight, Config.GHOST_HOUSE.HEIGHT * scale),
position = CFrame.new(-Config.GHOST_HOUSE.WIDTH/2 * scale + wallThickness/2, wallHeight/2, 0)
},
-- Right wall
{
size = Vector3.new(wallThickness, wallHeight, Config.GHOST_HOUSE.HEIGHT * scale),
position = CFrame.new(Config.GHOST_HOUSE.WIDTH/2 * scale - wallThickness/2, wallHeight/2, 0)
},
-- Back wall
{
size = Vector3.new(Config.GHOST_HOUSE.WIDTH * scale, wallHeight, wallThickness),
position = CFrame.new(0, wallHeight/2, Config.GHOST_HOUSE.HEIGHT/2 * scale - wallThickness/2)
}
}
-- Create blue walls
for _, wallConfig in ipairs(walls) do
local wall = Instance.new("Part")
wall.Size = wallConfig.size
wall.CFrame = base.CFrame * wallConfig.position
wall.Color = Config.COLORS.WALL
wall.Material = Enum.Material.SmoothPlastic
wall.Anchored = true
wall.Parent = ghostHouse
end
-- Create pink gate at the top
local gateWidth = Config.GHOST_HOUSE.GATE_WIDTH * scale
local gate = Instance.new("Part")
gate.Size = Vector3.new(gateWidth, wallHeight/2, wallThickness) -- Half height for gate
gate.CFrame = base.CFrame * CFrame.new(0, wallHeight * 0.75, -Config.GHOST_HOUSE.HEIGHT/2 * scale + wallThickness/2)
gate.Color = Config.COLORS.GHOST_GATE
gate.Material = Enum.Material.SmoothPlastic
gate.Anchored = true
gate.Parent = ghostHouse
-- Add side pieces for the gate (in blue)
local leftGateSide = Instance.new("Part")
leftGateSide.Size = Vector3.new((Config.GHOST_HOUSE.WIDTH * scale - gateWidth)/2, wallHeight, wallThickness)
leftGateSide.CFrame = base.CFrame * CFrame.new(-gateWidth/2 - leftGateSide.Size.X/2, wallHeight/2, -Config.GHOST_HOUSE.HEIGHT/2 * scale + wallThickness/2)
leftGateSide.Color = Config.COLORS.WALL
leftGateSide.Material = Enum.Material.SmoothPlastic
leftGateSide.Anchored = true
leftGateSide.Parent = ghostHouse
local rightGateSide = Instance.new("Part")
rightGateSide.Size = Vector3.new((Config.GHOST_HOUSE.WIDTH * scale - gateWidth)/2, wallHeight, wallThickness)
rightGateSide.CFrame = base.CFrame * CFrame.new(gateWidth/2 + rightGateSide.Size.X/2, wallHeight/2, -Config.GHOST_HOUSE.HEIGHT/2 * scale + wallThickness/2)
rightGateSide.Color = Config.COLORS.WALL
rightGateSide.Material = Enum.Material.SmoothPlastic
rightGateSide.Anchored = true
rightGateSide.Parent = ghostHouse
return ghostHouse
end
-- Initialize maze container
local mazeModel = Instance.new("Folder")
mazeModel.Name = "Grid"
mazeModel.Parent = Workspace
-- Remove baseplate if it exists
if Workspace:FindFirstChild("Baseplate") then
Workspace.Baseplate:Destroy()
end
--[[
Grid Initialization
Creates the initial maze grid with all walls intact
Each cell contains:
- Four walls (WallL, WallR, WallT, WallB)
- Floor
- Four corner pieces
--]]
local grid = {}
for y = 1, Config.SIZE_Y do
grid[y] = {}
for x = 1, Config.SIZE_X do
local segment = Segment:Clone()
segment:SetPrimaryPartCFrame(
CFrame.new(
x * scale - halfScaleX - scale/2,
0, -- Ground level
y * scale - halfScaleY - scale/2
)
)
-- Apply Pac-Man styling
applyPacManStyle(segment)
-- Add corner pieces
local cornerTL = createCornerPiece()
local cornerTR = createCornerPiece()
local cornerBL = createCornerPiece()
local cornerBR = createCornerPiece()
-- Position corners
local basePos = segment:GetPrimaryPartCFrame()
local halfScale = scale/2
cornerTL.CFrame = basePos * CFrame.new(-halfScale, 0, -halfScale)
cornerTR.CFrame = basePos * CFrame.new(halfScale, 0, -halfScale)
cornerBL.CFrame = basePos * CFrame.new(-halfScale, 0, halfScale)
cornerBR.CFrame = basePos * CFrame.new(halfScale, 0, halfScale)
cornerTL.Name = "CornerTL"
cornerTR.Name = "CornerTR"
cornerBL.Name = "CornerBL"
cornerBR.Name = "CornerBR"
cornerTL.Parent = segment
cornerTR.Parent = segment
cornerBL.Parent = segment
cornerBR.Parent = segment
segment.Name = y .. "," .. x
segment.Parent = mazeModel
grid[y][x] = segment
end
end
--[[
Direction Configuration
Defines movement and wall relationships for each direction
Each direction contains:
- dx, dy: Movement deltas
- wall1, wall2: Walls to remove when moving
- move: Movement code for backtracking
- corners: Corner pieces to remove
--]]
local DIRECTIONS = {
LEFT = {dx = -1, dy = 0, wall1 = "WallL", wall2 = "WallR", move = "B", corners = {"CornerTL", "CornerBL"}},
RIGHT = {dx = 1, dy = 0, wall1 = "WallR", wall2 = "WallL", move = "T", corners = {"CornerTR", "CornerBR"}},
UP = {dx = 0, dy = 1, wall1 = "WallB", wall2 = "WallT", move = "R", corners = {"CornerBL", "CornerBR"}},
DOWN = {dx = 0, dy = -1, wall1 = "WallT", wall2 = "WallB", move = "L", corners = {"CornerTL", "CornerTR"}}
}
-- Maze generation state tracking
local state = {
currentX = 1,
currentY = 1,
moves = {},
completed = 0
}
--[[
Validates if a cell position is within the maze bounds
Parameters:
- x, y: number (Cell coordinates)
Returns: boolean
--]]
local function isValidCell(x, y)
return x >= 1 and x <= Config.SIZE_X and y >= 1 and y <= Config.SIZE_Y and grid[y] and grid[y][x] ~= nil
end
--[[
Gets all unvisited neighboring cells
Returns: table (Array of valid neighbors with their directions)
--]]
local function getUnvisitedNeighbors()
local neighbors = {}
for _, dir in pairs(DIRECTIONS) do
local newX = state.currentX + dir.dx
local newY = state.currentY + dir.dy
if isValidCell(newX, newY) and not grid[newY][newX].GoneInto.Value then
table.insert(neighbors, {
cell = grid[newY][newX],
direction = dir
})
end
end
return neighbors
end
--[[
Removes walls between two cells when creating a path
Parameters:
- currentCell: Model (Current maze segment)
- chosenCell: Model (Target maze segment)
- direction: table (Direction configuration)
--]]
local function deleteWall(currentCell, chosenCell, direction)
-- Delete walls
currentCell[direction.wall2]:Destroy()
chosenCell[direction.wall1]:Destroy()
-- Delete corresponding corner pieces
for _, cornerName in ipairs(direction.corners) do
if currentCell:FindFirstChild(cornerName) then
currentCell[cornerName]:Destroy()
end
if chosenCell:FindFirstChild(cornerName) then
chosenCell[cornerName]:Destroy()
end
end
state.currentX = state.currentX + direction.dx
state.currentY = state.currentY + direction.dy
table.insert(state.moves, direction.move)
end
--[[
Implements backtracking when reaching dead ends
Returns: boolean (true if backtracking successful)
--]]
local function backtrack()
while #state.moves > 0 do
local lastMove = table.remove(state.moves)
for _, dir in pairs(DIRECTIONS) do
if dir.move == lastMove then
state.currentX = state.currentX - dir.dx
state.currentY = state.currentY - dir.dy
break
end
end
if #getUnvisitedNeighbors() > 0 then
return true
end
end
return false
end
--[[
Main Generation Loop
Implements a modified depth-first search algorithm with noise-based randomization
Key steps:
1. Check and mark current cell
2. Find unvisited neighbors
3. Choose direction using perlin noise
4. Create path by removing walls
5. Backtrack when needed
6. Create ghost house when complete
--]]
while true do
local currentCell = grid[state.currentY] and grid[state.currentY][state.currentX]
if not currentCell then
if not backtrack() then
break
end
continue
end
-- Mark cell as visited
if not currentCell.GoneInto.Value then
state.completed = state.completed + 1
currentCell.GoneInto.Value = true
end
-- Get available neighbors
local neighbors = getUnvisitedNeighbors()
if #neighbors > 0 then
-- Use perlin noise for direction selection
local noise = math.noise(state.currentX, Config.SEED/Config.CONTROL, state.currentY)
if noise == 0 then
noise = math.noise(state.currentX/Config.CONTROL, Config.SEED/Config.CONTROL, state.currentY/Config.CONTROL)
end
-- Calculate direction based on noise
local percentage = math.floor((noise - math.floor(noise)) * 1000)
local index = math.floor(percentage / (1000/#neighbors)) + 1
local chosen = neighbors[math.min(index, #neighbors)]
-- Create path in chosen direction
deleteWall(currentCell, chosen.cell, chosen.direction)
else
-- Check if maze is complete
local validCells = 0
for y = 1, Config.SIZE_Y do
for x = 1, Config.SIZE_X do
if grid[y] and grid[y][x] then
validCells = validCells + 1
end
end
end
-- Finish generation if all cells visited
if state.completed >= validCells then
clearGhostHouseInterior(grid)
local ghostHouse = createGhostHouse(mazeModel)
break
else
-- Backtrack if dead end reached
if not backtrack() then
break
end
end
end
task.wait() -- Yield to prevent script timeout
end
Specific Questions
- How can I ensure the ghost house floor is level with the maze floor?
- What’s the best way to prevent walls from generating in front of the ghost house gate?
- How can I improve the ghost house positioning to align perfectly with the grid?
- How can I optimise the wall deletion process? Currently, I’m deleting walls one at a time.
- What’s the best way to implement pellet generation after the maze is created?
- Is there a more efficient way to handle corner pieces?
- How can I implement proper error handling for missing dependencies?
Additional Information
- The script uses a Segment model with predefined wall and floor parts
- Currently using task.wait() during generation to prevent script timeout
- Using perlin noise for randomisation
- Ghost house is created after maze generation but before path creation
Any help or suggestions would be greatly appreciated! I’m particularly interested in fixing the ghost house issues and improving overall maze generation performance.
Note: All code is provided and I’m looking for optimisation help, not asking anyone to write the system for me.