How to detect, if someone is staring at the NPC?

I know it has stuff to do with raycasting but I haven’t been able to get it to work.

Basically
2 parts
NPC’s head
Player’s head
controlled by local script

1 Like

This code placed in a Local Script will create a loop on hearthbeat that checks if the player is looking at the NPC every frame.
The ray is set on the players Head and will follow the Cameras LookVector as the head don’t move when looking around.

local RunService = game:GetService("RunService")
local Player = game.Players.LocalPlayer
local NPC = game.Workspace -- Refrence the NPC's Character here
local Cam = game.Workspace.CurrentCamera

local IsPlayerLooking = false

local raycastParams = RaycastParams.new()
raycastParams.FilterType = Enum.RaycastFilterType.Blacklist 	-- Makes the following array a blacklist.
raycastParams.FilterDescendantsInstances = {Player.Character} 
-- We add the players character to the Blacklist so that his character will not interfer the ray.

local DetectionRange = 100 -- The ray will be this long, so the player has to be closer than this to the NPC

-- This check will run on Heartbeat which should be equal to the players FPS.
-- Meaning, IsPlayerLooking will allways be correct.
RunService.Heartbeat:Connect(function()
	local raycastResult = workspace:Raycast(Player.Character.Head.CFrame.Position, Cam.CFrame.LookVector * DetectionRange, raycastParams)
	if raycastResult then
		-- The ray hit something
		if raycastResult.Instance:IsDescendantOf(NPC) then
			-- The player is looking at the NPC
			IsPlayerLooking = true
		else
			-- The player is looking at something, but its not the NPC
			IsPlayerLooking = false
		end
	else
		-- The player is not looking at anything
		IsPlayerLooking = false
	end
end)

This only triggers when looking directly on a descendant part of the NPC, if you want it to trigger when looking close to the NPC instead, you need a different approach:

local RunService = game:GetService("RunService")
local Player = game.Players.LocalPlayer
local NPC = game.Workspace -- Refrence the NPC's Character here
local Cam = game.Workspace.CurrentCamera

local IsPlayerLooking = false

-- The max distance from the camera hit point to the NPC before he would not be looking.
local MaxDistance = 10

-- This check will run on Heartbeat which should be equal to the players FPS.
-- Meaning, IsPlayerLooking will allways be correct.
RunService.Heartbeat:Connect(function()
	-- We get the distance to the NPC.
	local DistanceToNPC = (Player.Character.Head.CFrame.Position - NPC.PrimaryPart.CFrame.Position).magnitude
	
	-- We use the distance and the cameras lookvector to make a point which would hit the NPC normally if direct hit took place.
	local LookPosition = CFrame.new(Player.Character.Head.CFrame.Position) + Cam.CFrame.LookVector * DistanceToNPC
	
	if (LookPosition.Position - NPC.PrimaryPart.CFrame.Position).magnitude < MaxDistance then
		-- The player is looking close or on the NPC.
		IsPlayerLooking = true
	else
		-- Not looking close to the NPC.
		IsPlayerLooking = false
	end
end)

3 Likes

@MEsAv2 has a fun solution, I’ll offer another one!

vector1:Dot(vector2) gives you the cosine of the angle between two vectors. You can use this to check the angle between the “player head → npc head” and the “look direction of the player head”:

local player = game.Players.LocalPlayer
local character = player.Character or player.CharacterAdded:Wait()
local npc = workspace:WaitForChild("Part")

local function CanSee(playerHead, npcHead, maxAngle)
	local lookDirection = playerHead.CFrame.LookVector
	local towardsNpc = (npcHead.Position - playerHead.Position).Unit
	local dotProduct = lookDirection:Dot(towardsNpc)
	local cosineAngle = math.cos(maxAngle)
	return dotProduct >= cosineAngle
end

-- EXAMPLE
game:GetService("RunService").Stepped:Connect(function()
	if CanSee(character.Head, npc, 20) then
		npc.BrickColor = BrickColor.Green()
	else
		npc.BrickColor = BrickColor.Red()
	end
end)
7 Likes

Both are successful, so I dont know which one to mark as the Solution, I’ll like both and select the second one as a solution. Thank you!