AI unable to catch up to player

You can check when the zombie gets within the radius and raycast to check if it will walk through anything so you don’t have to make a new path

1 Like

From the video it looks like the dummy is walking for a bit and then ending at a spot behind the character before resuming again. This is most likely caused by me getting the direction wrong. My bad! I can never keep it straight, which vector to subtract from the other. Just try replacing this line:

with this maybe?

local direction = (zombiePos - characterPos)

Let me know if it works!

1 Like

I made the change you suggested and the AI seems to be making changes in its pathfinding a little faster and getting a little closer, but it still can’t catch up to me.

1 Like

Try multiplying the direction by two or something then.

EDIT: Also, I noticed that the npc is stopping before resuming. This shouldn’t be happening. You should be updating the path or whatever enough that the npc always has a goal it’s working towards.

1 Like

I multiplied the direction by two and this is the result. I can’t tell if anything is different, though I don’t have the sharpest eye when it comes to something like this, so maybe you can spot something I can’t.

As for the stopping and starting, I’m not sure what could be causing that. The script should be checking whether there is anything in the way of the bot and its target and then choosing a pathfinding solution accordingly for every cycle, and the loop runs via the Heartbeat function. I don’t understand how I could get the function to run any faster than that, or how it could be not finding pathways unless they were just straight up not possible, which shouldn’t be the case in all the videos I’ve posted.

1 Like

I decided to look through the documentation and examples for SimplePath.

I think it’d be simpler if you simply used the path events rather than a loop. This is based on personal preference that’s based on experience, as the more logic you put into something the more difficult it is to understand. SimplePath is meant to make pathfinding simpler.

--Computes path again if something blocks the path
Path.Blocked:Connect(function()
	Path:Run(Goal)
end)

--If the position of Goal changes at the next waypoint, compute path again
Path.WaypointReached:Connect(function()
	Path:Run(Goal)
end)

--Computes path again if an error occurs
Path.Error:Connect(function(errorType)
	Path:Run(Goal)
end)

Also, I think you might want to try replacing the Vector3 goal that you pass to Run and replace it with the RootPart of the target character instead. So something like this:

local RS = game:GetService("ReplicatedStorage")
local SS = game:GetService("ServerStorage")
local SP = require(SS:WaitForChild("SimplePath"))

local sprinter = script.Parent
local hum = sprinter:WaitForChild("Humanoid")
local HRP = sprinter:WaitForChild("HumanoidRootPart")
sprinter.PrimaryPart:SetNetworkOwner(nil) -- set sprinter network owner to server

local path = SP.new(sprinter)
path.Visualize = true


local names = {}
for _, name in pairs(RS:WaitForChild("Enemies"):GetChildren()) do
	table.insert(names, name.Name)
end

function checkNames(query)
	for _, name in pairs(names) do
		if query == name then
			return true
		end
	end
	return false
end

function findTarget()
	local aggro = 200
	local target
	local blocked = true
	for _, character in pairs(workspace:GetChildren()) do
		local human = character:FindFirstChild("Humanoid")
		local RP = character:FindFirstChild("HumanoidRootPart")
		if human and RP and human.Health > 0 and not checkNames(RP.Parent.Name) then
			if (RP.Position - HRP.Position).Magnitude < aggro then
				aggro = (RP.Position - HRP.Position).Magnitude
				target = RP
				local rayInfo = RaycastParams.new()
				rayInfo.FilterType = Enum.RaycastFilterType.Blacklist
				rayInfo.FilterDescendantsInstances = sprinter:GetChildren()
				local hit = workspace:Raycast(HRP.Position, (RP.Position - HRP.Position).Unit * 200, rayInfo)
				if hit.Instance == RP then
					blocked = false
				end
			end
		end
	end
	return target, blocked
end

local target: BasePart, heartbeat: RBXScriptConnection
heartbeat = game:GetService("RunService").Heartbeat:Connect(function()
	target = findTarget()
	if target then
		heartbeat:Disconnect()
	end
end)

-- Recompute Path:
local function recomputePath()
	path:Run(target)
end

-- Event Connections:
path.Error:Connect(recomputePath)
path.Blocked:Connect(recomputePath)
path.WaypointReached:Connect(recomputePath)
Path.Reached:Connect(recomputePath)

path:Run(target) -- compute path

This may not solve your issue, but it may make it simpler to script the pathfinding. If it doesn’t solve, then please tell me. I have one more idea for how to fix it.

2 Likes

I tested your code just now and the AI doesn’t seem to be moving at all. Did you mean to put the initial “path:Run(target)” in the conditional in the Heartbeat function? I checked and it is getting inside that condition when I get in range, but it’s not doing anything after that.

2 Likes

I’m basing it off one of the examples the authors provided, so no. I edited the code a teeny bit now, but not much. If it doesn’t work then it just means the module isn’t working as intended. That would be a bad thing because it would mean the module is unreliable.

2 Likes

If it would be easier for you to diagnose, we could switch back to the default pathfinding system and recreate the script using it. I just tried out your revised code and I got the same result. The only reason I used SimplePath was that it was less complex and required fewer lines of code to run properly. At this rate, I’ll take any solution that works.

I had tried to use the object-oriented pathfinding (using an instance as the target instead of a vector3) starting out but it hadn’t really changed anything.

2 Likes

Use a while loop and change all the waits to task.wait so it doesn’t yield. If you are still having trouble its probably because of your pathfinding method, not the loop.

2 Likes

I fixed this by setting my AI’s networkownership to the currently chasing player, No clue if it would work for you though.

3 Likes

