Pathfinding stuttering

I’m using PathfindingService to make this monster wander around my map until it’s near a player, then it chases the player. My problem is that for the first 2 minutes, it works perfectly (video 1), but then it starts stuttering (video 2). I found temporary fixes like moving the monster up half a stud, recalculating the route, and teleporting to an open room when the monster hasn’t moved far enough in an amount of time, but the monster will always go back to stuttering after those 2 minutes. I have no idea why.

(Video 1)

(Video 2)

Function MoveToSpawn() moves the monster to a spawn point

Code:

local function FollowPath(destination)
		task.wait()
		
		local continuing = nil
		destination, continuing = GetDestination(destination) -- This is a function that checks if a player is nearby, if not, it checks if the monster is currently going to a random room, if it is not, it chooses a room
	
		local humanoid = MONSTER_OBJ.Humanoid
		local rootPart = MONSTER_OBJ.PrimaryPart
		
		local success, errorMessage = true, nil
		if (not continuing) then 
           -- Go to new random room or close by player
			success, errorMessage = pcall(function()
				PATH:ComputeAsync(rootPart.Position, destination)
			end)
			
			if (success and PATH.Status == Enum.PathStatus.Success) then
				WAYPOINTS = PATH:GetWaypoints()

				if (BLOCKED_CONNECTION) then
					BLOCKED_CONNECTION:Disconnect()
				end

				BLOCKED_CONNECTION = PATH.Blocked:Connect(function(blockedWaypointIndex)
                   -- Path blocked, stop everything and recalculate
					if (blockedWaypointIndex >= NEXT_WAYPOINT_INDEX) then
						REACHED_CONNECTION:Disconnect()
						BLOCKED_CONNECTION:Disconnect()
						WAYPOINTS = nil
						NEXT_WAYPOINT_INDEX = nil
						REACHED_CONNECTION = nil
						BLOCKED_CONNECTION = nil
						destination = nil
					end
				end)
				
				if (not REACHED_CONNECTION) then
					REACHED_CONNECTION = humanoid.MoveToFinished:Connect(function(reached)
						
						if (reached and NEXT_WAYPOINT_INDEX+1 < #WAYPOINTS) then

							NEXT_WAYPOINT_INDEX += 1

							if (WAYPOINTS[NEXT_WAYPOINT_INDEX].Action == Enum.PathWaypointAction.Jump) then
								MONSTER_OBJ.Humanoid:ChangeState(Enum.HumanoidStateType.Jumping)
							end

						    humanoid:MoveTo(WAYPOINTS[NEXT_WAYPOINT_INDEX].Position)
						else
							-- Reached destination, stop everything and restart
							REACHED_CONNECTION:Disconnect()
							BLOCKED_CONNECTION:Disconnect()
							WAYPOINTS = nil
							NEXT_WAYPOINT_INDEX = nil
							REACHED_CONNECTION = nil
							BLOCKED_CONNECTION = nil
							destination = nil
						end

					end)
				end

				NEXT_WAYPOINT_INDEX = 2
				if (WAYPOINTS[NEXT_WAYPOINT_INDEX]) then
					-- Go to first waypoint
					if (WAYPOINTS[NEXT_WAYPOINT_INDEX].Action == Enum.PathWaypointAction.Jump) then
						MONSTER_OBJ.Humanoid:ChangeState(Enum.HumanoidStateType.Jumping)
					end
					
                    -- Make sure the monster isn't stuck, out of the map, or on top of the map
					if (MONSTER_OBJ.PrimaryPart.Position.Y >= 23) then
						MoveToSpawn()
					elseif (tick()-lastCheckStuck[1] > 3) then
						if (lastCheckStuck[2]) then
							local movedSince = (MONSTER_OBJ.PrimaryPart.Position-lastCheckStuck[2]).Magnitude
							if (movedSince < 20) then
								stuckCnt += 1
								if (stuckCnt == 1) then
									MONSTER_OBJ:PivotTo(MONSTER_OBJ.PrimaryPart.CFrame+Vector3.new(0, 0.3, 0))
								elseif (stuckCnt == 3) then
									stuckCnt = 0
									MoveToSpawn()
								end
							end
						end
						lastCheckStuck = {tick(), MONSTER_OBJ.PrimaryPart.Position}
					end
					
					if (WAYPOINTS and NEXT_WAYPOINT_INDEX and WAYPOINTS[NEXT_WAYPOINT_INDEX] and humanoid) then
						humanoid:MoveTo(WAYPOINTS[NEXT_WAYPOINT_INDEX].Position)
					end
					
				else
					destination = nil
				end
			else
				warn("Path not computed!", errorMessage)
				destination = nil
			end
			FollowPath(destination)
		else
            -- Monster has a destination, just make sure it doesn't end up on top of the map, below the map, or stuck
			if (MONSTER_OBJ.PrimaryPart.Position.Y <= 7) then
				REACHED_CONNECTION:Disconnect()
				BLOCKED_CONNECTION:Disconnect()
				WAYPOINTS = nil
				NEXT_WAYPOINT_INDEX = nil
				REACHED_CONNECTION = nil
				BLOCKED_CONNECTION = nil
				
				MoveToSpawn()
			elseif (MONSTER_OBJ.PrimaryPart.Position.Y >= 23) then
				REACHED_CONNECTION:Disconnect()
				BLOCKED_CONNECTION:Disconnect()
				WAYPOINTS = nil
				NEXT_WAYPOINT_INDEX = nil
				REACHED_CONNECTION = nil
				BLOCKED_CONNECTION = nil
				
				MoveToSpawn()
			elseif (tick()-lastCheckStuck[1] > 3 and lastCheckStuck[2]) then
				local movedSince = (MONSTER_OBJ.PrimaryPart.Position-lastCheckStuck[2]).Magnitude
				if (movedSince < 20) then
                    -- Monster is stuck, this is where I will move the monster up a stud, recalculate, or move to an open room
					stuckCnt += 1
					if (stuckCnt <= 2) then
						MONSTER_OBJ:PivotTo(MONSTER_OBJ.PrimaryPart.CFrame+Vector3.new(0, 0.3, 0))
					elseif (stuckCnt <= 4) then
						REACHED_CONNECTION:Disconnect()
						BLOCKED_CONNECTION:Disconnect()
						REACHED_CONNECTION = nil
						BLOCKED_CONNECTION = nil
						WAYPOINTS = nil
					elseif (stuckCnt == 5) then
						stuckCnt = 0

						REACHED_CONNECTION:Disconnect()
						BLOCKED_CONNECTION:Disconnect()
						REACHED_CONNECTION = nil
						BLOCKED_CONNECTION = nil
						NEXT_WAYPOINT_INDEX = nil
						WAYPOINTS = nil
						destination = nil

						MoveToSpawn()
					end
				end
				lastCheckStuck = {tick(), MONSTER_OBJ.PrimaryPart.Position}
			end
			
			task.wait()
			FollowPath(destination)
		end
	end

Thanks for the help, let me know if you have any questions

My theory is that after 2 minutes, the script is calculating too much unnecessary things. It could be running lines of codes that you didn’t intend for.

You are calling the function within itself and using connections. This, if not done carefully could have loops and stuff still running over and over again. Calling a function in itself could cause an inf loop of just repeating itself over and over again.

You also have “:Connect()” with in the function, which means it will connect to the event the number of times you called the function. It’s like having lots of touched events running for the same purpose.

It’s hard to explain but hopefully this helped.

I’ve looked through the script several times and couldn’t find a way for any memory leaks to be created.

As a first step, Try adding print statements at various places, it can help you see which loop may be firing excessively

I have a bunch of prints, I just took them out to make the script more readable.
But more specifically, I have a print right here

And when it starts stuttering, the print matches the monster’s movement, so I’m not sure if that means that the monster at some point decides to stop moving so the function stops getting called or what.

So it looks like a major part of the problem were my lightbulbs hanging from ropes. I anchored everything and disabled collisions and it seems to be working way better. It still bugs out, but the code I had in place to deal with it getting stuck is way more efficient.

Perhaps you could implement path caching and a finite-state machine? Instead of recalculating paths to those areas, you can reuse the cached paths until a significant change occurs. As for finite-state machines, I still don’t know all too much about how to implement them, but they seem applicable in this scenario.

1 Like