Hello, I’m RubyEpicFox. This is the first time I make a post btw.
So I have a little problem, I was writing a pathfinding module but found out that it didn’t work.
It does sort of work but instead of following the calculated path the AI walks to the point in a straight line which is not what I want.
I am unsure what causes this, I feel like it has something to do with the raycast that checks if the target is close enough and can be moved to in a straight line or something else.
You create a AI with MODULE.new(ai) where ai must be a model containing a humanoid and torso/root part.
height, radius and can jump are additional. (CanJump not functional yet.)
MODULE.remove(ai) removes the AI and cleans up everything.
To make the humanoid move, the path object contains a ObjectValue and a Vector3value.
What they are intended to do is, if any of those values are changed it should activate the pathfinding and make the humanoid calculate and walk a path to it’s goal.
local pfs = game:GetService("PathfindingService")
local mod = {}
mod.ai_list = {}
mod.new = function(ai, height, radius, canjump, refresh)
local human = ai:FindFirstChild("Humanoid")
local root = ai:FindFirstChild("HumanoidRootPart") or ai:FindFirstChild("Torso") or ai:FindFirstChild("LowerTorso")
if human and root then
mod.ai_list[ai] = {}
mod.ai_list[ai].data = {
current = 0;
points = 0;
}
mod.ai_list[ai].waypoints = {}
mod.ai_list[ai].running = false
mod.ai_list[ai].enabled = true
mod.ai_list[ai].connections = {}
mod.ai_list[ai].path = pfs:CreatePath({
AgentHeight = height or 5;
AgentRadius = radius or 3;
AgentCanJump = canjump or false})
local path = mod.ai_list[ai].path
path.Name = "AI"
path.Parent = ai
local target = Instance.new("ObjectValue")
target.Name = "target"
target.Parent = path
local pos = Instance.new("Vector3Value")
pos.Name = "position"
pos.Parent = path
mod.ai_list[ai].connections['targetupdated'] = target.Changed:Connect(function()
if target.Value then
local targ = target.Value
if targ:IsA("BasePart") then
path:ComputeAsync(root.Position, targ.Position)
if path.Status == Enum.PathStatus.Success then
mod.ai_list[ai].data.current = 1
mod.ai_list[ai].waypoints = path:GetWaypoints()
mod.ai_list[ai].data.points = #mod.ai_list[ai].waypoints
mod.ai_list[ai].running = true
human:MoveTo(mod.ai_list[ai].waypoints[mod.ai_list[ai].data.current].Position)
else
target.Value = nil
warn("Failed to compute path.")
end
end
end
end)
mod.ai_list[ai].connections['positionchanged'] = pos.Changed:Connect(function()
local p = pos.Value
if p and (p - root.Position).magnitude > 3 then
path:ComputeAsync(root.Position, pos.Value)
if path.Status == Enum.PathStatus.Success then
mod.ai_list[ai].running = true
mod.ai_list[ai].data.current = 1
mod.ai_list[ai].waypoints = path:GetWaypoints()
mod.ai_list[ai].data.points = #mod.ai_list[ai].waypoints
human:MoveTo(mod.ai_list[ai].waypoints[mod.ai_list[ai].data.current].Position)
else
target.Value = nil
warn("Failed to compute path.")
end
end
end)
mod.ai_list[ai].connections['movetofinished'] = human.MoveToFinished:Connect(function(succ)
if succ and mod.ai_list[ai].data.points > 0 and mod.ai_list[ai].running then
if mod.ai_list[ai].data.current >= mod.ai_list[ai].data.points then
if target.Value ~= nil then
if (target.Value.Position - root.Position).magnitude < 5 then
mod.ai_list[ai].running = false
human:MoveTo(target.Value.Position)
end
else
if (pos.Value - root.Position).magnitude < 5 then
mod.ai_list[ai].running = false
human:MoveTo(pos.Value)
end
end
end
if target.Value ~= nil and mod.ai_list[ai].running then
if (target.Value.Position - root.Position).magnitude < 20 then
--Raycast.
local ray = Ray.new(root.Position, CFrame.new(root.Position, target.Value.Position).LookVector)
local hit = workspace:FindPartOnRay(ray, root.Parent, false, false)
if hit ~= nil and (hit.Parent == target.Parent or hit.Parent.Parent == target.Parent) then
mod.ai_list[ai].data.current = 0
mod.ai_list[ai].data.points = 0
mod.ai_list[ai].waypoints = {}
human:moveTo(target.Value.Position)
elseif mod.ai_list[ai].data.points > 0 then
mod.ai_list[ai].data.current = math.max(mod.ai_list[ai].data.current + 1, mod.ai_list[ai].data.points)
human:MoveTo(mod.ai_list[ai].waypoints[mod.ai_list[ai].data.current].Position)
elseif mod.ai_list[ai].data.points < 1 then
path:ComputeAsync(root, target.Value.Position)
if path.Status == Enum.PathStatus.Success then
mod.ai_list[ai].data.current = 1
mod.ai_list[ai].waypoints = path:GetWaypoints()
mod.ai_list[ai].data.points = #mod.ai_list[ai].waypoints
mod.ai_list[ai].running = true
human:MoveTo(mod.ai_list[ai].waypoints[mod.ai_list[ai].data.current].Position)
else
mod.ai_list[ai].running = false
end
end
--End of raycast.
else
mod.ai_list[ai].data.current = math.max(mod.ai_list[ai].data.current + 1, mod.ai_list[ai].data.points)
human:MoveTo(mod.ai_list[ai].waypoints[mod.ai_list[ai].data.current].Position)
end
elseif target.Value == nil and mod.ai_list[ai].running then
if (pos.Value - root.Position).magnitude > 7 then
mod.ai_list[ai].data.current = math.max(mod.ai_list[ai].data.current + 1, mod.ai_list[ai].data.points)
human:MoveTo(mod.ai_list[ai].waypoints[mod.ai_list[ai].data.current].Position)
else
human:MoveTo(pos.Value)
end
end
else
if target.Value ~= nil or (pos.Value - root.Position).magnitude > 5 then
human:MoveTo(mod.ai_list[ai].waypoints[mod.ai_list[ai].data.current].Position)
else
mod.ai_list[ai].running = false
human:MoveTo(root.Position)
end
end
end)
mod.ai_list[ai].connections['pathblocked'] = path.Blocked:Connect(function(index)
print("Path blocked.")
if mod.ai_list[ai].data.points > 0 and index > mod.ai_list[ai].data.current then
local position = Vector3.new(0,0,0)
if target.Value ~= nil then
position = target.Value.Position
else
position = pos.Value
end
path:ComputeAsync(root, position)
if path.Status == Enum.PathStatus.Success then
print("Successfully recalculated path.")
mod.ai_list[ai].data.current = 1
mod.ai_list[ai].waypoints = path:GetWaypoints()
mod.ai_list[ai].data.points = #mod.ai_list[ai].waypoints
mod.ai_list[ai].running = true
human:MoveTo(mod.ai_list[ai].waypoints[mod.ai_list[ai].data.current].Position)
else
print("Failed to recalculate path.")
mod.ai_list[ai].running = false
mod.ai_list[ai].data.current = 0
mod.ai_list[ai].data.points = 0
mod.ai_list[ai].waypoints = {}
human:moveTo(target.Value.Position)
end
end
end)
script.AIs.Value = script.AIs.Value + 1
end
end
mod.remove = function(ai)
if mod.ai_list[ai] == nil then print("AI does not exist.") return end
for k, v in ipairs(mod.ai_list) do
if mod.ai_list[k] == mod.ai_list[ai] then
for _, connection in ipairs(mod.ai_list[ai].connections) do
connection:Disconnect()
end
mod.ai_list[ai].path:Destroy()
mod.ai_list[ai].path = nil
table.remove(mod.ai_list, k)
print("AI succesfully removed.")
script.AIs.Value = script.AIs.Value - 1
end
end
end
return mod
So this is my whole module, it seems perfectly fine, to me atleast, I am unsure what causes my AI to not do what I wanted it to do.
The module is made for regular Roblox humanoids (not custom).
This is actually the second time I ever write a pathfinding module for NPCs.
I wrote one before that did work however… It was poorly optimized, used a lot of coroutines, etc.
Now I want to make a better optimalized one by using events and all instead of coroutines that check if the target is close enough/in a straight line, etc ever x times per second.
If anyone can help me it would be very appreciated, please tell me what I did wrong and/or what I could do better.
It’s a global pathfinding module btw, it’s not for a specific game or project, it’s made to be reused/shared.
Also note that it’s the first post I ever make, might still have to figure out how things work here.