Pathfinding
Pathfinding is a server library designed to offer an ultra light and highly customizable node based pathfinding solution for large maps.
Benchmark
Using a 30028 indoors part map with 1245 nodes as the reference, the following are the benchmarking results of a comparison between Delta Pathfinding and Roblox's PathfindingService:
DELTA
04:52:33.274 SAMPLES: 5000 - Server - Benchmark:33
04:52:33.274 AVG. DURATION: 0.003587420011899667ms - Server - Benchmark:34
04:52:33.274 TOTAL: 17.937100059498334ms - Server - Benchmark:35
04:52:33.275 MAX: 1.3361999990593176ms - Server - Benchmark:36
04:52:33.275 MIN: 0.0030999981390777975ms - Server - Benchmark:37
ROBLOX
05:55:04.599 SAMPLES: 1000 - Server - Benchmark:37
05:55:04.599 AVG. DURATION: 100.20428790000369ms - Server - Benchmark:38
05:55:04.599 TOTAL: 100204.28790000369ms - Server - Benchmark:39
05:55:04.599 MAX: 312.11240000266116ms - Server - Benchmark:40
05:55:04.599 MIN: 58.51209999673301ms - Server - Benchmark:41
One key difference to note in this benchmark is that Delta's Pathfinding makes use of preset nodes, manually placed down by the developer(s), and that Delta's Pathfinding returns a position, rather than a list of waypoints as opposed to Roblox's PathfindingService.
This means much better versatility and performance, specially on large maps.
Variables
Cache: table used to cache node positionsNodeIgnoreCache: table used to cache node ignore statesNodeTargetCache: Table used to cache pathfinder node targetsSetupNodes: bool that represents whether or not node positions have been set up internallyRaycastParamsVar: RaycastParams used internally during pathfinding
Functions
CastRay(Origin | Vector3, Target | Vector3, Ignore | table, IgnoreWater | bool)
Defines the RaycastParamsVar variable and performs a raycast. This is used instead of Utility's CastRay function for performance purposes.
ClosestNode(Id | string, From | Vector3, To | Vector3, OriginalIgnore | table: { }, IgnoredNodes | table: { }, Strict | bool: false, NodeDistanceLimit | number: 6, Nodes | table: Pathfinding.Cache, JumpHeight | number: nil, MaxDistance | number: 5000)
Retrieves the closest node position based on all given parameters. Essentially the backbone of the library.
It is possible to modify this function's parameters and consequently the pathfinder's behavior by implementing Pathfinding.BeforeClosestNode. However, this requires following a strict format in order not to break anything.
Depending on the provided JumpHeight, Pathfinding:ClosestNode() may return a second value indicating if the pathfinder should jump.
local NewTarget = Vector3.new(234, 37.85, 152.469)
Pathfinding.BeforeClosestNode = function(Id, From, To, OriginalIgnore, IgnoredNodes, Strict, NodeDistanceLimit, Nodes, JumpHeight, MaxDistance)
if From.X > 325 and To.X < 325 then
To = NewTarget
end
return Id, From, To, OriginalIgnore, IgnoredNodes, Strict, NodeDistanceLimit, Nodes, JumpHeight, MaxDistance
end
Nodes(Location | string)
Retrieves the cached nodes or sets up the node cache if not previously set up. If you are looking to customize internal node allocation without using the Nodes parameter, this is the right function to modify.
A Location string can be specified to skip the global pathfinding nodes folder and instead use the specified folder within DeltaPFCache. Note that this forced folder change will not cache; if you'd like to optimize, consider storing the return value locally and passing that value to Pathfinding:Target() instead.
SetFolder(Name | string)
Changes the nodes folder being used globally within DeltaPFCache for pathfinding.
Pathfinding:SetFolder("Map1/Level1")
Target(Id | string, From | Vector3, To | Vector3, Ignore | table: { }, IgnoredNodes | table: { }, Strict | bool: false, NodeDistanceLimit | number: 6, Nodes | table/string: Pathfinding:Nodes(), JumpHeight | number: nil, MaxDistance | number: 5000)
Returns the closest available pathfinding node position from the specificied location to its target.
local Position, ObstructingPart, PathFound, ShouldJump = Pathfinding:Target({ -- Dictionary format is optional
Id = "Pathfinder",
From = Vector3.new(0, 2.5, 0),
To = Vector3.new(100, 2.5, 100),
Nodes = "Map1/Level1"
})
Understanding Pathfinding:Target()
To appropriately calculate the best position at any given time for your pathfinder in your use case, you must first provide the Target function an id. This identifier will be used to cache previous locations for your pathfinder and previous pathfind data so that it can best adjust to new situations.
Once you have provided your pathfinder with an id, you must provide it your entity's position and where you would like it to go to. Once these parameters have been set, your pathfinder should be ready!
However, you most likely want better customization than just origin and target. For example, you might want your pathfinder to ignore certain obstacles, such as doors or other entities.
The Ignore parameter is just for that. A table of, for example { workspace.Entities }, in the Ignore parameter will make the pathfinder ignore any obstacle inside workspace.Entities.
You may also choose to ignore nodes or to limit nodes. The first option, using IgnoredNodes, allows you to specifically blacklist nodes from all available nodes. The second option, using Nodes, allows you to whitelist nodes to use during pathfinding. Nodes is an optional parameter, but can be specified as a string or a table of Vector3 values.
Using this, you can create pathfinders that ignore obstacles that most pathfinders can go through, or even make pathfinders that follow paths no other pathfinders do.
You can use the JumpHeight parameter to define a distance at which the pathfinder may attempt to travel between a gap or barrier in order to reach its target.
In case your pathfinder gets stuck in a position, such as tall humanoids or wide beings, you can adjust the NodeDistanceLimit parameter. This will allow the pathfinder to recognise that your From position has reached a node position and move on.
Additionaly, you can make use of the MaxDistance parameter to alter the maximum distance a ray can travel during pathfinding computation.
As a nice bonus, you can decide if your pathfinder should act in a strict pathfind manner or if it should act in a non-strict manner.
During non-strict mode, which is the default, the pathfinder will "search" for its target. This allows you to create more natural entities that do not chase players in an instant manner from across the map.
During strict mode, the pathfinder will attempt to follow the fastest path to its target.
Examples
Ignoring lights
local ToIgnore = { }
for i, v in ipairs(Lights) do
if v[1] and v[1].Enabled then
local Radius = Utility:LightRadius(v[1]) + 2.5
if v[3] ~= v[2].Position then
local Near = { }
for _, x in ipairs(Nodes) do
if (x - v[2].Position).Magnitude <= Radius then
table.insert(Near, x)
end
end
Lights[i] = { v[1], v[2], v[2].Position, Near }
end
for _, x in ipairs(v[4]) do
table.insert(ToIgnore, x)
end
end
end
Pathfinding:Target('LightIgnore', Entity.HumanoidRootPart.Position, Target, nil, ToIgnore)
Restricting nodes based on parts
local Nodes = { }
for i, v in ipairs(workspace.Nodes:GetChildren()) do
table.insert(Nodes, v.Position)
v:Destroy()
end
Pathfinding:Target('NodeRestriction', Entity.HumanoidRootPart.Position, Target, nil, nil, nil, Nodes)