Introduction
Hello everyone! In this tutorial, I’m going to teach you how to create a realistic NPC eyesight system so NPCs can see and detect players and other objects. For example, you could use this to make a guard detect a player.
Steps
-
Create a script in
ServerScriptService
and name it whatever you’d like. This script will control every NPC with eyesight. -
Give the NPCs you want to have eyesight a tag named “NPC” and the objects you want to NPC to detect a tag named “Detectable.” Players will be automatically detected. The “Detectable” tag may only be added to models.
-
Start defining the variables in your script.
local PlayerService = game:GetService("Players")
local CollectionService = game:GetService("CollectionService")
local npcModels = CollectionService:GetTagged("NPC")
local viewRange = 30 -- How far the NPC can see in studs
local fieldOfView = 70 -- The NPC's field of view in degrees
local objectLastDetected = nil -- The object the NPC last saw
- Create a
getCharacters
function. This function will return atable
of every player’s character in the game.
local function getCharacters()
local characters = {}
local players = PlayerService:GetPlayers()
for index, player in players do
table.insert(characters, player.Character)
end
return characters
end
- Next, create a
raycast
function. This function will be used to check if anything is blocking the NPC’s line of sight, as it cannot see through walls.
local function raycast(npc, start, finish)
local parameters = RaycastParams.new()
parameters.FilterDescendantsInstances = {npc, getCharacters()}
parameters.FilterType = Enum.RaycastFilterType.Exclude
local raycast = workspace:Raycast(start, (finish - start), parameters)
if raycast then
return true
else
return false
end
end
- Now, create a
checkSight
function. We will use this to determine whether/not an object is being seen by the NPC.
local function checkSight(npc)
local detectable = {}
local characters = getCharacters()
local taggedObjects = CollectionService:GetTagged("Detectable")
for index, character in characters do
table.insert(detectable, character)
end
for index, object in taggedObjects do
table.insert(detectable, object)
end
for index, object in detectable do
if object:IsA("Model") then
object = object.PrimaryPart
end
local headPosition = npc.Head.Position
local objectPosition = object.Position
local headCFrame = npc.Head.CFrame
local direction = (objectPosition - headPosition).Unit
local lookVector = headCFrame.LookVector
local dotProduct = direction:Dot(lookVector)
local angle = math.deg(math.acos(dotProduct))
local distance = (headPosition - objectPosition).Magnitude
if angle > fieldOfView then
continue
end
if distance > viewRange then
continue
end
if raycast(npc, headPosition, objectPosition) then
return
end
if object:IsA("Model") then
objectLastDetected = object.Parent
else
objectLastDetected = object
end
return true
end
end
- Lastly, create a
for
loop that goes through every NPC and sets up detection every second with error prevention.
for index, npc in npcModels do
local humanoid = npc:FindFirstChildOfClass("Humanoid")
if not npc:IsA("Model") then
continue
end
if not humanoid then
continue
end
local function checking()
while task.wait(1) do
if checkSight(npc) then
-- Code for when an NPC sees a detectable object
else
-- Code for when an NPC does not see a detectable object
end
end
end
coroutine.wrap(checking)()
end
Resources
Dot Product - Wikipedia
Coroutine - Roblox Documentation
-- Formula for distance (in studs)
local distance = (pointA.Position - pointB.Position).Magnitude
Conclusion
Thank you for checking out my tutorial! If you have any questions, let me know in the comments down below.
If this tutorial helped you, please consider leaving a like!