Faster, smarter and more efficient AI pathfinding needed

I recently got into Roblox development after several years of being on the platform and I have been doing a lot of reading up on scripting certain things that I want to script. After all that reading, I’m working on making an AI pathfinding system for an AI that can most effectively chase after someone and catch them, basically the “smart zombie” idea that a lot of people go for. Despite plenty of implementations, such as a combination of TheDevKing’s tutorials that recalculated the path after each waypoint and Y3llow Mustang’s “advanced zombie” scripts wholesale, I haven’t found the success I’m looking for with this system. Here’s a quick run down of what I want:

  • I want something that allows the AI to chase after someone as smoothly/naturally as if the script were just looping “:MoveTo()” the person, but with the ability to jump and navigate obstacles at roughly the same speed the average player would.
  • I want something that moves the AI to where the player is instead of where they were. I’ve already tried running the pathfinding service through a loop (create path then compute path then move through all waypoints before creating next path etc.) and it is effortless to evade the AI’s movements because it has to complete the path it previously computed before it moves to the next one. It will never actually hit you.
  • I want something that’s efficient as possible and puts minimal strain on the game engine because this is going to be replicated a lot, with possibly dozens of active instances running at the same time.

With all that said, the code below is my latest implementation. I’m having multiple problems right now with it (apologies because I don’t know how to film it and pictures wouldn’t do it justice) but the main two are:

  • When the target is moving, the AI stops in its tracks and doesn’t begin moving again until the target stops moving.
  • Very often, if the target moves to a location that the AI needs to jump in order to reach, the AI will continuously jump and, instead of navigating a practical path, will move directly towards the target in a straight line, even if it’s impossible for the AI to reach the target from that trajectory.

I included comments, but the tl;dr of the pathfinding thought process is for the bot to compute the path, then follow all the waypoints, but if the target changes, the bot will stop following the waypoints and will instead recompute the path and follow those waypoints instead. I hope this all makes sense. The code is below:

-- SERVICES
local PFS = game:GetService("PathfindingService")

-- PATH OBJECT
local path = PFS:CreatePath()

-- VARIABLES
local test = script.Parent
local hum = test:WaitForChild("Humanoid")
local HRP = test:WaitForChild("HumanoidRootPart")

-- FUNCTIONS
local function findTarget()
	local aggroDistance = 100
	local target
	for i, v in pairs(game.Workspace:GetChildren()) do
		local humanoid = v:FindFirstChild("Humanoid")
		local humanoidrootpart = v:FindFirstChild("HumanoidRootPart")
		if humanoid and humanoidrootpart and v ~= script.Parent then
			-- check distance
			if (HRP.Position - humanoidrootpart.Position).Magnitude < aggroDistance then
				aggroDistance = (HRP.Position - humanoidrootpart.Position).Magnitude
				target = humanoidrootpart
			end
		end
	end
	return target
end

-- (necessary for smoothness)
test.PrimaryPart:SetNetworkOwner(nil)

-- MAIN LOOP
while true do
	local target = findTarget()
	-- if a target exists, then run this loop until their health is zero
	if target then
		repeat
			-- get the target's current position down in case it changes, then compute the path
			local currentPosition = target.Position
			local success, errorMsg = pcall(function()
				path:ComputeAsync(HRP.Position, currentPosition)
			end)
			-- if the path was computed and it's possible, then get the waypoints and move through each one
			if success and path.Status == Enum.PathStatus.Success then
				local waypoints = path:GetWaypoints()
				for _, waypoint in pairs(waypoints) do
					-- if the waypoint requires the bot to jump and the bot isn't already jumping, then jump
					if waypoint.Action == Enum.PathWaypointAction.Jump and hum.Jump == false then
						hum.Jump = true
						wait()
						hum.Jump = false
					end
					hum:MoveTo(waypoint.Position)
					-- if the target's current position does not match that of the position we logged, then stop following the path, break out of the loop and start over
					if target.Position ~= currentPosition then
						break
					end
				end
			-- otherwise if there's no possible path, then tell me there's no possible path	
			elseif path.Status == Enum.PathStatus.NoPath then
				print("No possible path")
			-- otherwise if the path couldn't be computed, then tell me it couldn't be computed
			else
				warn("Failed to compute path: ", errorMsg)
			end
		until target.Parent.Humanoid.Health == 0
	else
		hum:MoveTo(HRP.Position + Vector3.new(math.random(-50, 50), 0, math.random(-50, 50)))
		hum.MoveToFinished:Wait(2)
	end
