How can I fix this following AI?

I’m trying to make an AI that will follow the player. One of the things I wanted to fix instead of just doing a constant humanoid:MoveTo() loop is that the AI will eventually run into walls. To fix this, I’m trying to make it dynamically switch from doing the constant humanoid:MoveTo() to pathfinding.

The biggest issue is that when the player is walking behind a wall, the AI will constantly be creating paths causing the AI to “stutter”. I need help to figure out how to fix this. What I tried to do was I tried making it so that when the AI creates a new path, it will continue walking on it’s old path but that didn’t work. Any help is appreachiated!

Video of the AI in action:

RBXL file:
movement.rbxl (71.7 KB)

AI Script:

local character = script.Parent
local humanoid = character.Humanoid
local root = character.HumanoidRootPart

local raycastParameters = RaycastParams.new()
raycastParameters.FilterType = Enum.RaycastFilterType.Blacklist
raycastParameters.IgnoreWater = true

local pathfindingService = game:GetService("PathfindingService")
local agentParameters = {}

agentParameters.AgentRadius = 4
agentParameters.AgentHeight = 5
agentParameters.AgentCanJump = true
agentParameters.WaypointSpacing = 4

local path:Path = pathfindingService:CreatePath(agentParameters)

local function positionChanged(old:Vector3, current:Vector3)
	local distance = (old - current).Magnitude
	if distance > 1 then
		print("Position Changed")
		return true
	end
	return false
end

local function blocked(target:Vector3)
	local origin = root.Position
	local direction = target - origin
	local raycastResult = workspace:Raycast(origin, direction, raycastParameters)
	if raycastResult then
		print("Blocked")
		return true
	end
	return false
end

local function resetNetworkOwner()
	for _, child:Instance in pairs(character:GetChildren()) do
		if child:IsA("BasePart") and not child.Anchored then
			child:SetNetworkOwner(nil)
		end
	end
end

local function getClosestPlayer()
	local closestPlayer:Player, closestPlayerDistance:number
	for _, player:Player in pairs(game.Players:GetPlayers()) do
		local playerCharacter = player.Character
		if playerCharacter then
			local playerRoot:Part? = playerCharacter:FindFirstChild("HumanoidRootPart")
			local playerHumanoid:Humanoid? = playerCharacter:FindFirstChild("Humanoid")
			if playerRoot and playerHumanoid and playerHumanoid.Health > 0 then
				local distance = (root.Position - playerRoot.Position).Magnitude
				if not closestPlayer or closestPlayerDistance > distance then
					closestPlayer = player
					closestPlayerDistance = distance
				end
			end
		end
	end
	return closestPlayer
end

local function stuck()
	print("Checking if stuck")
	raycastParameters.FilterDescendantsInstances = {character}
	local raycastResult = workspace:Raycast(root.Position, root.CFrame.LookVector * 1000, raycastParameters)
	if raycastResult then
		print("Found something")
		if (root.Position - raycastResult.Instance.Position).Magnitude <= 4 then
			print("That something is in range, we're stuck")
			return true
		else
			print("That something is outta range, we ain't stuck")
			return false
		end
	end
	print("Didn't find anything, we ain't stuck")
	return false
end

local function makePart(position:Vector3)
	local part = Instance.new("Part")
	part.Name = "Waypoint"
	part.Parent = workspace.Waypoints
	part.Size = Vector3.new(0.3, 0.3, 0.3)
	part.Position = position
	part.Anchored = true
	part.BrickColor = BrickColor.new("Deep orange")
	part.Material = Enum.Material.Neon
	part.Shape = Enum.PartType.Ball
end

local pathfindingTo = false

local function pathfindTo(goal:Vector3, part:Part)
	pathfindingTo = true
	workspace.Waypoints:ClearAllChildren()
	path:ComputeAsync(root.Position, goal)
	local waypoints = path:GetWaypoints()
	for _, waypoint:PathWaypoint in pairs(waypoints) do
		makePart(waypoint.Position)
	end
	for _, waypoint:PathWaypoint in pairs(waypoints) do
		if (blocked(goal) or stuck()) and not positionChanged(goal, part.Position) then
			resetNetworkOwner()
			if waypoint.Action == Enum.PathWaypointAction.Jump then
				humanoid:ChangeState(Enum.HumanoidStateType.Jumping)
			end
			humanoid:MoveTo(waypoint.Position)
			humanoid.MoveToFinished:Wait()
		end
	end
	workspace.Waypoints:ClearAllChildren()
	pathfindingTo = false
end

local lastTargetPosition

while true do
	local target
	repeat 
		target = getClosestPlayer()
		wait()
	until target
	local targetRoot:Part = target.Character.HumanoidRootPart
	raycastParameters.FilterDescendantsInstances = {character, target.Character}
	if blocked(targetRoot.Position) then
		if not pathfindingTo or (not lastTargetPosition or positionChanged(lastTargetPosition, targetRoot.Position)) then
			pathfindTo(targetRoot.Position, targetRoot)
			lastTargetPosition = targetRoot.Position
		end
	else
		if not stuck() then
			resetNetworkOwner()
			humanoid:MoveTo(targetRoot.Position)
		else
			pathfindTo(targetRoot.Position, targetRoot)
			lastTargetPosition = targetRoot.Position
		end
	end
	wait()
end
3 Likes

Replying to bump this up because nobody replied and the topic got pushed down.

I downloaded the place file and reviewed every script in game.


I don’t see anything wrong with anything, so this is quite strange.
I will continue looking into it and try and find something that might be wrong.

I’ll report back later if I do or do not find something.

Thanks! I just got home from school. I’ll put the new code in and let you know how it goes.

Alright so, the issue still remains however it is a lot less noticeable.

