How could i optimize my pathfinding code?

I don’t wanna outright leak it, but i will break down how it works since it’s not that complicated.

I haven’t noticed any performance issues with it, the activity seems to linger around 5%

For reference, this is a local script, since the game is singleplayer.

it has a patrol mode and a chase mode, which it can switch between quickly. both update every frame because i’ve run into some really strange issues with the patrol mode when calculating the path only once like trying to just… fly up stairs i guess? making it update constantly has fixed that though.

the loop isn’t an actual loop though, it’s a recursive function, i call Walk() once in a different thread (task.spawn) and after it finishes, the function calls itself again, like so
image

i would love to use Heartbeat, however that means i cannot yield, and i have to yield a second before choosing a new patrol point.

if i need the loop to terminate itself, i have an if statement at the start checking if a “killloop” variable has been set to true, hence it will not continue and it will not be able to call itself again.

image

however this feels like a really hacky way to get around it. i would love some feedback on this

i was also made aware that raycasts are supported in parallel now, and i’d like to run them in parallel since i am shooting 11 raycasts every frame (number will increase) which can’t be good for performance.

if spotted then it calls a function decreasing a timer to cause the AI to lose track.

that same timer is added to if you are in direct line of sight (no debounce to it is intentional)

that’s basically the bulk of my code. how should i go about improving it? it’s not hard to manage, i’m just worried about performance. i could probably run the raycasting code in parallel in a heartbeat loop, but aside from that i really don’t know

also i’m really sorry if this is the worst code you’ve read i am genuinely not that good at organizing my code for others to read :sob:

No need to cast the rays right away. Since this is a single player game, you can first utilize a magnitude check and then cast a ray to reduce overall ray count per frame in FindTarget. If you have an FOV for the NPC, you can do some Dot/Cross checks to ensure the player is in front of it. Secondly, for managing the State, you could make a StateMachine however, it requires some uplift and may not be necessary if the state of your NPC is already really simple.

1 Like

it is indeed the latter, i am not that well versed in raycasts (it literally took me 3 hours to figure out fully somehow) could you give me a small code sample? right now there’s just 11 parts looking in different directions because i didn’t wanna get hung up on raycasts and i wanted to move onto working on other stuff but i also wanted to leave the AI in a good state so i just did.. that. it’s horrible i know i’ve been meaning to change it to just one part that shoots out several rays in a cone shape but now that i think about it i could just have one ray going towards the direction of the player (though with a limit to it of course because it’d suck if you got spotted while being behind it lol)

It’s been a while since I’ve worked w/ Dot product so bare w/ me…

-- pseudo code; use as an example
local npcPivot = NPC:GetPivot()
local characterPivot = character:GetPivot() -- character of the player
local lookVector = npcPivot.LookVector
-- for the below I actually recommend keeping it as (B-A) so you can throw in a Magnitude check.
-- This is mostly to show how Dot works.
local toPlayer = (characterPivot.Position - npcPivot.Position).Unit

local scalar = lookVector:Dot(toPlayer)
local rad = math.acos(scalar) -- make the scalar of the Dot product into a radian
warn(math.deg(rad)) -- will output the radian in degrees for you to then check for FOV...
1 Like

i think i got it for the most part, i’m doing this

local direction = (plrhrp.Position - root.Position).Unit
		local scalar = script.Parent.Camera1.CFrame.LookVector:Dot(direction)
		local rad = math.acos(scalar)
		local fov = math.deg(rad)
		print(math.deg(rad))
		if fov >= 70 then
			local ray = workspace:Raycast(script.Parent.Camera1.Position, plrhrp.CFrame.LookVector, rparams)
			if ray == nil then return end
			print(ray.Instance)
			if ray.Instance.Parent.Name == plr.Name then
				following = true
				workspace.SFX.Spotted:Play()
				task.spawn(LoseTrack)
			end
		end

but the raycast never hits anything probably because i’m doing something wrong i am terrible at math

When working w/ Raycast in this case, I believe you meant to cast it towards the player. So instead of getting the Unit right away, store it as (B-A) so you can pass the resulting vector(difference) as the direction of your raycast. This should fix the issue you’re having.

i was passing the wrong thing anyways, but it somewhat worked, the issue is it only seems to actually see me when i’m right in front of it, because i one again probably did something wrong. i don’t really know how to visualize raycasts because every time i’ve tried it just… went in the wrong direction.

		local distance = plrhrp.Position - root.Position
		local direction = distance.Unit
		local scalar = script.Parent.Camera1.CFrame.LookVector:Dot(direction)
		local rad = math.acos(scalar)
		local fov = math.deg(rad)
		print(math.deg(rad))
		if fov >= 70 then
			local ray = workspace:Raycast(script.Parent.Camera1.Position, distance, rparams)
			if ray == nil then return end
			print(ray.Instance)
			if ray.Instance.Parent.Name == plr.Name then
				following = true
				workspace.SFX.Spotted:Play()
				task.spawn(LoseTrack)
			end
		end

Sorry for such a late reply!

To help you visualize the ray, you need the mid point and target. In your case, the mid point is (Camera1.Position+plrhrp.Position)/2 and the target is plrhrp.Position. You can use the magnitude of the distance for length. I’ll be using A and B in this example…

-- pseudo code
--[[
    A = Camera1.Position
    B = plrhrp.Position
]]

local midPoint = (A + B)/2
local distance = B - A

local p = Instance.new("Part")
p.Anchored = true
p.CanCollide = false
p.CanQuery = false -- so our raycasting doesn't detect it...
p.Size = Vector3.new(.2, .2, distance.Magnitude) -- distance.Magnitude is the length of our ray
p.Color = Color3.fromRGB(255,0,0)
p:PivotTo(CFrame.lookAt(midPoint, B))
p.Parent = workspace

task.delay(1, p.Destroy, p)

Also for the FOV check, it is very important that you make sure your LookVector is pointing outward for your NPC. If this is done properly, the check would be if the fov <= 70 not fov >= 70. If the FOV of your NPC is 70, you technically would do 70/2 since FOV represents the total angular area.

Here’s the test script I was using to make sure I was feeding you the proper info, feel free to debug with it:

local A,B = script.Parent.A, script.Parent.B

game:GetService("RunService").Heartbeat:Connect(function(deltaTime: number) 
	
	local toB = (B.Position - A.Position).Unit
	
	local scalar = toB:Dot(A.CFrame.LookVector)
	local rad = math.acos(scalar)
	warn(math.deg(rad))
	
	--local midPoint = (A.Position+B.Position)/2
	--local distance = B.Position - A.Position

	--local p = Instance.new("Part")
	--p.Anchored = true
	--p.CanCollide = false
	--p.CanQuery = false -- so our raycasting doesn't detect it...
	--p.Size = Vector3.new(.2,.2, distance.Magnitude) -- distance.Magnitude is the length of our ray
	--p.Color = Color3.fromRGB(255,0,0)
	--p:PivotTo(CFrame.lookAt(midPoint, B.Position))
	--p.Parent = workspace

	--task.delay(1, p.Destroy, p)
end)

I’m not the best at explaining things such as this but, I hope this helps!

1 Like

I figured it out on my own! Also checking if the fov is high seems to just work already so that’s strange but hey if it ain’t broke don’t fix it. Only issue I encounter SOMETIMES is that the ray is just BARELY missing you if you’re at an angle but the chances of that happening are pretty slim and tbh I think it doesn’t matter that much if it doesn’t see you for like a few seconds. Thanks for the help with the original raycast improvement though it genuinely helped a ton.

1 Like

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