end

Please let me know if you have any questions. I can possibly get footage for next time if someone is willing to recommend methods for recording in Studio (I tried the in-game video recorder and got mixed results).

12 Likes

I’m still looking for help with this. Does anyone know what might be wrong with my method?

2 Likes

The only way I would think of getting better pathfinding is to make my own, because roblox does not give use the source code to things like this. (I think…)

I hope you get better pathfinding, I can’t help you much, sorry.

A while back, I had attempted to make a pathfinding NPC which chases the player. Overall, it worked well for me. Here are some methods that I had implemented:

  • If the NPC is within a short range of the player and there is a direct path (detected using raycasting or magnitude), the NPC does not compute a path, and instead moves directly to the player’s position (Moving it one stud further than the player using LookVector may also help)
  • If there is no path found, simply walk directly towards the nearest player and jump
  • Skip waypoints if there is a direct line to it (raycasting or magnitude)
  • If no path is found for a while, walk to a random nearby point and jump (mainly used in case the AI gets stuck)

I made this script about a year ago, so it may not be the absolute best way to do this, but I find all of these reasonable currently.

5 Likes

Try using the SimplePath module, it simplifies a lot of work so you don’t have to do it yourself, I’m using it currently for my AI, actually works quite well. You can also add in your own logic to fine-tune it according to your needs.

2 Likes

You don’t write a Path script; you get one which works and mod it:

Roblox: You must handle the errors it throws at you, and switch to another method, after checking; “We are not getting anywhere”… This checks two of the Errors u recieve. Uses a Moduel script, that you call from the NPC
Hike Ball - Roblox

Raycast: Here are two “Hug the walls” raycasters:
City Obstacles - Roblox

Bread-crumb method looks the smoothest and shortest to write: Divide the World into a 3D chessboard of about 6X6X6 Squaes. Each player drops/overwrites a breadcrumb as he passes through a square of his next Breadcrumb position.
If NPC ever finds a breadcrumb revert to the following crumbs method (Jump if next crumb is higher…) NPC ends-up looking just like a Player.

GL

1 Like

I am not pretty sure is the right time to ask. But my npc is moving so slow to the new waypoint when the npc lost detection of player but ordered to move to the last position that player been seen.

1 Like

I’m guessing it’s “limping”

workspace.NPC.PrimaryPart:SetNetworkOwnership(nil)
local pathparams = {
["WaypointSpacing"] = math.huge
--there are more things you can do here like AgentCanJump, climb, AgentRadius and AgentHeight
}
path:Create(pathparams)

Put pathparams in the line where you create the path. I’m gonna give this thread my pathfinding code which is good. They can change it up afterwards. I can’t copy it right now.

Would be nice if you could post the pathfinding code you were referring to

LMFAO i literally bookmarked my post so i can post it but forgot. Screw it imma get the code rq and post it here.

here’s the code in question: (this is a stripped down ver so it may be a bit buggy, also messy as heck but ehh)

local pfs = game:GetService("PathfindingService")
local pathparams = {
	["AgentHeight"] = 6,
	["AgentRadius"] = 5,
	["AgentCanJump"] = true,
	["WaypointSpacing"] = math.huge
}
local waypoints = {}
local waypointindex = 1
script.Parent.PrimaryPart:SetNetworkOwner(nil)
local torso = script.Parent:WaitForChild("Torso")
local humanoid = script.Parent:WaitForChild("Humanoid")
local root = script.Parent:WaitForChild("HumanoidRootPart")
local anim = script.Parent.JumpscareAnimation
local animation = humanoid:LoadAnimation(anim)
local plrhumanoid
game.Players.PlayerAdded:Connect(function(plr)
	local character = plr.Character or plr.CharacterAdded:Wait()
	plrhumanoid = plr.Character:WaitForChild("HumanoidRootPart")
end)
local path
local function Walk()
	if plrhumanoid == nil then
		return
	end
	script.Parent.PrimaryPart:SetNetworkOwner(nil)
