Detect if player is looking at NPC

I’m trying to achieve this:

I’ve got a current LocalScript inside of StarterGui, which works , however it goes through walls. How would I make it so it only makes the sound when I look at the Noob but not through the wall?

This is the current script:

local player = game.Players.LocalPlayer
local character = player.Character or player.CharacterAdded:Wait()
local npc = workspace:FindFirstChild(game.Workspace.NoobName.Value).HumanoidRootPart

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

game:GetService("RunService").Stepped:Connect(function()
	if CanSee(character.Head, npc, 20) then
		script["Creepy Suspense Hit"]:Play()
	end
end)
RunService.RenderStepped:Connect(playCreepySuspense)

Thanks in advance

1 Like

Use the DotProduct method to compare the two LookVectors of the player and the NPC. If the result is between 1 and -1, the player is looking at the NPC.

2 Likes

Where can I find out more about this and how to put it into the current script?

1 Like

How to detect, if someone is staring at the NPC? - Help and Feedback / Scripting Support - Developer Forum | Roblox
You might find this topic helpful.

3 Likes



local player = game.Players.LocalPlayer
local character = player.Character or player.CharacterAdded:Wait()
local npc = workspace:FindFirstChild(game.Workspace.NoobName.Value).HumanoidRootPart

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
		script["Creepy Suspense Hit"]:Play()
	end
end)

This script works, but it goes through walls. How would I fix that?

From here you could use the product and raycast to see if the instance is not a part of the NPC model, if it is not a part of the NPC return false, if so then return true.

2 Likes

Could you give an example of a short script to help me understand how to implement that into my current script?

You could do something like this:

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(math.rad(maxAngle))
	
	if dotProduct >= cosineAngle then
		local ray = Ray.new(playerHead.Position, (npcHead.Position - playerHead.Position).Unit * 1000)
		local hit, position = workspace:FindPartOnRayWithIgnoreList(ray, {character})
		if hit and hit:IsDescendantOf(npc.Parent) then
			return true
		end
	end
	return false
end
1 Like

How can I use the Return statement to decide whether or not to play the sound

local player = game.Players.LocalPlayer
local character = player.Character or player.CharacterAdded:Wait()
local npc = workspace:FindFirstChild(game.Workspace.NoobName.Value).HumanoidRootPart

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(math.rad(maxAngle))

	if dotProduct >= cosineAngle then
		local ray = Ray.new(playerHead.Position, (npcHead.Position - playerHead.Position).Unit * 1000)
		local hit, position = workspace:FindPartOnRayWithIgnoreList(ray, {character})
		if hit and hit:IsDescendantOf(npc.Parent) then
			return true
		end
	end
	return false
end


game:GetService("RunService").Stepped:Connect(function()
	if CanSee(character.Head, npc, 20) then --- what would I put here if it can't see the NPC
		script["Creepy Suspense Hit"]:Play()
	end
end)
1 Like

You can do this:

local player = game.Players.LocalPlayer
local character = player.Character or player.CharacterAdded:Wait()
local npc = workspace:FindFirstChild(game.Workspace.NoobName.Value).HumanoidRootPart

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(math.rad(maxAngle))

	if dotProduct >= cosineAngle then
		local ray = Ray.new(playerHead.Position, (npcHead.Position - playerHead.Position).Unit * 1000)
		local hit, position = workspace:FindPartOnRayWithIgnoreList(ray, {character})
		if hit and hit:IsDescendantOf(npc.Parent) then
			return true
		end
	end
	return false
end


game:GetService("RunService").Stepped:Connect(function()
	local CanSee = CanSee(character.Head, npc, 20)
	if CanSee then --- what would I put here if it can't see the NPC
		script["Creepy Suspense Hit"]:Play()
	else
		-- do nothing
	end
end)
2 Likes

Worked perfectly! Thank you.

I’ve added a debounce variable so the sound plays properly, because currently it’s repeating the sound so fast you can’t hear it.

local player = game.Players.LocalPlayer
local character = player.Character or player.CharacterAdded:Wait()
local npc = workspace:FindFirstChild(game.Workspace.NoobName.Value).HumanoidRootPart
local debounce = true
local cooldownTime = 30
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(math.rad(maxAngle))

	if dotProduct >= cosineAngle then
		local ray = Ray.new(playerHead.Position, (npcHead.Position - playerHead.Position).Unit * 1000)
		local hit, position = workspace:FindPartOnRayWithIgnoreList(ray, {character})
		if hit and hit:IsDescendantOf(npc.Parent) then
			return true
		end
	end
	return false
end


game:GetService("RunService").Stepped:Connect(function()
	local CanSee = CanSee(character.Head, npc, 20)
	if CanSee then --- what would I put here if it can't see the NPC
		if debounce == true then
			debounce = false
			script["Creepy Suspense Hit"]:Play()
			wait (cooldownTime )
			debounce = true
		end
	else
		-- do nothing
	end
end)

Also, not relevant your post is the first thing that comes up when you search "Detect if player is looking at NPC" on google

2 Likes

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