(Help) AStar Pathfinding Script Getting My Game To Timeout The script Without to Understand The Error!

You can write your topic however you want, but you need to answer these questions:

  1. What do you want to achieve? Keep it simple and clear!
    I have created an A* pathfinding Script that works great based on this tutorial:
    Improving our A* (Part 2)

The target is a part in the workspace and the start position is the NPC humanoid rootpart which gets updated every time the npc reached its previous node.

Here is the code for the script:

  1. What is the issue? Include screenshots / videos if possible!

When i move the part while the code is running, Studio freezes for few seconds then i get errors. Which i dont understand why they happen.


  1. What solutions have you tried so far? Did you look for solutions on the Developer Hub?
    I am stumped, i dont know what to do since i dont understand why i keep getting the errors.
-- Priority Queue (Min-Heap) Implementation
local PriorityQueue = {}
PriorityQueue.__index = PriorityQueue

function PriorityQueue.new()
	return setmetatable({ heap = {} }, PriorityQueue)
end

function PriorityQueue:Push(node, priority)
	table.insert(self.heap, {node = node, priority = priority})
	self:BubbleUp(#self.heap)
end

function PriorityQueue:Pop()
	local root = self.heap[1]
	local last = table.remove(self.heap)
	if #self.heap > 0 then
		self.heap[1] = last
		self:BubbleDown(1)
	end
	return root.node
end

function PriorityQueue:BubbleUp(index)
	local parentIndex = math.floor(index / 2)
	if parentIndex > 0 and self.heap[index].priority < self.heap[parentIndex].priority then
		self.heap[index], self.heap[parentIndex] = self.heap[parentIndex], self.heap[index]
		self:BubbleUp(parentIndex)
	end
end

function PriorityQueue:BubbleDown(index)
	local leftChildIndex = index * 2
	local rightChildIndex = index * 2 + 1
	local smallest = index

	if leftChildIndex <= #self.heap and self.heap[leftChildIndex].priority < self.heap[smallest].priority then
		smallest = leftChildIndex
	end

	if rightChildIndex <= #self.heap and self.heap[rightChildIndex].priority < self.heap[smallest].priority then
		smallest = rightChildIndex
	end

	if smallest ~= index then
		self.heap[index], self.heap[smallest] = self.heap[smallest], self.heap[index]
		self:BubbleDown(smallest)
	end
end

function PriorityQueue:IsEmpty()
	return #self.heap == 0
end

-- A* Pathfinding in ROBLOX with Vector3 Nodes of Size 3 and Priority Queue
local function Heuristic(a, b)
	return (math.abs(a.X - b.X) + math.abs(a.Y - b.Y) + math.abs(a.Z - b.Z))
end


local function roundToStep(pos : Vector3)
	return Vector3.new(math.floor(pos.X/3)*3+1.5,math.floor(pos.Y/3)*3+1.5, math.floor(pos.Z/3)*3+1.5)
end
local offsets = {
	Vector3.new(3, 0, 0), Vector3.new(-3, 0, 0),
	Vector3.new(0, 3, 0), Vector3.new(0, -3, 0),
	Vector3.new(0, 0, 3), Vector3.new(0, 0, -3),
	Vector3.new(3, 0, 3), Vector3.new(3, 0, -3),
	Vector3.new(-3, 0, 3), Vector3.new(-3, 0, -3)
}
local function AStar(start, goal)
	local openSet = PriorityQueue.new()
	openSet:Push(start, 0)
	local cameFrom = {}
	local gScore = {[start] = 0}
	local fScore = {[start] = Heuristic(start, goal)}

	while not openSet:IsEmpty() do
		local current = openSet:Pop()

		if current == goal then
			local totalPath = {current}
			while cameFrom[current] do
				current = cameFrom[current]
				table.insert(totalPath, 1, current)
			end
			return totalPath
		end

		
		for _, offset in pairs(offsets) do
			local neighbor = roundToStep(current + offset) 
			local inPart = #workspace:getPartBoundsInRadius(neighbor, 0.2) > 0
			if inPart == false then
				local tentative_gScore = gScore[current] + (neighbor - current).Magnitude
				if not gScore[neighbor] or tentative_gScore < gScore[neighbor] then
					cameFrom[neighbor] = current
					gScore[neighbor] = tentative_gScore
					fScore[neighbor] = gScore[neighbor] + Heuristic(neighbor, goal)
					openSet:Push(neighbor, fScore[neighbor])
				end
			end
		end
	end

	return {}
end


while task.wait() do
	local path = AStar(roundToStep(script.Parent.HumanoidRootPart.Position), roundToStep(workspace.Target.Position))
	if path and path[3] then
		script.Parent.Humanoid:MoveTo(path[3])
		script.Parent.Humanoid.MoveToFinished:Wait()

	end
end

this is the place file
TestAStar.rbxl (71.2 KB)

Please do not ask people to write entire scripts or design entire systems for you. If you can’t answer the three questions above, you should probably pick a different category.

3 Likes

Apparently, your BubbleDown function is calling itself infinite times, since it’s recursive. Try checking if the function is doing everything as it should, check the operations, and the comparisons. You can also try adding a task.wait() and see if that does something.

function PriorityQueue:BubbleDown(index)
    task.wait()
	local leftChildIndex = index * 2
	local rightChildIndex = index * 2 + 1
	local smallest = index

	if leftChildIndex <= #self.heap and self.heap[leftChildIndex].priority < self.heap[smallest].priority then
		smallest = leftChildIndex
	end

	if rightChildIndex <= #self.heap and self.heap[rightChildIndex].priority < self.heap[smallest].priority then
		smallest = rightChildIndex
	end

	if smallest ~= index then
		self.heap[index], self.heap[smallest] = self.heap[smallest], self.heap[index]
		self:BubbleDown(smallest)
	end
end
1 Like

The pathfinding is stuck now. Also i set a rule so if smallest index. also i get another error on “for _, offset in pairs(offsets) do”
which i dont understand why i get this error

1 Like

Yeah, your issue is definitely some infinite loop due to the recursive functions. I’m not familiar with this pathfinding algorithm, but can you show me the line it’s talking about now (Line 115)?

1 Like

115 is the final call of the function its " local path = AStar(roundToStep(script.Parent.HumanoidRootPart.Position), roundToStep(workspace.Target.Position))
"

1 Like

I’ll debug it for you, give me a minute

1 Like

I have changed and updated the code.
Here it is:

-- Priority Queue (Min-Heap) Implementation
local PriorityQueue = {}
PriorityQueue.__index = PriorityQueue

function PriorityQueue.new()
	return setmetatable({ heap = {} }, PriorityQueue)
end

function PriorityQueue:Push(node, priority)
	table.insert(self.heap, {node = node, priority = priority})
	self:BubbleUp(#self.heap)
end

function PriorityQueue:Pop()
	local root = self.heap[1]
	local last = table.remove(self.heap)
	if #self.heap > 0 then
		self.heap[1] = last
		self:BubbleDown(1)
	end
	return root.node
end

function PriorityQueue:BubbleUp(index)
	local parentIndex = math.floor(index / 2)
	while parentIndex > 0 and self.heap[index].priority < self.heap[parentIndex].priority do
		self.heap[index], self.heap[parentIndex] = self.heap[parentIndex], self.heap[index]
		index = parentIndex
		parentIndex = math.floor(index / 2)
	end
end

function PriorityQueue:BubbleDown(index)
	local smallest = index
	while true do
		local leftChildIndex = index * 2
		local rightChildIndex = index * 2 + 1

		if leftChildIndex <= #self.heap and self.heap[leftChildIndex].priority < self.heap[smallest].priority then
			smallest = leftChildIndex
		end

		if rightChildIndex <= #self.heap and self.heap[rightChildIndex].priority < self.heap[smallest].priority then
			smallest = rightChildIndex
		end

		if smallest ~= index then
			self.heap[index], self.heap[smallest] = self.heap[smallest], self.heap[index]
			index = smallest
		else
			break
		end
	end
end

function PriorityQueue:IsEmpty()
	return #self.heap == 0
end

-- A* Pathfinding in ROBLOX with Vector3 Nodes of Size 3 and Priority Queue
local function Heuristic(a, b)
	return (math.abs(a.X - b.X) + math.abs(a.Y - b.Y) + math.abs(a.Z - b.Z))
end


local function roundToStep(pos : Vector3)
	return Vector3.new(math.floor(pos.X/3)*3+1.5,math.floor(pos.Y/3)*3+1.5, math.floor(pos.Z/3)*3+1.5)
end
local offsets = {
	Vector3.new(3, 0, 0), Vector3.new(-3, 0, 0),
	Vector3.new(0, 3, 0), Vector3.new(0, -3, 0),
	Vector3.new(0, 0, 3), Vector3.new(0, 0, -3),
	Vector3.new(3, 0, 3), Vector3.new(3, 0, -3),
	Vector3.new(-3, 0, 3), Vector3.new(-3, 0, -3)
}
local function AStar(start, goal)
	local openSet = PriorityQueue.new()
	openSet:Push(start, 0)
	local cameFrom = {}
	local gScore = {[start] = 0}
	local fScore = {[start] = Heuristic(start, goal)}

	while not openSet:IsEmpty() do
		local current = openSet:Pop()

		if current == goal then
			local totalPath = {current}
			while cameFrom[current] do
				current = cameFrom[current]
				table.insert(totalPath, 1, current)
			end
			return totalPath
		end

		
		for _, offset in pairs(offsets) do
			local neighbor = roundToStep(current + offset) 
			local inPart = #workspace:getPartBoundsInRadius(neighbor, 0.2) > 0
			if inPart == false then
				local tentative_gScore = gScore[current] + (neighbor - current).Magnitude
				if not gScore[neighbor] or tentative_gScore < gScore[neighbor] then
					cameFrom[neighbor] = current
					gScore[neighbor] = tentative_gScore
					fScore[neighbor] = gScore[neighbor] + Heuristic(neighbor, goal)
					openSet:Push(neighbor, fScore[neighbor])
				end
			end
		end
	end

	return {}
end


while task.wait() do
	local path = AStar(roundToStep(script.Parent.HumanoidRootPart.Position), roundToStep(workspace.Target.Position))
	if path and path[3] then
		script.Parent.Humanoid:MoveTo(path[3])
		script.Parent.Humanoid.MoveToFinished:Wait()

	end
end

I replaced the recursive with while true or while contidioned. It still doesnt work

2 Likes

That’s odd, both of your scripts work fine for me. Could you perhaps restart studio? Maybe even your computer? I know it sounds dumb, but sometimes Roblox Studio decides to stop working properly

1 Like

Ok so i added a break to stop the functions of the bubbleup and bubbledown incase they go infinite. Restarting studio didnt fix it. Restarting pc wont fix it.

-- Priority Queue (Min-Heap) Implementation
local PriorityQueue = {}
PriorityQueue.__index = PriorityQueue

function PriorityQueue.new()
	return setmetatable({ heap = {} }, PriorityQueue)
end

function PriorityQueue:Push(node, priority)
	table.insert(self.heap, {node = node, priority = priority})
	self:BubbleUp(#self.heap)
end

function PriorityQueue:Pop()
	local root = self.heap[1]
	local last = table.remove(self.heap)
	if #self.heap > 0 then
		self.heap[1] = last
		self:BubbleDown(1)
	end
	return root.node
end

function PriorityQueue:BubbleUp(index)
	while index > 1 do
		local parentIndex = math.floor(index / 2)
		if self.heap[index].priority < self.heap[parentIndex].priority then
			self.heap[index], self.heap[parentIndex] = self.heap[parentIndex], self.heap[index]
			index = parentIndex
		else
			break
		end
	end
end

function PriorityQueue:BubbleDown(index)
	local length = #self.heap
	while true do
		local leftChildIndex = 2 * index
		local rightChildIndex = 2 * index + 1
		local smallest = index

		if leftChildIndex <= length and self.heap[leftChildIndex].priority < self.heap[smallest].priority then
			smallest = leftChildIndex
		end

		if rightChildIndex <= length and self.heap[rightChildIndex].priority < self.heap[smallest].priority then
			smallest = rightChildIndex
		end

		if smallest ~= index then
			self.heap[index], self.heap[smallest] = self.heap[smallest], self.heap[index]
			index = smallest
		else
			break
		end
	end
end

function PriorityQueue:IsEmpty()
	return #self.heap == 0
end

-- A* Pathfinding in ROBLOX with Vector3 Nodes of Size 3 and Priority Queue
local function Heuristic(a, b)
	return (math.abs(a.X - b.X) + math.abs(a.Y - b.Y) + math.abs(a.Z - b.Z))
end


local function roundToStep(pos : Vector3)
	return Vector3.new(math.floor(pos.X/3)*3+1.5,math.floor(pos.Y/3)*3+1.5, math.floor(pos.Z/3)*3+1.5)
end
local offsets = {
	Vector3.new(3, 0, 0), Vector3.new(-3, 0, 0),
	Vector3.new(0, 3, 0), Vector3.new(0, -3, 0),
	Vector3.new(0, 0, 3), Vector3.new(0, 0, -3),
	Vector3.new(3, 0, 3), Vector3.new(3, 0, -3),
	Vector3.new(-3, 0, 3), Vector3.new(-3, 0, -3)
}
local function AStar(start, goal)
	local openSet = PriorityQueue.new()
	openSet:Push(start, 0)
	local cameFrom = {}
	local gScore = {[start] = 0}
	local fScore = {[start] = Heuristic(start, goal)}

	while not openSet:IsEmpty() do
		local current = openSet:Pop()

		if current == goal then
			local totalPath = {current}
			while cameFrom[current] do
				current = cameFrom[current]
				table.insert(totalPath, 1, current)
			end
			return totalPath
		end

		
		for _, offset in pairs(offsets) do
			local neighbor = roundToStep(current + offset) 
			local inPart = #workspace:getPartBoundsInRadius(neighbor, 0.2) > 0
			if inPart == false then
				local tentative_gScore = gScore[current] + (neighbor - current).Magnitude
				if not gScore[neighbor] or tentative_gScore < gScore[neighbor] then
					cameFrom[neighbor] = current
					gScore[neighbor] = tentative_gScore
					fScore[neighbor] = gScore[neighbor] + Heuristic(neighbor, goal)
					openSet:Push(neighbor, fScore[neighbor])
				end
			end
		end
	end

	return {}
end


while task.wait() do
	local path = AStar(roundToStep(script.Parent.HumanoidRootPart.Position), roundToStep(workspace.Target.Position))
	if path and path[3] then
		script.Parent.Humanoid:MoveTo(path[3])
		script.Parent.Humanoid.MoveToFinished:Wait()

	end
end

The issue exists in the “for _, offset in pairs(offsets) do” line on the offsets. The offsets are outside so the number of the offsets is same.

2 Likes

Try this code:

local PriorityQueue = {}
PriorityQueue.__index = PriorityQueue

function PriorityQueue.new()
	return setmetatable({ heap = {} }, PriorityQueue)
end

function PriorityQueue:Push(node, priority)
	table.insert(self.heap, {node = node, priority = priority})
	self:BubbleUp(#self.heap)
end

function PriorityQueue:Pop()
	local root = self.heap[1]
	local last = table.remove(self.heap)
	if #self.heap > 0 then
		self.heap[1] = last
		self:BubbleDown(1)
	end
	return root.node
end

function PriorityQueue:BubbleUp(index)
	while index > 1 do
		local parentIndex = math.floor(index / 2)
		if self.heap[index].priority < self.heap[parentIndex].priority then
			self.heap[index], self.heap[parentIndex] = self.heap[parentIndex], self.heap[index]
			index = parentIndex
		else
			break
		end
	end
end

function PriorityQueue:BubbleDown(index)
	local length = #self.heap
	while true do
		local leftChildIndex = 2 * index
		local rightChildIndex = 2 * index + 1
		local smallest = index

		if leftChildIndex <= length and self.heap[leftChildIndex].priority < self.heap[smallest].priority then
			smallest = leftChildIndex
		end

		if rightChildIndex <= length and self.heap[rightChildIndex].priority < self.heap[smallest].priority then
			smallest = rightChildIndex
		end

		if smallest ~= index then
			self.heap[index], self.heap[smallest] = self.heap[smallest], self.heap[index]
			index = smallest
		else
			break
		end
	end
end

function PriorityQueue:IsEmpty()
	return #self.heap == 0
end

local function Heuristic(a, b)
	return (math.abs(a.X - b.X) + math.abs(a.Y - b.Y) + math.abs(a.Z - b.Z))
end

local function roundToStep(pos : Vector3)
	return Vector3.new(math.floor(pos.X/3)*3+1.5,math.floor(pos.Y/3)*3+1.5, math.floor(pos.Z/3)*3+1.5)
end

local offsets = {
	Vector3.new(3, 0, 0), Vector3.new(-3, 0, 0),
	Vector3.new(0, 3, 0), Vector3.new(0, -3, 0),
	Vector3.new(0, 0, 3), Vector3.new(0, 0, -3),
	Vector3.new(3, 0, 3), Vector3.new(3, 0, -3),
	Vector3.new(-3, 0, 3), Vector3.new(-3, 0, -3)
}

local cache = {}
local function isPartInRadius(position)
	local key = tostring(position)
	if cache[key] ~= nil then
		return cache[key]
	end
	local result = #workspace:getPartBoundsInRadius(position, 0.2) > 0
	cache[key] = result
	return result
end

local function AStar(start, goal)
	local openSet = PriorityQueue.new()
	openSet:Push(start, 0)
	local cameFrom = {}
	local gScore = {[start] = 0}
	local fScore = {[start] = Heuristic(start, goal)}

	while not openSet:IsEmpty() do
		local current = openSet:Pop()

		if current == goal then
			local totalPath = {current}
			while cameFrom[current] do
				current = cameFrom[current]
				table.insert(totalPath, 1, current)
			end
			return totalPath
		end

		for _, offset in pairs(offsets) do
			local neighbor = roundToStep(current + offset)
			if not isPartInRadius(neighbor) then
				local tentative_gScore = gScore[current] + (neighbor - current).Magnitude
				if not gScore[neighbor] or tentative_gScore < gScore[neighbor] then
					cameFrom[neighbor] = current
					gScore[neighbor] = tentative_gScore
					fScore[neighbor] = gScore[neighbor] + Heuristic(neighbor, goal)
					openSet:Push(neighbor, fScore[neighbor])
				end
			end
		end
	end

	return {}
end

while task.wait() do 
	local path = AStar(roundToStep(script.Parent.HumanoidRootPart.Position), roundToStep(workspace.Target.Position))
	if path and path[3] then
		script.Parent.Humanoid:MoveTo(path[3])
		script.Parent.Humanoid.MoveToFinished:Wait()
	end
end
1 Like


I get error on return cache[key] line

1 Like

I honestly have no idea what’s going on then, at first glance it seemed like an infinite loop problem, but I tried with the scripts you sent me – not changing a single line – and it worked with no issue. I could suggest you to copy-paste the script into a brand-new game – put the parts from scratch, add a new NPC – and see if that works, because I don’t know why it won’t work for you.

Here’s how it works for me with the code I sent you:

Try to move the part while the npc is moving, that is where the bug happens

This is still open for help please.

well i do have a script, but its more advanced but also a lil buggy but still needs fixing want it still?

I got it from someone. How 2 Make Custom A* Pathfinding

Yes. Same i read tutorial from this guy

Server/Legacy Code:

local SIZE = 3

local DIRECTIONS = {
	Vector3.xAxis;
	Vector3.zAxis;

	Vector3.xAxis * -1;
	Vector3.zAxis * -1;

	Vector3.zAxis + Vector3.xAxis;

	Vector3.xAxis * -1 + Vector3.zAxis;

	Vector3.xAxis + Vector3.zAxis * -1;
}

for i, dir in DIRECTIONS do
	DIRECTIONS[i] = dir * SIZE -- scale the directions with the grid size
end

local function RoundToNearest(v)
	return Vector3.new(
		math.round(v.X / SIZE) * SIZE,
		math.round(v.Y / SIZE) * SIZE,
		math.round(v.Z / SIZE) * SIZE
	)
end

local Params = OverlapParams.new()
local filterdDescendantsInstances = {}

local function CheckForHumanoids()
	for _, Object in ipairs(workspace:GetChildren()) do
		if Object:IsA("Model") then
			if Object:FindFirstChildWhichIsA("Humanoid") then
				print(Object)
				Params.FilterType = Enum.RaycastFilterType.Exclude
				Params:AddToFilter(Object)
			end
		end
	end
end

CheckForHumanoids()

local function NodeIsTraversable(point: Vector3)
	local collisions = workspace:GetPartBoundsInBox(CFrame.new(point), Vector3.new(SIZE, 0, SIZE), Params)

	if collisions[1] then
		return false
	end
	return true
end

local D = math.sqrt(2) -- A constant

local function ConstructNode(worldPos: Vector3, target: Vector3)
	local function HeuristicFunction(a: Vector3, b: Vector3): number
		local D2 = 1

		local dx = math.abs(a.X - b.X)
		local dy = math.abs(a.Y - b.Y)
		local dz = math.abs(a.Z - b.Z)

		return D * math.min(dx, dy, dz) + (D2 - D) * math.max(0, dx + dy + dz - 2 * math.min(dx, dy, dz))		
	end

	local Object = setmetatable({
		Traversable = false;
		Position = worldPos;

		Costs = setmetatable({
			G = 0;
			H = 0;
		}, {
			__index = function(t, k)
				if k == 'F' then
					return rawget(t, 'H') + rawget(t, 'G') -- F is the sum of H and G
				end

				return rawget(t, k)
			end,	
		});

		HeapIndex = 0; -- For our new heap
		Parent = nil; -- No more storing Parents table
	}, {
		__sub = function(a, b)
			local compare = a.Costs.F - b.Costs.F -- Substract a's F and b's F

			if compare == 0 then -- If the F costs are equal, compare the H cost instead
				compare = a.Costs.H - b.Costs.H
			end

			return -compare -- Return the negated comparison
		end,

		__eq = function(a, b)
			return a.Position == b.Position
		end,
	})

	Object.Costs.G = (worldPos - target).Magnitude
	Object.Costs.H = HeuristicFunction and HeuristicFunction(worldPos, target) or 0 -- Heuristic is 0 if no function is set

	Object.Traversable = NodeIsTraversable(worldPos)

	return Object
end

local function FindPath(start: Vector3, target: Vector3)			
	start = RoundToNearest(start)
	target = RoundToNearest(target)

	local StartNode = ConstructNode(start, target)
	local TargetNode = ConstructNode(target, target)

	-- Safety precaution checks so we don't waste time computing the path
	assert(StartNode.Traversable, 'Starting Node is intraversable, thus path is intraversable')
	assert(TargetNode.Traversable, 'Target Node is intraversable, thus path is intraversable')

	local Grid = {[start] = StartNode, [target] = TargetNode}

	local Heap = require(script:WaitForChild("Heap"))
	local OpenSet = Heap.new()-- :: Heap.Heap<typeof(ConstructNode(Vector3, Vector3))>
	local ClosedSet = {}

	OpenSet:Add(StartNode)

	while OpenSet:Count() > 0 do
		local CurrentNode = OpenSet:RemoveFirst()
		ClosedSet[CurrentNode.Position] = true

		local function RetracePath()
			local Path = {}
			local CurrentPathNode = TargetNode

			while CurrentPathNode ~= StartNode do
				table.insert(Path, 1, CurrentPathNode)
				CurrentPathNode = CurrentPathNode.Parent
			end

			return Path
		end

		if CurrentNode == TargetNode then
			return RetracePath()
		end

		for _, direction in DIRECTIONS do
			local NeighborPos = CurrentNode.Position + direction

			-- If neighbor already evaluated/not traversable, skip
			if ClosedSet[NeighborPos] or not NodeIsTraversable(NeighborPos) then
				continue
			end

			-- Get neighbor node
			local NeighborNode = Grid[NeighborPos] or ConstructNode(NeighborPos, target)
			Grid[NeighborPos] = NeighborNode

			-- Get new G cost to the neighbor
			local CostToNeighbor = CurrentNode.Costs.G + (CurrentNode.Position - NeighborPos).Magnitude

			-- If cost turns out to be better or not in openset
			if CostToNeighbor < NeighborNode.Costs.G or not OpenSet:Contains(NeighborNode) then
				NeighborNode.Costs.G = CostToNeighbor
				NeighborNode.Costs.H = (NeighborPos - target).Magnitude

				NeighborNode.Parent = CurrentNode

				if not OpenSet:Contains(NeighborNode) then -- If it doesn't have the neighbor node yet, add the node
					OpenSet:Add(NeighborNode)
				end
			end
		end
	end
end

local function roundToStep(pos : Vector3)
	return Vector3.new(math.floor(pos.X/3)*3+1.5,math.floor(pos.Y/3)*3+1.5, math.floor(pos.Z/3)*3+1.5)
end

local debugFinished = false

local allParts = 0

function debugParts(path)
	for i, child in pairs(workspace.DebugParts:GetChildren()) do
		if child.Name == tostring(i) then
			child:Destroy()
		end
	end
	
	debugFinished = false
	for _, node in pairs(path) do
		local a = Instance.new("Part", workspace.DebugParts)
		a.Name = _
		a.Position = node.Position
		a.Size = Vector3.new(1, 1, 1)
		a.Anchored = true
		a.CanCollide = false
		a.CanTouch = false
		a.CanQuery = false
	end
	debugFinished = true
end

local ai = workspace:WaitForChild("r15AI")
local humanoid = ai:WaitForChild("Humanoid")
local rootPart = ai:WaitForChild("HumanoidRootPart")
local startPos = workspace.Start.Position


while wait() do
	local goalPos = workspace.Goal.Position
	
	ai:MoveTo(startPos)
	
	local path = FindPath(rootPart.Position, roundToStep(goalPos))
		
	if script:GetAttribute("debug") then
		if #path ~= allParts then
			debugParts(path)
		else
			
		end
	end
			
	for _, node in pairs(path) do
		humanoid:MoveTo(node.Position)
		humanoid.MoveToFinished:Wait()
	end
end

Heap Module:

local Heap = {}

function Heap.new<T>() -- Just some typechecking stuff we'll be using later on
	-- Our heap private virables
	type Item = {HeapIndex: number} & T

	local Items = {} :: {[number]: Item} -- The items in our heap tree (in this case, our nodes)
	local CurrentItemCount = 0 -- The number of items currently in our tree

	local function Swap(a: Item, b: Item)
		Items[a.HeapIndex], Items[b.HeapIndex] = b, a
		local itemAIndex = a.HeapIndex

		a.HeapIndex = b.HeapIndex
		b.HeapIndex = itemAIndex
	end

	local function SortUp(item: Item)
		local parentIndex = (item.HeapIndex-1)/2

		while true do
			local parentItem = Items[parentIndex]

			-- Check if the parent item exists and if the item is greater than the parent
			if parentItem and item - parentItem > 0 then
				-- If so, swap the item with the parent item
				Swap(item, parentItem)
			else
				-- Otherwise, the item is in the correct position, so break out of the loop
				break
			end

			parentIndex = (item.HeapIndex-1)/2
		end
	end

	local function SortDown(item: Item)
		while true do
			local childLeftIndex = item.HeapIndex * 2 + 1
			local childRightIndex = item.HeapIndex * 2 + 2
			local swapIndex = 0

			-- Check if the left child exists
			if childLeftIndex < CurrentItemCount then
				swapIndex = childLeftIndex

				-- If the right child has a higher priority, set the swap index to the right child index
				if childRightIndex < CurrentItemCount then
					-- If so, compare the priorities of the left and right children
					if Items[childLeftIndex] - Items[childRightIndex] < 0 then
						-- If the right child has a higher priority, set the swap index to the right child index
						swapIndex = childRightIndex
					end
				end

				-- Compare the priority of the item with the priority of the child at the swap index
				if item-Items[swapIndex] < 0 then
					-- If the child has a higher priority, swap the item with the child
					Swap(item, Items[swapIndex])
				else
					-- Otherwise, the item is in the correct position, so break out of the loop
					return
				end
			else
				-- If the left child does not exist, the item is at the bottom of the heap and in the correct position
				return
			end
		end
	end

	local Object = {} -- The class' instantiated object

	function Object:RemoveFirst() : Item
		local firstItem = Items[0] -- Get the root node
		CurrentItemCount -= 1 -- Decrease the items count
		Items[0] = Items[CurrentItemCount] -- Set the root node to lowest item
		Items[0].HeapIndex = 0

		SortDown(Items[0]) -- Sort down since the lowest item is at the root node

		return firstItem
	end

	function Object:Add(item: Item)
		item.HeapIndex = CurrentItemCount
		Items[CurrentItemCount] = item

		SortUp(item) -- Sort up since our heap item is at the bottom
		CurrentItemCount += 1
	end

	function Object:Contains(item: T)
		return Items[item.HeapIndex] == item
	end

	function Object:Count() -- Just a simple getter
		return CurrentItemCount
	end

	return Object
end

return Heap

If you want to use it (again he helped me from somethings that were errors) and if you want to include the while loop that’s fine with me.

the checkforhumanoids function is basically checking everywhere in the workspace and adding them to the filter in the overlap params, which is kind of like the start is a new part and the npc is on top of it basically

it crashes the studio. I guess there is an issue in the code

What part of the code have you checked? is it because the roundToStep function? because the root Part cant be rounded it has to be normal. So with the code I gave you which is odd to be crashing, mine doesn’t I can give you a video that my code works. If you want.