Maze Generation Help

I have two problems:

  1. Maze genertation; I am making a maze generator, and everything works, including the exit however across from the exit there is a problem with the wall genertation. Below I have a picture showcasing the problem and the maze generation script. Please note that this happens only sometimes and is hard to fix, any help is appreciated.

Screenshot 2024-07-23 080628

Here is the script:

-- Variables
local MazeGeneration = {}

local debounce = false

-- Settings
local maze = {}
local pathWd = 10
local wallHt = 15
local wallHtPos = wallHt / 2
local wallThick = 2
local wallLen = pathWd + 2 * wallThick
local wallOffset = pathWd / 2 + wallThick / 2
local floorHt = 1
local floorHtPos = floorHt / 2
local floorTileDist = pathWd + wallThick

local stack = {}
table.insert(stack, { zVal = 0, xVal = 0 })
local cols = 10 -- Number of columns
local rows = 10 -- Number of rows
local rnd = Random.new()

-- Unload maze
function MazeGeneration.UnLoad()
	for _, part in pairs(game.Workspace.Maze:GetChildren()) do
		part:Destroy()
	end

	for _, part in pairs(game.Workspace.Waypoints:GetChildren()) do
		part:Destroy()
	end

	game.Workspace:FindFirstChild("Enemy"):Destroy()

	maze = {}
	stack = {}
	table.insert(stack, { zVal = 0, xVal = 0 })
end

-- Creates a part
local function CreatePart(x, y, z, px, py, pz)
	local part = Instance.new("Part", game.Workspace.Maze)
	part.Anchored = true
	part.Size = Vector3.new(x, y, z)
	part.Position = Vector3.new(px, py, pz)
	part.TopSurface = Enum.SurfaceType.Smooth
	part.Material = Enum.Material.Concrete
	part.BrickColor = BrickColor.new("Dark stone grey")
	return part
end

-- Creates a waypoint
local function CreateWaypoint(px, py, pz)
	local part = Instance.new("Part", game.Workspace.Waypoints)
	part.Anchored = true
	part.Size = Vector3.new(1, 1, 1)
	part.Position = Vector3.new(px, py + 1, pz)
	part.Transparency = 1
	part.CanCollide = false
	part.CanTouch = false
end

-- Creates the floor
local function CreateFloor()
	for z = 0, rows - 1 do
		maze[z] = {}
		for x = 0, cols - 1 do
			local posX = x * floorTileDist
			local posZ = z * floorTileDist
			local part = CreatePart(pathWd, floorHt, pathWd, posX, floorHtPos, posZ)
			local waypoint = CreateWaypoint(posX, floorHtPos, posZ)
			maze[z][x] = { tile = part }
		end
	end
end

-- Creates the walls
local function CreateWalls()
	local exitPosX = (cols - 1) * floorTileDist + wallOffset
	local exitPosZ = (rows - 1) * floorTileDist

	for z = 0, rows - 1 do
		for x = 0, cols - 1 do
			-- East Walls
			local posX = x * floorTileDist + wallOffset
			local posZ = z * floorTileDist
			if posX or posZ then
				local part = CreatePart(wallThick, wallHt, wallLen, posX, wallHtPos, posZ)
				maze[z][x].eastWall = part

				if posX == exitPosX and posZ == exitPosZ then
					part.Color = Color3.fromRGB(0, 255, 0)
					part.Material = Enum.Material.Neon
					part.Name = "Exit"
				end

				if maze[z][x + 1] then
					maze[z][x + 1].westWall = part
				end
			end

			-- South Walls
			posX = x * floorTileDist
			posZ = z * floorTileDist + wallOffset
			if posX or posZ then
				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
			end
		end
	end

	-- Create edge walls except at the exit position

	-- West edge walls
	for z = 0, rows - 1 do
		local posX = -wallOffset
		local posZ = z * floorTileDist
		if posZ ~= exitPosZ then
			CreatePart(wallThick, wallHt, wallLen, posX, wallHtPos, posZ)
		end
	end

	-- North edge walls
	for x = 0, cols - 1 do
		local posX = x * floorTileDist
		local posZ = -wallOffset
		if posX ~= exitPosX then
			CreatePart(wallLen, wallHt, wallThick, posX, wallHtPos, posZ)
		end
	end

	-- Create the exit part separately
	--[[local exitPart = CreatePart(wallThick, wallHt, wallLen, exitPosX, wallHtPos, exitPosZ - wallOffset)
	exitPart.Color = Color3.fromRGB(0, 255, 0)
	exitPart.Material = Enum.Material.Neon
	exitPart.Name = "Exit"--]]

	-- Add a wall across from the exit part if there is no wall
	if maze[(rows - 1)][(cols - 1)] then
		local exitWallX = exitPosX + -(rows * (pathWd + wallThick))
		local exitWallZ = exitPosZ -- Adjust to place wall across from the exit

		-- Check if the part at the position already exists
		local existingWall = game.Workspace.Maze:FindFirstChildWhichIsA("Part") and game.Workspace.Maze:FindFirstChildWhichIsA("Part").Position == Vector3.new(exitWallX, wallHtPos, exitWallZ)
		if not existingWall then
			local wallAcrossExit = CreatePart(wallThick, wallHt, wallLen, exitWallX, wallHtPos, exitWallZ)
			wallAcrossExit.BrickColor = BrickColor.new("Dark stone grey")
			wallAcrossExit.Material = Enum.Material.Concrete
		end
	end
