Pathfinding Issue

So, I’ve been working on this Pathfinding AI thing that I can reuse overtime, Everything is working fine besides one thing, no the AI doesn’t stutter (well It kinda does but you get what i’m trying to say).

So, the issue is that the NPC seems to be slower whilst playtesting (KEEP IN MIND THE WALKSPEED IS 50).

Theres not much else to say, but heres a video of it.

Playtesting:

Running on the server:

The script:

script.Parent.PrimaryPart:SetNetworkOwner(nil)

--// MAIN FUNCTIONS \\--

function RayCheck(target)
	local origin = AIRoot.Position
	local direction = (target.Position - AIRoot.Position).Unit * CONFIG_DR

	local rp = RaycastParams.new()
	rp.FilterDescendantsInstances = {script.Parent}
	rp.FilterType = Enum.RaycastFilterType.Blacklist

	local result = workspace:Raycast(origin, direction, rp)

	if result then
		if result.Instance:IsDescendantOf(target.Parent) and math.abs(result.Position.Y - AIRoot.Position.Y) < 2 then
			return true
		else
			return false
		end
	end
end

function MoveToRayCheck(pos)
	local origin = AIRoot.Position
	local direction = (pos - AIRoot.Position).Unit * CONFIG_DR

	local rp = RaycastParams.new()
	rp.FilterDescendantsInstances = {script.Parent}
	rp.FilterType = Enum.RaycastFilterType.Blacklist

	local result = workspace:Raycast(origin, direction, rp)

	if result then
		return false
	else
		return true
	end
end

function FOVCheck(target) -- DEPRECATED
	local npcToChar = (target.Position - script.Parent.Head.Position).Unit
	local npcLook = script.Parent.Head.CFrame.LookVector

	local dotProduct = npcToChar:Dot(npcLook)

	if dotProduct > 0.5 then
		if RayCheck(target) then
			print("IN FOV")
			return true
		else
			return false
		end
	else
		print("NOT IN FOV")
		return false
	end
end

local function MoveRandomly()
	print("MOVING RANDOMLY")
	local X_Rand = math.random(-25, 25)
	local Z_Rand = math.random(-25, 25)
	local FinalResult = Vector3.new(X_Rand, 0, Z_Rand)
	
	if MoveToRayCheck(AIRoot.Position + FinalResult) then
		repeat
			AIHum:MoveTo(AIRoot.Position + FinalResult)
			AIHum.MoveToFinished:Wait(1)
		until PathfindingToTarget == true or (AIRoot.Position - (AIRoot + FinalResult)).Magnitude < 3
	end
end

local function FindTarget()
	local ws = workspace
	local dist = CONFIG_DR
	local temp = nil
	for i, v in pairs(workspace:GetDescendants()) do
		if v:FindFirstChild("Humanoid") and v.Humanoid ~= AIHum then
			if v:FindFirstChild("HumanoidRootPart") and v.HumanoidRootPart ~= AIRoot then
				if (v.HumanoidRootPart.Position - AIRoot.Position).Magnitude < dist and v.Humanoid.Health > 0 and v.Humanoid ~= nil then
					dist = (v.HumanoidRootPart.Position - AIRoot.Position).Magnitude
					temp = v.HumanoidRootPart
				end
			end
		end
	end
	return temp
end

local function CalculateAgentParam()
	if AIHum.RigType == Enum.HumanoidRigType.R15 then
		local Torso = script.Parent.UpperTorso
		local Limb1 = script.Parent.RightUpperArm
		local Limb2 = script.Parent.LeftUpperArm
		local Head = script.Parent.Head
		local LegLower = script.Parent.LeftLowerLeg
		local LegUpper = script.Parent.LeftUpperLeg
		local Foot = script.Parent.LeftFoot
		
		local radius = math.ceil((Torso.Size.X + Limb1.Size.X + Limb2.Size.X))
		local height = math.floor((Head.Size.Y + Torso.Size.Y + LegLower.Size.Y + LegUpper.Size.Y + Foot.Size.Y))
		
		local agentCanJump = true
		
		local final = {
			["AgentRadius"] = radius,
			["AgentHeight"] = height,
			["AgentCanJump"] = agentCanJump
		}
		
		agentParam = final
		
		return final
	end
end

local function GeneratePath(target)
	PathfindingToTarget = true
	local path = PS:CreatePath(CalculateAgentParam())
	path:ComputeAsync(AIRoot.Position, target.Position)
	local waypoints = path:GetWaypoints()
	
	if path.Status == Enum.PathStatus.Success then
		for _, v in pairs(waypoints) do
			-- WAYPOINT GENERATION
			
			local point = Instance.new("Part")
			point.Anchored = true
			point.CanCollide = false
			point.Parent = workspace.Waypoints
			point.Size = Vector3.new(0.5, 0.5, 0.5)
			point.Shape = Enum.PartType.Ball
			point.Position = v.Position + Vector3.new(0, 2, 0)
			point.BrickColor = BrickColor.new("Institutional white")
			
			-- MAIN
			
			if v.Action == Enum.PathWaypointAction.Jump then
				AIHum.Jump = true
			end
			
			if RayCheck(target) then
				repeat
					AIHum:MoveTo(target.Position)
					wait(0.1)
					if target == nil then
						break
					elseif target.Parent == nil then
						break
					end
				until RayCheck(target) == false or target.Parent.Humanoid.Health < 1
				break
			else
				AIHum:MoveTo(v.Position)
				local timeout = AIHum.MoveToFinished:Wait(0.1)
				
				if not timeout then
					AIHum.Jump = true
					print("Path too long ")
					break
				end
			end
		end
	end
	PathfindingToTarget = false
end

while wait() do
	local target = FindTarget()
	if target and RayCheck(target) then
		GeneratePath(target)
	elseif target == nil then
		--MoveRandomly() (DEPRECATED)
	end
end

When it’s chasing you, go onto the server and print the network owner. Or I think you can set the network owner to the player being chased then change it back to the server when the player dies or is lost.

I’ve tried it, unfortunately it doesn’t work.

I believe the actual issue here is something generally unfixable and it’s latency lag. Latency lag is where the client is a few milliseconds in front of the server (it’s actually more like the client is actually slower than the server, but I’ll get into that later). It usually depends on how good your internet connection is.

There are a few solutions, but if this issue doesn’t destroy gameplay then I suggest not fixing it at all, as there really isn’t anything to fix.

Solution one would be to determine where the client is going on the server before the client actually updates to the server. It sounds confusing, I know, but generally the AI is a few milliseconds behind the client because the AI is ran on the server, so you can just assume that the player is moving and get their moving direction and just have the AI be a little bit closer. It seems crazy but if done properly it can really bridge the gap between the client and server lag.

The second solution, which should only be done if you can’t do the first solution properly, is to trust the client. I’ve never scripted something like this before so this might not work as well as I think it will, but generally you can get the clients positions from asking the client via RemoteEvent and updating the AI accordingly with that information.

The issue with the second solution is that 50% of exploiters that know how to use RemoteSpy will be able to possibly destroy your game, especially if your game is multiplayer. They could send false coordinates to the server. You could do sanity checks but in reality, they could bypass those too by just making the AI even farther behind than it already is.

Let me know if I should elaborate more!

2 Likes

Thank you so much! I’ll keep this in mind!

1 Like