How to create a 'NPC sees player' algorithm

In my new project, which involves sneaking into other buildings and stealing items, we want to make a system where NPCs can spot the player. The question is how we go about doing this

It’s not as simple as using grid logic, since our game is a 2D game based around grids, we want it to be variable based on the height of objects and if the player is crouching or not
image

8 Likes

Raycasts might be a good idea. They’re easy and almost work in any solution. You send a raycast from enemy to the player and if the player can be seen then they will be “detectable” at a minimum.

Look into the Ray API.

5 Likes

Like what @EpicMetatableMoment said, use Raycasting. I made a quick script to help you get the idea.

--// CONSTANTS \\--

local DISTANCE = 20 -- How far the NPC can see.

--// SERVICES \\--

local Players = game:GetService("Players")

--// VARIABLES \\--

local origin = script.Parent.Head -- Origin of where the raycast is.

--// MAIN CODE \\--

while wait() do
	
	local ray = Ray.new(origin.Position, origin.CFrame.lookVector * DISTANCE) -- Calculating the raycast.
	
	local hit = workspace:FindPartOnRay(ray, origin.Parent) -- Creating a ray to see what the npc sees.
	
	if hit then  
		for _, player in pairs(Players:GetPlayers()) do -- We're going to loop through all players to see if the npc sees any of them.
			if player.Character and player.Character:IsAncestorOf(hit) then 
				-- Handle what happens here. 
				print("I see " .. player.Name)
                break
			end
		end
	end 
		
end

Of course, you’re going to want to change the script to have it suit your needs, but this will give you an idea of how they work.

17 Likes

You can also use the dot product.

local viewP = viewDirection:dot(directionToPlayer)

viewDirection would be the direction their eyes face and directionToPlayer is (playerPositon - eyePosition).Unit
This returns a number between -1 and 1. -1 means theyre behind them, 0 means theyre perpendicular to them, and 1 means theyre right in front of them. You can use this to detect if the NPC can see them within a cone of vision. This is very useful if it is like old Metal Gear where you can sneak around and behind people.

13 Likes

This is useful but how can you make it see wider? Like bigger angle on the vision, cuz rn it can only see something exactly in front of the head. I guess it can be maybe fixed if u make the “origin” part some other part that is bigger.

You could probably cast a ray to all nearby players up to 90° and check if theres anything in the way.
Use the information from this comment to check the angle.

I have a question, is there a more effective way than looping constantly through all players because would that not cause some lag?

Raycasts generally shouldn’t cause lag in a server loop unless theres a RemoteEvent involved

1 Like

Raycasts are extremely fast on low distances, if you’re not calling it every single frame

1 Like

I have also an idea where you could use a if then statement.

try dot producting the NPCs head and the target,
and raycasting from NPC’s head to the target to check if anything non see through is blocking it.

local function DP(TargetPos, CF)
 --Target is what youre trying to check whether if its in the field of view
--CF is the CF of the NPCs head
	local LV = CF.LookVector
	local DIR_TO_POS  = (TargetPos - CF.Position).Unit 

	return LV:Dot(DIR_TO_POS)
end 

returns a value between -1 and 1, depending on how you set it up, it will return something between 0 and 1 for everything in the front, and something between 0 and -1 for everything in the back where I believe 1 to be the 90* front angle and -1 the 90* behind angle and 0 being the sides…

Play around with the return values to understand them, enjoy!

Im personally using it to help Vehicular NPCs steer towards nodes where im using the RightVector instead of the Lookvector so that -1 is left and 1 is right and the front is 0… Good Luck!!

EDIT:
I had no idea this was a 3 year old post thats already been covered, sorry everyone!