Hello, So I am making a SCIFI PVE style game where the entities will navigate to the player through a complicated path which requires pathfinding, I was going to use roblox pathfinding but decided that using one created by another user wasn’t really a bad idea, And it works perfectly well.. until i implemented it in a heartbeat loop and its glitching very much, the pathfinding module itself has no problems.
Code:
local entities = {
["Entities"] = {}
}
local allEntities = entities["Entities"]
local entitiesFolder = _G.Services.Rep:WaitForChild("Entities")
local entitiesDB = _G.AllModules.Entities
local PathfindingModule = _G.AllModules.Pathfinding
local entityNumber = 1
function entities.new(name, location)
if not entitiesDB[name] then return end
local entity = _G.Functions.deepCopy(entitiesDB[name])
local clone = entitiesFolder:FindFirstChild(name):Clone()
clone.Parent = workspace.Entities
clone.PrimaryPart.CFrame = CFrame.new(location)
clone.PrimaryPart:SetNetworkOwner(nil)
clone.Name = entityNumber
entity["Physical"] = clone
entity["Target"] = nil
entity["Waypoints"] = {}
entity["WaypointIndex"] = 1
entity["LastPathUpdate"] = 0
entity["ID"] = entityNumber
allEntities[entityNumber] = entity
entityNumber += 1
return entity
end
local UPDATE_INTERVAL = 0.5
local WAYPOINT_REACH_DISTANCE = 3
local function findNearestTarget(entityPosition)
local closest, shortestDistance = nil, math.huge
for _, player in ipairs(game.Players:GetPlayers()) do
local char = player.Character
if char and char:FindFirstChild("HumanoidRootPart") and char:FindFirstChild("Humanoid") and char.Humanoid.Health > 0 then
local distance = (char.PrimaryPart.Position - entityPosition).Magnitude
if distance < shortestDistance then
shortestDistance = distance
closest = char
end
end
end
return closest
end
local function getPath(startPos, endPos)
local waypoints = PathfindingModule.mp(startPos, endPos)
if #waypoints > 0 then
return waypoints
else
return nil
end
end
_G.Services.RS.Heartbeat:Connect(function(dt)
for _, entity in pairs(allEntities) do
if not entity then continue end
local model = entity.Physical
if not model or not model.PrimaryPart then continue end
entity._lastUpdate = entity._lastUpdate or 0
entity._pathIndex = entity._pathIndex or 1
entity._lastUpdate += dt
if entity._lastUpdate >= UPDATE_INTERVAL then
entity._lastUpdate = 0
local target = findNearestTarget(model.PrimaryPart.Position)
if target then
local waypoints = getPath(model.PrimaryPart.Position, target.HumanoidRootPart.Position)
if waypoints then
entity._path = waypoints
entity._pathIndex = 1
else
entity._path = nil
end
else
entity._path = nil
end
end
local path = entity._path
if path and path[entity._pathIndex] then
local currentWP = path[entity._pathIndex]
local entityPos = model.PrimaryPart.Position
if (currentWP - entityPos).Magnitude <= WAYPOINT_REACH_DISTANCE then
entity._pathIndex += 1
else
local direction = (currentWP - entityPos).Unit
model.PrimaryPart.CFrame = model.PrimaryPart.CFrame + direction * entity.Speed * dt
end
end
end
end)
for i = 1, 50 do
entities.new("Spiderbot", Vector3.new(math.random(10, 60), 10, math.random(1, 50)))
end
return entities
pathfinding module:
local module = {}
local distance = 2
local max = 100000
local diagonal_directions = {
Vector3.new(1,0,1), Vector3.new(1,0,-1), Vector3.new(1,0,0), Vector3.new(0,0,1), Vector3.new(0,0,-1),
Vector3.new(-1,0,1), Vector3.new(-1,0,-1), Vector3.new(-1,0,0),
Vector3.new(1,1,1), Vector3.new(1,1,-1), Vector3.new(1,1,0), Vector3.new(0,1,1), Vector3.new(0,1,-1),
Vector3.new(-1,1,1), Vector3.new(-1,1,-1), Vector3.new(-1,0,0)
}
local function VecToKey(v)
return math.floor(v.X) .. "_" .. math.floor(v.Y) .. "_" .. math.floor(v.Z)
end
function module.mp(start: Vector3, finish: Vector3)
local openarr, closedarr = {}, {}
local visited_count, nodes_count = 1, 1
local endpoint, stop, Emergency_stop = nil, false, false
local startKey = VecToKey(start)
closedarr[startKey] = {pos = start, h = 0, f = 0, g = 0, origin = nil}
local function pdist(a, b)
return math.sqrt((b.X - a.X)^2 + (b.Z - a.Z)^2)
end
local function check(pos, origin)
if pdist(pos, finish) <= distance then
endpoint = pos
stop = true
end
local posKey = VecToKey(pos)
if not closedarr[posKey] and not openarr[posKey] then
local params = OverlapParams.new()
params.MaxParts = 1
params.FilterType = Enum.RaycastFilterType.Include
params.FilterDescendantsInstances = {workspace.Obstacles, workspace.Entities}
local objects = workspace:GetPartBoundsInBox(CFrame.new(pos), Vector3.new(distance - 0.1, distance - 0.1, distance - 0.1), params)
if #objects == 1 then return false end
nodes_count += 1
return true
end
return false
end
local function cycle(point)
local originKey = VecToKey(point)
for _, dir in ipairs(diagonal_directions) do
local pos = point + dir * distance
if check(pos, point) then
local h = (finish - pos).Magnitude
local g = (point - pos).Magnitude + closedarr[originKey].h
local posKey = VecToKey(pos)
openarr[posKey] = {pos = pos, h = h, f = h + g, g = g, origin = point}
end
end
if visited_count == nodes_count then
Emergency_stop = true
end
end
local min, minKey, d = nil, nil, start
cycle(start)
while not stop and not Emergency_stop and nodes_count < max do
min = math.huge
for key, v in pairs(openarr) do
if v.f < min then
min = v.f
minKey = key
end
end
if min ~= math.huge then
d = openarr[minKey].pos
closedarr[minKey] = openarr[minKey]
openarr[minKey] = nil
visited_count += 1
end
cycle(d)
end
if Emergency_stop then
warn("No path found")
return {}
elseif nodes_count >= max then
warn("Path too far")
return {}
end
local waypoints = {}
table.insert(waypoints, endpoint)
local cpKey = VecToKey(endpoint)
local current = closedarr[cpKey]
while current do
local origin = current.origin
if origin then
table.insert(waypoints, origin)
current = closedarr[VecToKey(origin)]
else
break
end
end
local reversed = {}
for i = #waypoints, 1, -1 do
table.insert(reversed, waypoints[i])
end
return reversed
end
return module