Help with npc noticing when the player is and isn't in its POV

So I have this npc and in this specific part of the script I want it so that once the npc has found a player to chase the npc will have a timer that goes down whenever the focused player moves out of its pov and when the timer reaches 0 the npc gives up and goes back to its waypoints but if the focused player went back into the npcs pov before the timer reached 0 then the timer would go back up to 500 and keep chasing the player.

local castRate = 24
local castRadius = 32

local updatePOVTime = 0.15
local updateTargetLocationTime = 0.1

local showRaycasts = true
local lockedIn = false

local focusedPlayer

local filter = {Dummy, Workspace.povVisuals}
local params = RaycastParams.new()
params.FilterType = Enum.RaycastFilterType.Exclude
params.FilterDescendantsInstances = filter

local giveUpChaseTime = 500
local giveUpChaseTimer = giveUpChaseTime

while true do
	for i = 1, castRate do
		if lockedIn then continue end

		local step = i * (90 / castRate)
		local _end = (finish.CFrame * CFrame.Angles(0, math.rad(step), 0) * CFrame.new(25, 0, -castRadius))

		if showRaycasts then
			local visualizePart = Instance.new("Part", Workspace.povVisuals)
			visualizePart.BrickColor = BrickColor.new("Bright red")
			visualizePart.FormFactor = "Custom"
			visualizePart.Material = "Neon"
			visualizePart.Transparency = 0.25
			visualizePart.Anchored = true
			visualizePart.Locked = true
			visualizePart.CanCollide = false
			visualizePart.Size = Vector3.new(0.1, 0.1, castRadius)
			visualizePart.CFrame = CFrame.new(start.CFrame.p, _end.p) * CFrame.new(0, 0, -castRadius / 2)
		end

		local ray = Workspace:Raycast(start.CFrame.p, (_end.p - start.CFrame.p).unit * castRadius, params)
		if ray and (Players:GetPlayerFromCharacter(ray.Instance.Parent) or Players:GetPlayerFromCharacter(ray.Instance.Parent.Parent)) then
			local foundPlayer = (Players:GetPlayerFromCharacter(ray.Instance.Parent) or Players:GetPlayerFromCharacter(ray.Instance.Parent.Parent))

			print("I SEE YOU ".. string.upper(foundPlayer.DisplayName))

			lockedIn = true
			focusedPlayer = foundPlayer

			task.spawn(function()
				while lockedIn do
					if not focusedPlayer then
						lockedIn = false
						task.wait(2)
						moveRandomWaypoint()
						break
					end

					Path:Run(focusedPlayer.Character.HumanoidRootPart)

					for i2 = 1, castRate do
						local step2 = i2 * (90 / castRate)
						local _end2 = (finish.CFrame * CFrame.Angles(0, math.rad(step2), 0) * CFrame.new(25, 0, -castRadius))

						if showRaycasts then
							local visualizePart = Instance.new("Part", Workspace.povVisuals)
							visualizePart.BrickColor = BrickColor.new("Dark blue")
							visualizePart.FormFactor = "Custom"
							visualizePart.Material = "Neon"
							visualizePart.Transparency = 0.25
							visualizePart.Anchored = true
							visualizePart.Locked = true
							visualizePart.CanCollide = false
							visualizePart.Size = Vector3.new(0.1, 0.1, castRadius)
							visualizePart.CFrame = CFrame.new(start.CFrame.p, _end2.p) * CFrame.new(0, 0, -castRadius / 2)
						end
						
						local ray2 = Workspace:Raycast(start.CFrame.p, (_end2.p - start.CFrame.p).unit * castRadius, params)
						if ray2 then
							if (Players:GetPlayerFromCharacter(ray2.Instance.Parent) or Players:GetPlayerFromCharacter(ray2.Instance.Parent.Parent)) then
								print("player is in range")
							end
						else
							print("player is out range")
						end

						if giveUpChaseTimer <= 0 then
							print("WHERE DID YOU GO?")
							focusedPlayer = nil
							giveUpChaseTimer = giveUpChaseTime
						end
					end

					task.wait(updateTargetLocationTime)
				end
			end)
		end
	end

	task.delay(0.3, function()
		for _, v in Workspace.povVisuals:GetChildren() do
			v:Destroy()
		end
	end)

	task.wait(updatePOVTime)
end

And here is a video:

You can see that it works when the player isn’t in the range of the blue lines but once they are, both prints get triggered.

Make sure to update the giveUpChaseTimer. I don’t see anywhere that it is.

One way lots of people do this is instead of having a timer, they set a future time the want it to end (e.g. chaseEndTime = tick() + giveUpChaseTime) then they can check if the chase should be over using tick() > chaseEndTime.

1 Like

Well, the idea was to have the giveUpChaseTimer update code be where the print is for when the player is in range of the blue raycasts, and I tested that out and it works fine. It’s just a matter of figuring out the issue as to why both the print statements happen at the same time when the player is in range of the blue raycasts.

That happens because not all of the raycasts hit the player at once each time, but the printing is done per raycast.

Yeah, I was thinking it might have been that. I’ve thought maybe I could add a debounce somewhere to stop additional raycasts but then I feel this gets a bit more complex than this needs to be so maybe I should look for an alternative to raycasts to get the player in the npc’ pov.

I would recommend breaking it into some functions. For example, you could make a simple function to check if the player is in view.

Writing code in a big block without functions can make it get unmanageable.

I would also recommend against using task.spawn where you do. task.spawn lets your code run out of line, which means it can cause weird stuff when multiple separate copies of your code are running out of line.

Alright well I was able to simplify the code and tried using dotProducts and It now works.

local npcToCharacter = (focusedPlayer.Character.Head.Position - head.Position).Unit
local npcLook = head.CFrame.LookVector
local dotProduct = npcToCharacter:Dot(npcLook)

if dotProduct < 0.54 then
    giveUpChaseTimer = giveUpChaseTimer - 1
    print("player is in range")
else
    giveUpChaseTimer = giveUpChaseTime
    print("player is out range")
end

if giveUpChaseTimer <= 0 then
    print("WHERE DID YOU GO?")
    focusedPlayer = nil
    giveUpChaseTimer = giveUpChaseTime
end

But there are now 2 additional problems

1 - There isn’t a fall off distance. Meaning if you are far away from the npc, as long as you are in front of it, it will see you.

2 - Walls no longer block the npc’ vision so they can detect players through walls. I imagine raycasts will be needed for this but I don’t know how to incorporate that into dotProducts.

1 Like

That’s smart! Nice work!

To include fall off you can just use the distance between focusedPlayer.Character.Head.Position and head.Position:

local maxDistance = 20 -- for example
local distance = (focusedPlayer.Character.Head.Position - head.Position).Magnitude
if distance < maxDistance then
    print("In range!")
end

To include raycasts, you can similarly just raycast between focusedPlayer.Character.Head.Position and head.Position then make sure there is nothing in the way (i.e. there is no RaycastResult returned).

(Also make sure that when raycasting that you ignore the NPC and the character so it only detects stuff in the way.)

Yep, I was able to get it all working now, thanks a bunch!

1 Like

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