Pathfinding NPC struggles to move while in midair

I am trying to make a simple yet effective pathfinding system (Capable of jumping, crossing gaps, and well, pathfinding of course), and it is almost finished and ready to implement in my game.

However, when the pathfinding runs, I’ve noticed an issue. When it is in midair or is currently jumping, it only slightly moves, which inhibits its ability to cross small gaps.

Unfortunately, I have not found any posts with a similar problem on DevForum. If you guys know any details on the reason behind this problem, please let me know. Thank you!

-- Here's my code below! I'm sincerely sorry if my code is very hard to read. 
--[[
Target is the targetted part that the current NPC is walking towards.
hrp is the current NPC's HumanoidRootPart.
]]

while self.Active do
		---
		if humanoid.Health <= 0 or (not target or not target.Parent) then break end
		
		task.desynchronize()
		---
		local LegCF = hrp.CFrame*CFrame.new(0,-2.8,0)
		
		local head:Part = Mob:FindFirstChild("Head")
		---
		if not head then 
			task.synchronize()
			self.Active = false
			break
		end
		---
		local leg_raycast = workspace:Raycast(LegCF.Position, LegCF.LookVector, raycastparameters)
		local up_jump_rc = workspace:Raycast(hrp.Position, hrp.CFrame.UpVector * humanoid.JumpPower + hrp.CFrame.LookVector/2, raycastparameters)
		local hrp_rc = workspace:Raycast(hrp.Position, hrp.CFrame.LookVector, raycastparameters)
		local pickedinstance = leg_raycast or hrp_rc
		---
		if pickedinstance
			and not humanoid.Jump 
			and not up_jump_rc 
		then		
			task.synchronize()
			task.spawn(function()
				humanoid.AutoRotate = false -- i attempted to prevent the npc from turning while jumping, to hopefully help resolve the problem. It did not work.
				humanoid.Jump = true
				repeat task.wait() until humanoid.FloorMaterial ~= Enum.Material.Air
				humanoid.AutoRotate = true
			end)
		end
		
		task.synchronize()
		---
		Path:ComputeAsync(hrp.Position, target.Position)
		
		local waypoints = Path:GetWaypoints() :: {PathWaypoint}
		
		if Path.Status == Enum.PathStatus.Success then
			local GUID = httpservice:GenerateGUID(false)
			self.Running_GUID = GUID
			
			task.spawn(function()
				task.synchronize()
				---
				for k, v in ipairs(waypoints) do
					---
					if humanoid.Health <= 0 or (not target or not target.Parent) then break end
					---
					humanoid:MoveTo(v.Position)
					while Get_Magnitude(v.Position, hrp.Position) > 4.5 and self.Active do
						task.desynchronize()
						if humanoid.Health <= 0 or (not target or not target.Parent) then break end
						----
						local HRP_RC_Vector = workspace:Raycast(hrp.Position + hrp.CFrame.LookVector, -hrp.CFrame.UpVector * 12, raycastparameters)
						local Platform_Below = v.Position.Y - hrp.Position.Y <= -3
						local WaypointDownRC = workspace:Raycast(v.Position, Vector3.new(0, -2, 0), raycastparameters)
						local Instance_Is_Part
						local Requirement_Check = false
						
						if WaypointDownRC then
							Instance_Is_Part = WaypointDownRC.Instance.ClassName == "Part"
							if Instance_Is_Part then
								if WaypointDownRC.Instance.CanCollide then
									Requirement_Check = true
								end
							else
								Requirement_Check = true
							end
						end
						
						local Judgment_Jump = not Platform_Below and 
							(not HRP_RC_Vector or HRP_RC_Vector.Distance > humanoid.JumpPower/2)
							and Requirement_Check
						if Judgment_Jump
							and not humanoid.Jump
						then
							task.synchronize()
							task.spawn(function()
								humanoid.AutoRotate = false
								humanoid.Jump = true
								repeat task.wait() until humanoid.FloorMaterial ~= Enum.Material.Air
								humanoid.AutoRotate = true
							end)
						end
						self.Previous_Position = hrp.Position
						task.synchronize()
						if self.Running_GUID ~= GUID then break end
						if not self.Active then break end
						task.wait()
					end
					
					if self.Running_GUID ~= GUID then break end
					if not self.Active then break end
				end
				waypoints = nil
				GUID = nil
			end)
			
		elseif Path.Status == Enum.PathStatus.NoPath then
			local Counter = 0
			repeat
				Path:ComputeAsync(hrp.Position, target.Position)
				Counter += 0.5
				task.wait(0.5)
			until Path.Status == Enum.PathStatus.Success or Counter >= 3
			if Counter >= 3 then
				print("TpToTarget")
				Mob:SetPrimaryPartCFrame(target.CFrame)
			end
		end
	end

edit: fixed my code formatting, sorry!

This is a very painful fun problem to solve. I’ve spent years hacking away at the humanoid movement system and still haven’t perfected it. It just depends on how much time you’re willing to invest. I’ll list some solutions from easiest to hardest.

  1. Use SimplePath - Pathfinding Module and see if the problem goes away.

  2. Have the AI simply generate 1 path and commit to it rather than spam the pathfinder every second. If the AI gets stuck somewhere while walking, you can check for this either by hooking to the Path.Blocked event and/or by implementing a watchdog timer (if AI’s position hasn’t changed for 5 seconds, make a new path)

  3. Manually define the path itself either by using pathfinding links or a custom waypoint graph/navigation mesh system. Roblox’s pathfinding system is extremely generalized for most types of games, which leads to a lot of unwanted behavior if you push the system to its limits. A custom pathfinding system would eliminate unwanted artifacts from a generalized system.

Thank you for the help! I’ll try implementing solutions 1 to 2.

Thank you so much for your help! I tried using SimplePath, and it worked so much better!

1 Like

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.