path = pfs:CreatePath(pathparams)
	path:ComputeAsync(torso.Position, plrhumanoid.Position)
	if path.Status == Enum.PathStatus.Success then
		local waypoints = path:GetWaypoints()
		waypointindex = 2
		humanoid:MoveTo(waypoints[waypointindex].Position)
	end
	path.Blocked:Connect(function(blockedwaypointindex)
		if blockedwaypointindex > waypointindex then
			Walk()
		end
	end)
end
while task.wait() do
	Walk()
end

my original code with jumpscares and crap:

local pfs = game:GetService("PathfindingService")
local pathparams = {
	["AgentHeight"] = 6.5,
	["AgentRadius"] = 5,
	["AgentCanJump"] = false,
	["WaypointSpacing"] = math.huge
}
local waypoints = {}
local waypointindex = 1 
local torso = script.Parent:WaitForChild("Torso")
local humanoid = script.Parent:WaitForChild("Humanoid")
local root = script.Parent:WaitForChild("HumanoidRootPart")
local anim = script.Parent.JumpscareAnimation
local animation = humanoid:LoadAnimation(anim)
local plrhumanoid
game.Players.PlayerAdded:Connect(function(plr)
	local character = plr.Character or plr.CharacterAdded:Wait()
	plrhumanoid = plr.Character:WaitForChild("HumanoidRootPart")
end)
	root.Touched:Connect(function(hit)
	local plr = game.Players:GetPlayerFromCharacter(hit.Parent)
	if plr then
		script.Parent:WaitForChild("Jumpscare"):Play()
		animation:Play()
		root.CanTouch = false
		humanoid.WalkSpeed = 0
		plr.Character:WaitForChild("Humanoid").WalkSpeed = 0
		plr.PlayerGui:WaitForChild("RunningScript"):Destroy()
		plr.Character:MoveTo(script.Parent.Reposition.Position)
		plr.PlayerGui:WaitForChild("GamePaused"):Destroy()
		animation.Stopped:Connect(function()
			local camera = plr.Character:WaitForChild("Camera")
			plr.CameraMode = Enum.CameraType.Scriptable
			plr.CameraMode.CFrame = camera 
			plr.Character:WaitForChild("Humanoid"):TakeDamage(100)
			end)
		end
end)
local path
local function Walk()
	if plrhumanoid == nil then
		return
	end
	path = pfs:CreatePath(pathparams)
	path:ComputeAsync(torso.Position, plrhumanoid.Position)
	if path.Status == Enum.PathStatus.Success then
		local waypoints = path:GetWaypoints()
		waypointindex = 2
		humanoid:MoveTo(waypoints[waypointindex].Position)
	end
	path.Blocked:Connect(function(blockedwaypointindex)
		if blockedwaypointindex > waypointindex then
			Walk()
		end
	end)
end
while task.wait() do
	Walk()
end

some things happened twice since i had the same issue. i’ll prob have to clean up the code someday.

Use SimplePath, it’s easier and pretty good at following moving objects. You can change the settings to improve the actual pathfinding.

3 Likes

Maybe we could utilise Neural Networks? Feeding it the environment and relative position to the player if spotted?

It’s an interesting idea and this might work, recently I’ve been experimenting with Fully Connected Neural Networks and using gradient decent or more commonly referred back-propagation to train the NN.

for this case we would have to use PPO (Proximal Policy Optimisation) to train the NN to find the optimal path. The extra bonus could be more realistic choices and movements. As an example, instead of using the pathfinding algorithm which cuts corners extremely fast and efficiently when finding a point to go to, the NN could like a human notice a corner approaching and make a decision of going that way.

Summarised using Neural Networks could be beneficial and offer a more realistic movement for the dummy as it makes more intelligent decisions. Although, nothing is perfect and the leading issue with using Neural Networks is the lack of memory. If the NN finds itself inside a maze the NN’s inputs have to be scaled to allow for memory by either feeding it the previous frames or the whole map in black and white which can drastically effect performance.

I’ll see if I’ll try learn PPO, I think PPO contains back-propagation which I have learnt how to code from the ground up.

But we will see : )

Nice! This is a really cool post

There is a great video that was made for the Inspire 2024 about pathfinding, running large ammount of NPC at once and having faster pathfiding calculation, here is the title (subtitle available)

(French) Efficiently Running Large Numbers of NPC AI by Florianne10 | Inspire 2024