(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.

1 Like

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

1 Like

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.

1 Like

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


I get error on return cache[key] line

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