Pathfinding AI Begins to Stutter and Slowdown Overtime

I am trying to make a pathfinding AI that chases the nearest player, however I keep running into an issue with the AI to where overtime it begins to stutter making it slow down as time passes by in the server.

In the video you’ll see that at first it’s fine but overtime it’ll end up slowing down and stuttering:

I’ve tried some already existing solutions from other topics however none have worked so far and I’m not too experience in pathfinding or advanced scripting.

--Services
local Players = game:GetService("Players");
local RunService = game:GetService("RunService");
local PathFindingService = game:GetService("PathfindingService");
--Variables
local Character = script.Parent;
local Humanoid = Character.Monster;
local Config = Character.Configuration;
local atksfx = Character.Torso.attack;
local RootPart = Character.HumanoidRootPart;

local CanJump = true;

local AttackRange = Config.AttackRange.Value;
local AttackDamage = Config.AttackDmg.Value;
local AttackInterval = Config.AttackInterval.Value;

local CharacterSize = Character:GetExtentsSize();

local PathParams = {
	AgentRadius = (CharacterSize.X+CharacterSize.Z)/4,
	AgentHeight = CharacterSize.Y, 
	AgentCanJump = CanJump,
};
local CurrentTarget = nil;
--Attack Animations
local Attackanim1 = script:WaitForChild("Attack1")
local Attackanim2 = script:WaitForChild("Attack2")
local Attackanim3 = script:WaitForChild("Attack3")
local Attackanim4 = script:WaitForChild("Attack4")
--Database
local database = {
	AttackTrack1 = Humanoid:LoadAnimation(Attackanim1),
	AttackTrack2 = Humanoid:LoadAnimation(Attackanim2),
	AttackTrack3 = Humanoid:LoadAnimation(Attackanim3),
	AttackTrack4 = Humanoid:LoadAnimation(Attackanim4),
}

--Fetching Nearest Player
while wait(0.1) do --It constantly looks to get the closest player
	local NearestPlayer
	local Nearest = math.huge
	for i, q in pairs(workspace:GetDescendants(Players:GetDescendants())) do
		if q.Name == "Humanoid" and q.Parent ~= Character then
			local magni = (Character.HumanoidRootPart.Position - q.Parent.HumanoidRootPart.Position).Magnitude
			if magni <= Nearest then
				Nearest = magni
				NearestPlayer = q.Parent
			end
		end
	end

--Connections
RunService.Heartbeat:Connect(function()
		local path = PathFindingService:CreatePath(PathParams);

		if NearestPlayer ~= nil then
			path:ComputeAsync(Character.HumanoidRootPart.Position, NearestPlayer.HumanoidRootPart.Position)

			CurrentTarget = NearestPlayer;

			if path.Status == Enum.PathStatus.Success then
				local waypoints = path:GetWaypoints();
				if waypoints and waypoints[2] then
					local data = waypoints[2]
					if data.Action == Enum.PathWaypointAction.Jump then
						Humanoid.Jump = true
					end
					Humanoid:MoveTo(data.Position)
				end
			end
		end
	end)
--NPC will begin to attack whichever player is the nearest
while wait(AttackInterval) do
	local myPos, targetPos = Humanoid.RootPart.Position, CurrentTarget.Torso.Position
	if (myPos - targetPos).magnitude <= AttackRange then

		print("NPC Attacked")
		CurrentTarget.Humanoid:TakeDamage(AttackDamage)
		local RandomNumber = math.random(1, 4)
		local anim = database["AttackTrack" .. RandomNumber]
		anim:Play()
		atksfx:Play()
		end
	end
end
2 Likes

Hello BlooIsHere!

You are creating a new path on every heartbeat which is extremely expensive.

What professional pathfinding systems do is:

  • Perform a raycast and see if target player is visible. If they are, then you can assume you can walk in a straight path towards them.
  • If target is not visible, cache the TargetPosition and create a path to X position.
  • Verify target is still within range of X position while moving towards them. If not, redraw a new path.
  • During waypoints of path traversing, re-check target visibility. Cancel further path if visible and walk straight to target.

( You can completely cut out raycasting if you find it to be too expensive to perform, just use the improved pathfinding logic and don’t hook pathfinding on a heartbeat! It should be on a loop that will pause while it yields to X target. )

3 Likes

Although what you point out is indeed a problem, its not what he meant. While this may indeed cause lag, the actual issue may be his networking! When the game detects an enemy in range of a player, the network owner is changed by default unless otherwise specified not to.

To tell the game to just keep network ownership on the server, you need to loop through specifically the baseparts of the enemy/unit and run basepart:SetNetworkOwner(nil) on all of them, where basepart is the referenced part. I had this issue with my TD game, and this fixed it!

4 Likes

That will fix it most times but sometimes Roblox sets the network ownership of humanoids automatically every second. It breaks my game and I can’t fix it because of that.

Humanoids cannot even have a network owner. They are not a basepart. Only baseparts have a network owner as far as I am aware

I mean the whole model with the humanoid in it. Any part of that can be randomly set by Roblox