The biggest thing is that while the player is moving behind a wall, the AI will go to pathfind. Since the player is moving, the AI will be constantly updating the path, and will stop moving while it is computing the path. What I’m thinking about doing is that when the path is being computed, the AI will move in the direction of the last waypoint.

Not really sure, but the script is a lot more optimized! Thanks!

1 Like

Got it working!!

Code:

local character = script.Parent
local humanoid = character.Humanoid
local root = character.HumanoidRootPart

local raycastParameters = RaycastParams.new()
raycastParameters.FilterType = Enum.RaycastFilterType.Blacklist
raycastParameters.IgnoreWater = true

local pathfindingService = game:GetService("PathfindingService")
local agentParameters = {}

agentParameters.AgentRadius = 4
agentParameters.AgentHeight = 5
agentParameters.AgentCanJump = true
agentParameters.WaypointSpacing = 4

local path:Path = pathfindingService:CreatePath(agentParameters)

local function positionChanged(old:Vector3, current:Vector3)
	local distance = (old - current).Magnitude

	if distance > 1 then
		print("Position Changed")
		return true
	end

	return false
end

local function blocked(target:Vector3)
	local origin = root.Position
	local direction = target - origin
	local raycastResult = workspace:Raycast(origin, direction, raycastParameters)

	if raycastResult then
		warn("Blocked")
		return true
	end

	return false
end

local function resetNetworkOwner()
	for _, child:Instance in pairs(character:GetChildren()) do
		if child:IsA("BasePart") and not child.Anchored then
			child:SetNetworkOwner(nil)
		end

	end

end

local function getClosestPlayer()
	local closestPlayer:Player, closestPlayerDistance:number

	for _, player:Player in pairs(game.Players:GetPlayers()) do
		local playerCharacter = player.Character

		if playerCharacter then
			local playerRoot:Part? = playerCharacter:FindFirstChild("HumanoidRootPart")
			local playerHumanoid:Humanoid? = playerCharacter:FindFirstChild("Humanoid")

			if playerRoot and playerHumanoid and playerHumanoid.Health > 0 then
				local distance = (root.Position - playerRoot.Position).Magnitude

				if not closestPlayer or closestPlayerDistance > distance then
					closestPlayer = player
					closestPlayerDistance = distance
				end
			end
		end

	end

	return closestPlayer
end

local function stuck()
	print("Checking if stuck")
	raycastParameters.FilterDescendantsInstances = {character}
	local raycastResult = workspace:Raycast(root.Position, root.CFrame.LookVector * 1000, raycastParameters)
	if raycastResult then
		print("Found something")
		if (root.Position - raycastResult.Instance.Position).Magnitude <= 4 then
			warn("That something is in range, we're stuck")
			return true
		else
			print("That something is outta range, we ain't stuck")
			return false
		end
	end
	print("Didn't find anything, we ain't stuck")
	return false
end

local pathfindingTo = false
local pathsCreated = 0
local pathStarted = false
local directFollowing = false

local function pathfindTo(goal:Vector3, part:Part)
	pathfindingTo = true
	pathStarted = false
	pathsCreated += 1
	local currentPathNumber = pathsCreated

	path:ComputeAsync(root.Position, goal)

	local waypoints = path:GetWaypoints()
	
	pathStarted = true
	for _, waypoint:PathWaypoint in pairs(waypoints) do
		if (pathsCreated == currentPathNumber or not pathStarted) and not directFollowing then
			resetNetworkOwner()

			if waypoint.Action == Enum.PathWaypointAction.Jump then
				humanoid:ChangeState(Enum.HumanoidStateType.Jumping)
			end

			humanoid:MoveTo(waypoint.Position)
			humanoid.MoveToFinished:Wait()
		end

	end

	pathfindingTo = false
	pathStarted = false
end

local lastTargetPosition

coroutine.wrap(function()
	while true do
		local target

		repeat 
			target = getClosestPlayer()
			task.wait(0.001)
		until target

		local targetRoot:Part = target.Character.HumanoidRootPart
		raycastParameters.FilterDescendantsInstances = {character, target.Character}

		if blocked(targetRoot.Position) then
			directFollowing = false
			if (not lastTargetPosition or positionChanged(lastTargetPosition, targetRoot.Position)) then
				print("Starting new path")
				spawn(function()
					pathfindTo(targetRoot.Position, targetRoot)
				end)
				lastTargetPosition = targetRoot.Position
			end
		else
			directFollowing = true
			if not stuck() then
				resetNetworkOwner()
				humanoid:MoveTo(targetRoot.Position)
			else
				pathfindTo(targetRoot.Position, targetRoot)
				lastTargetPosition = targetRoot.Position
			end
		end

		task.wait(0.001)
	end

end)()

Feel free to take and use this as you want, I don’t mind!

2 Likes

I don’t think I could get it to stop completely, you’d probably need to rewrite a function or two for it to stop 100%

But, I’m glad I could help!!

Bro, we fr responded at the same time :skull:

Yeah, lol. What I did was alter the code so that the AI won’t stop on it’s current path until the path that’s being generated is generated.

Pretty smart.


Despite me being quite smart I am a real doofus sometimes, probably would have taken me a week or two to figure that out :skull:

And that is especially saying I work right after I get home from school to midnight, and sometimes later.

Next time, instead of going through the hassle of trying to understand pathfinding and stuff, just use this: Benis' Pathfinding - Customizable, Easy to Use & Auto-Animating (R6 & R15 SUPPORT)!

just insert it into an npc and change the settings

1 Like

Alright so, I’m using your script now and there’s an issue.

Whenever there’s a thing that the AI has to jump over to get to me, it will constantly jump until it reaches the point to where it needs to jump.

Thanks for reporting! I’ll look forward to fixing it