This is actually a pretty good solution, but it is still vulnerable to exploiters.

2 Likes

I’ll just do some testing on my own later today and see if I can find one of my past projects. If I do I’ll let you know.

2 Likes

Hey, I’m having the same issue with the same module. I’ll put this conversation at watching because I REALLY need solutions to this.

2 Likes

Just so you know, I’m working on this as I speak. Sorry it took me a few days to get to it, I had to deal with other stuff in life. Nevertheless, this problem is sort of fun. It’s a break from the usual problems I deal with. Anyways, I have done some initial testing, and I should have a solution later tonight. It turns out I was on the right track earlier when I provided the getPathfindPoint function, but I took a wrong turn, as well as I didn’t account for the actual latency between the client and server.

I’ll post in an hour or two with the actual solution. Just know that with it, it will definitely require you to centralize your system. However, it can also be done with a single script in a character for demonstration purposes.

EDIT: Never mind, it’s going to be until tomorrow until I complete this. I decided to go the perfectionist route and create a new module that has functionality above what is required to make this work. Oh well.

1 Like

I’m having this issue as well. I’m beginning to think this is an engine bug…

1 Like

It’s not an engine bug, It’s caused by latency between the client and the server. The problem doesn’t show up in studio unless you go to the network settings in the studio settings tab and set the incoming replication lag to something above 0.

Also, just updating to let everyone else know I’m still working on this. The problem turned out to be a bit more complex, but I’m on the right path now and can get the NPC within 2-3 studs of the player. I should have it completed either today or tomorrow.

EDIT: I am now able to predict the position of the player rather accurately whenever the player walks in a straight line. However, I am dealing with an issue whenever they are turning. So now I’m modeling the player walk path along a curve, something I’ve never done before. This may take a few more days.

1 Like

Alright, I’ve completed my testing. I’ll update this reply later with the finished product. As for now, all I can say is this. I’ve found a method for predicting the future position of the player, but it only works with low latency/ping. If the player has a relatively high ping (around/above 100) there is no reliable way to predict their current position. The latency is just too great. With that said, it should still work with those of a lower ping.

1 Like

This solution isn’t bad, just beware that giving network ownership of the character models to the player will allow them to do anything physics related to their body and it will replicate.
E.g WalkSpeed and JumpPower.

2 Likes

Now I’m no like genius I’m just trying to put in an effort or attempt to help
But really the latency happening is the functions you have, although yes run and execution time of the functions still happen very fast
I didn’t make very hard changes, just some I feel like would improve

local RS = game:GetService("ReplicatedStorage")
local SS = game:GetService("ServerStorage")
local SP = require(SS:WaitForChild("SimplePath"))

local sprinter = script.Parent
local hum = sprinter:WaitForChild("Humanoid")
local HRP = sprinter:WaitForChild("HumanoidRootPart")

local path = SP.new(sprinter)
path.Visualize = true

local names = {}
for _, name in pairs(RS:WaitForChild("Enemies"):GetChildren()) do
	table.insert(names, name.Name)
end

sprinter.PrimaryPart:SetNetworkOwner(nil)

local Target
local Blocked = true
local Distance

function findTarget()
	local aggro = 200
	local ThisTarget = nil
	local ThisBlock = true
	for _, character in pairs(workspace:GetChildren()) do
		local human = character:FindFirstChild("Humanoid")
		local RP = character:FindFirstChild("HumanoidRootPart")
		if human and RP and human.Health > 0 and not table.find(names,RP.Parent.Name) then
            local dist = (RP.Position - HRP.Position).Magnitude
			if dist < aggro then
				aggro = dist
				ThisTarget = RP
				local rayInfo = RaycastParams.new()
				rayInfo.FilterType = Enum.RaycastFilterType.Blacklist
				rayInfo.FilterDescendantsInstances = sprinter:GetChildren()
				local hit = workspace:Raycast(HRP.Position, (RP.Position - HRP.Position).Unit * 200, rayInfo)
				if hit.Instance == RP then
					ThisBlock = false
				end
			end
		end
	end
	Target = ThisTarget
    Distance = (ThisTarget ~= nil and aggro) or nil
    Blocked = (ThisTarget ~= nil and ThisBlock) or nil
end

function getPathfindingPosition(agent, target)
	local agentPos = agent:GetPivot().Position
	local targetPos = target:GetPivot().Position
	
	local direction = (agentPos - targetPos)
	local distance = direction.Magnitude
	
	if distance < 5 then
		local result = workspace:Raycast(targetPos, direction)
		return if result then result.Position else (targetPos + direction)
	elseif distance < 10 then
		local humanoid = target.Parent.Humanoid
		direction = (humanoid.MoveDirection*humanoid.WalkSpeed)
		
		local result = workspace:Raycast(targetPos, direction)
		return if result then result.Position else (targetPos + direction)
	else
		return targetPos
	end
end

game:GetService("RunService").Heartbeat:Connect(function()
    coroutine.resume(coroutine.create(function()
        findTarget()
    end))
	if Target then
		if not Blocked then
			print("not blocked")
			hum:MoveTo(getPathfindingPosition(sprinter, target), workspace.Terrain)
		elseif Blocked then
			path:Run(getPathfindingPosition(sprinter, target))
		end
	end
end)

You could give this try, obviously keep the code you have currently, as this is only just trial and error at this point and having older versions could lead to a solution

I also recommend maybe
when it does hum:MoveTo()
to just take the Target and do

local cf = Target.CFrame*CFrame.new(0,0,-3)
hum:MoveTo(cf.Position)
1 Like