end

-- Remove wall
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)
end

-- Redraw the maze
local function Redraw()
	for z = 0, rows - 1 do
		for x = 0, cols - 1 do
			local cell = maze[z][x]
			if cell.visited then
				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

-- Fun math checks
local function GetUnvisitedNeighbours(z, x)
	local neighbours = {}

	-- North
	if maze[z - 1] and not maze[z - 1][x].visited then
		table.insert(neighbours, 0)
	end

	-- East
	if maze[z][x + 1] and not maze[z][x + 1].visited then
		table.insert(neighbours, 1)
	end

	-- South
	if maze[z + 1] and not maze[z + 1][x].visited then
		table.insert(neighbours, 2)
	end

	-- West
	if maze[z][x - 1] and not maze[z][x - 1].visited then
		table.insert(neighbours, 3)
	end

	return neighbours
end

-- Searching algorithm
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
	task.wait()
	local neighbours = GetUnvisitedNeighbours(z, x)

	if #neighbours > 0 then
		local idx = rnd:NextInteger(1, #neighbours)
		local nextCellDir = neighbours[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

-- Generate Maze
function MazeGeneration.Generate(difficulty)
	if difficulty == 0 then
		cols = rnd:NextInteger(5, 6)
		rows = rnd:NextInteger(5, 6)
	elseif difficulty == 1 then
		cols = rnd:NextInteger(8, 10)
		rows = rnd:NextInteger(8, 10)
	elseif difficulty == 2 then
		cols = rnd:NextInteger(15, 20)
		rows = rnd:NextInteger(15, 20)
	elseif difficulty == 3 then
		cols = rnd:NextInteger(25, 30)
		rows = rnd:NextInteger(25, 30)
	end

	CreateFloor()
	CreateWalls()
	maze[0][0].visited = true

	while SearchPath() do
		Redraw()
		task.wait() -- remove for game; add if maze size is over 40x40
	end
	
	task.wait(5)
	-- Clone enemy and ensure visibility
	local enemy = script.Enemy:Clone()
	enemy.Parent = game.Workspace

	-- Ensure enemy parts are visible
	task.wait(5)
	for _, bodyPart in ipairs(enemy:GetDescendants()) do
		if bodyPart:IsA("BasePart") and bodyPart.Name ~= "HumanoidRootPart" then
			bodyPart.Transparency = 0
		end
	end
end

return MazeGeneration
  1. N.P.C., everything works but when the enemy (aka N.P.C.) found the player and should go to attack the player it just stands there, until the player leaves again. Here is the handler of the N.P.C.
-- Services
local Players = game:GetService("Players")
local PathfindingService = game:GetService("PathfindingService")

-- Constants
local ATTACK_DISTANCE = 5.5
local WAIT_TIME_BEFORE_PATROL = 7
local WAYPOINT_CHECK_INTERVAL = 0.1

-- Variables
local player = Players.LocalPlayer
local char = player.Character or player.CharacterAdded:Wait()
local enemy = nil
local waypointsFolder = game.Workspace:FindFirstChild("Waypoints")

repeat
	task.wait(0.1)
until game.Workspace:FindFirstChild("Enemy")

enemy = game.Workspace:FindFirstChild("Enemy")

-- Animations and Sounds
local walkAnim = enemy:WaitForChild("Humanoid"):LoadAnimation(script.WalkAnim)
local attackAnim = enemy.Humanoid:LoadAnimation(script.AttackAnim)
local walkSound = script.WalkSound
local attackSound = script.AttackSound

-- Pathfinding parameters
local pathParams = {
	AgentHeight = 5,
	AgentRadius = 3,
	AgentCanJump = false
}

-- Wait until enemy and waypoints are loaded
repeat
	task.wait(WAYPOINT_CHECK_INTERVAL)
	enemy = game.Workspace:FindFirstChild("Enemy")
until enemy and waypointsFolder and #waypointsFolder:GetChildren() > 0

-- Functions
local function CheckForCharacter(char)
	local rayOrigin = enemy:FindFirstChild("HumanoidRootPart").Position
	local rayDirection = (char.HumanoidRootPart.Position - rayOrigin).Unit * 40
	local raycastResult = workspace:Raycast(rayOrigin, rayDirection, RaycastParams.new())
	
	if raycastResult then
		local raycastInstance = raycastResult.Instance
		
		if raycastInstance:IsDescendantOf(char) then
			return true
		end
	else
		return false
	end
end

local function FindNearestPlayers()
	local players = Players:GetPlayers()
	local nearestPlayer = nil
	local maxDistance = 40
	
	for _, player in pairs(players) do
		if player.Character ~= nil then
			local targetCharacter = player.Character
			local distance = (enemy.HumanoidRootPart.Position - targetCharacter.HumanoidRootPart.Position).Magnitude
			
			if distance < maxDistance and CheckForCharacter(targetCharacter) then
				nearestPlayer = targetCharacter
				maxDistance = distance
			end
		end
	end
	
	return nearestPlayer
end

local function CalculatePath(destination)
	local path = PathfindingService:CreatePath(pathParams)
	path:ComputeAsync(enemy.HumanoidRootPart.Position, destination)
	return path
end

local function Attack(char)
	local distance = (enemy.HumanoidRootPart.Position - char.HumanoidRootPart.Position).Magnitude
	
	if distance > ATTACK_DISTANCE then
		local path = CalculatePath(char.HumanoidRootPart.Position)
		if path.Status == Enum.PathStatus.Success then
			if not walkSound.Playing then
				walkAnim:Play()
				walkSound:Play()
			end
		end
	else
		walkSound:Stop()
		walkAnim:Stop()
		attackAnim:Play()
		attackSound:Play()
		task.wait(0.7)
		char.Humanoid.Health = 0
	end
end

local function WalkToDesination(destination)
	local path = CalculatePath(destination)
	
	if path.Status == Enum.PathStatus.Success then
		if not walkSound.Playing then
			walkAnim:Play()
			walkSound:Play()
		end
		for _, waypoint in pairs(path:GetWaypoints()) do
			local nearestPlayer = FindNearestPlayers()
			if nearestPlayer then
				Attack(nearestPlayer)
				break
			else
				enemy.Humanoid:MoveTo(waypoint.Position)
				enemy.Humanoid.MoveToFinished:Wait()
			end
		end
	else
		enemy.Humanoid:MoveTo(destination - (enemy.HumanoidRootPart.CFrame.LookVector * 10))
	end
end

local function Patrol()
	local waypoints = workspace.Waypoints:GetChildren()
	if #waypoints > 0 then
		local randomNumber = math.random(1, #waypoints)
		
		WalkToDesination(waypoints[randomNumber].Position)
	end
end

-- Start patrol after initial wait
task.wait(WAIT_TIME_BEFORE_PATROL)
Patrol()

-- Check for enemy and waypoints continuously
while true do
	repeat
		task.wait(WAYPOINT_CHECK_INTERVAL)
		enemy = game.Workspace:FindFirstChild("Enemy")
	until enemy

	repeat
		task.wait(WAYPOINT_CHECK_INTERVAL)
	until #waypointsFolder:GetChildren() > 0

	task.wait(WAIT_TIME_BEFORE_PATROL)  -- Wait before starting patrol again
	while true do
		Patrol()
		task.wait(0.1)
	end
end

Any help makes my day 2Mx better :slight_smile:

1 Like

Problem 1.
If your maze is never having any sort of offset then just place a part where the exit of the maze is so it doesnt go away

1 Like

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