howdy fellow roblox devs! i’ve been trying to create a NPC suspicion system for a stealth game that i’m working on, lots of games have this for example, Roblox Entry Point and Hitman 1, 2, and 3. I’ve been thinking about it for some time and i can’t really think up a great way to like keep track of suspicion, moving the value of suspicion up while the player is in the view of the NPC, keeping it in the same position when the player leaves for a second, then gradually decreasing it after the player has not been in view for a while. here’s the script that i have:
local RunService = game:GetService("RunService")
local Players = game:GetService("Players")
local Workspace = game:GetService("Workspace")
local DebrisService = game:GetService("Debris")
local NPC = script.Parent
local head = NPC:WaitForChild("Head")
local rootPart = NPC:WaitForChild("HumanoidRootPart")
local detectionRange = 50 -- Adjust the detection range as needed
local fieldOfViewThreshold = 0.5 -- Adjust the field of view threshold as needed
local suspicionAmount = 0
local suspicionMeter = Instance.new("Part")
suspicionMeter.Size = Vector3.new(7.5, 0.25, 0.5)
suspicionMeter.BrickColor = BrickColor.new("Really black")
suspicionMeter.CanCollide = false
suspicionMeter.Position = rootPart.Position + rootPart.CFrame.UpVector * -2.875 + rootPart.CFrame.LookVector * 1.5
local susGui = Instance.new("SurfaceGui")
susGui.Face = Enum.NormalId.Top
susGui.Parent = suspicionMeter
local susFrame = Instance.new("Frame")
susFrame.Size = UDim2.new(1,0,.5,0)
susFrame.BackgroundColor3 = Color3.new(255,255,0.3)
susFrame.Parent = susGui
local susWeld = Instance.new("WeldConstraint")
susWeld.Part0 = suspicionMeter
susWeld.Part1 = rootPart
susWeld.Parent = suspicionMeter
suspicionMeter.Parent = NPC
local rayPartsFolder = Instance.new("Folder",workspace)
local playerCharacters = {}
Players.PlayerAdded:Connect(function(player)
player.CharacterAdded:Connect(function(char)
table.insert(playerCharacters, char)
char:WaitForChild("Humanoid").Died:Connect(function()
table.remove(playerCharacters, table.find(playerCharacters, char))
end)
end)
end)
function isInFieldOfView(npcHead, playerHead, npcForwardVector, threshold)
local npcToPlayer = (playerHead.Position - npcHead.Position).Unit
local dotProduct = npcToPlayer:Dot(npcForwardVector)
return dotProduct > threshold
end
RunService.Stepped:Connect(function(deltaTime)
local npcForwardVector = head.CFrame.LookVector
for _, char in pairs(playerCharacters) do
local pHead = char:FindFirstChild("Head")
if pHead then
if isInFieldOfView(head, pHead, npcForwardVector, fieldOfViewThreshold) then
local raycastParams = RaycastParams.new()
raycastParams.FilterDescendantsInstances = {NPC,workspace.RayParts}
raycastParams.FilterType = Enum.RaycastFilterType.Exclude
local raycastResult = Workspace:Raycast(head.Position, (pHead.Position - head.Position).Unit * detectionRange, raycastParams)
if raycastResult then
local distance = (head.Position - raycastResult.Position).Magnitude
local linePart = Instance.new("Part")
linePart.Anchored = true
linePart.CanCollide = false
linePart.Size = Vector3.new(0.1, 0.1, distance)
linePart.CFrame = CFrame.lookAt(head.Position, raycastResult.Position) * CFrame.new(0, 0, -distance / 2)
linePart.Transparency = 0.7
linePart.Parent = Workspace.RayParts
DebrisService:AddItem(linePart,10)
if raycastResult.Instance:FindFirstAncestor(char.Name) then
suspicionAmount = math.clamp(suspicionAmount + 0.01,0,1)
else
suspicionAmount = math.clamp(suspicionAmount - 0.01,0,1)
print(raycastResult.Instance)
end
end
--print(suspicionAmount)
susFrame.Size = UDim2.new(1,0,suspicionAmount,0)
end
end
end
end)
if you want to test it out you can just shove a server script into any rig and it should work. i mean the script works fine but obviously it’s not that great. does anyone have any suggestions? should i use deltatime? i really have no idea what the best way to do this is. thanks!