Hi, recently I’ve been working on a Roblox Pathfinding NPC Module and I’ve been wondering if there are any other ways to improve it.
Now, I’m not a huge fan of pathfinding, infact it’s pretty new to me as I’ve only been working on Pathfinding for about 1-2 years. But I’ve slowly gotten better at working with it but I still think there is much more to be done to really make NPCs smarter.
A couple issues I’ve been having with this script is NPCs tend to be dull if they’re not chasing a player directly and fall off the basepart they are standing on, or that NPCs don’t jump when needed to, you get the point.
What I really want here is pretty simple, NPCs that can actively chase players down (if possible) and for NPCs to be able to use the module at the same time (The script only allows one NPC to pathfind at a time for some reason.)
Module:
local PathfindModule = {}
local PlayersService = game:GetService("Players")
local PathfindingService = game:GetService("PathfindingService")
local RunService = game:GetService("RunService")
local debugMode = false
local raycastParams = RaycastParams.new()
raycastParams.FilterType = Enum.RaycastFilterType.Blacklist
function PathfindModule.startup(npcModel : Model)
if npcModel == nil then warn("No NPC found!", npcModel) return end
local npc = npcModel
if not npc:IsA("Model") then return end
local humanoid = npc:FindFirstChildOfClass("Humanoid")
local rootPart = npc:FindFirstChild("HumanoidRootPart")
local lastPathUpdate = 0
local pathfindingInProgress = false
if not humanoid or not rootPart then
warn("NPC is missing needed instances: Humanoid or HumanoidRootPart.", npcModel)
return
end
rootPart:SetNetworkOwner(nil) -- reduces lag
local function debugPrint(message)
if debugMode then
print("[npc Debug]: " .. message, npcModel)
end
end
local function raycastToPlayer(player)
local playerHRP = player.Character and player.Character:FindFirstChild("HumanoidRootPart")
if not playerHRP then return false end
raycastParams.FilterDescendantsInstances = {npc}
local direction = (playerHRP.Position - rootPart.Position).Unit * 45
local rayResult = workspace:Raycast(rootPart.Position, direction, raycastParams)
return not rayResult -- clear path if not hit.
end
local function moveToPlayer(playerHRP)
local targetPosition = playerHRP.Position
humanoid:MoveTo(targetPosition)
end
local function usePathfindingToPlayer(playerHRP)
if pathfindingInProgress then return end
pathfindingInProgress = true
local path = PathfindingService:CreatePath({
AgentRadius = 2,
AgentHeight = 5,
AgentCanJump = true,
AgentJumpHeight = 5,
AgentCanClimb = true,
AgentCanSwim = true
})
path:ComputeAsync(rootPart.Position, playerHRP.Position)
if path.Status == Enum.PathStatus.Success then
local waypoints = path:GetWaypoints()
for _, waypoint in ipairs(waypoints) do
humanoid:MoveTo(waypoint.Position)
if waypoint.Action == Enum.PathWaypointAction.Jump then
humanoid:ChangeState(Enum.HumanoidStateType.Jumping)
end
humanoid.MoveToFinished:Wait()
if (rootPart.Position - playerHRP.Position).Magnitude <= 5 then
break
end
end
else
debugPrint("Pathfinding failed.")
end
pathfindingInProgress = false
end
local function checkPlayerInPath(playerHRP)
local direction = (playerHRP.Position - rootPart.Position).Unit * 5
raycastParams.FilterDescendantsInstances = {npc}
local rayResult = workspace:Raycast(rootPart.Position, direction, raycastParams)
return rayResult ~= nil
end
local function trackPlayer(player)
local playerHRP = player.Character and player.Character:FindFirstChild("HumanoidRootPart")
if not playerHRP then return end
local isPathfinding = humanoid:FindFirstChild("IsPathfinding")
local canPathfind = not isPathfinding or isPathfinding.Value -- default true if the bool doesn't exist
if canPathfind then
if checkPlayerInPath(playerHRP) then
usePathfindingToPlayer(playerHRP)
else
moveToPlayer(playerHRP)
end
else
debugPrint("pathfinding disabled for this npc.")
end
end
local function getNearestPlr()
local players = PlayersService:GetPlayers()
local nearestPlayer = nil
local minDistance = math.huge
for _, player in ipairs(players) do
local playerHRP = player.Character and player.Character:FindFirstChild("HumanoidRootPart")
if playerHRP then
local distance = (rootPart.Position - playerHRP.Position).Magnitude
if distance < minDistance then
nearestPlayer = player
minDistance = distance
end
end
end
if nearestPlayer then
getNearestPlr(nearestPlayer)
end
end
RunService.Stepped:Connect(function(_, deltaTime)
if tick() - lastPathUpdate >= 0.5 then
lastPathUpdate = tick()
getNearestPlr()
end
end)
end
return PathfindingService