You can write your topic however you want, but you need to answer these questions:
What do you want to achieve?
I would like for the detection bar to increase ONLY when the player is in the NPC’s field of view but not behind a wall for example, hence why I had to combine dot product and ray casting. Plus any other improvements that you think I might need to add.
What is the issue?
Currently the detection bar does not work as intended, yes the NPC cannot detect you through walls but it’s only when standing right in front of them that the detection bar instantly maxes out and you’re spotted. I need it to increase and decrease based on the dot product value as long as the player is in sight (i.e. not behind a wall)
What solutions have you tried so far?
I have looked through a lot of devforum posts and found nothing, I changed the entire system several times but didn’t get the intended results.
To summarise, I am trying to create hitman-style stealth mechanics but I do not want an exact copy of course.
This is a local script currently in StarterCharacterScripts. The NPC is located in Workspace. The GUIs mentioned are in StarterGui.
local RunService = game:GetService("RunService")
local npc = workspace.NPC
local character = game.Players.LocalPlayer.Character
local characterIsInFov = false
local npcSight = .9
local characterIsInSight = false
local npcRange = 100
local TweenService = game:GetService("TweenService")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local detectedevent = ReplicatedStorage:WaitForChild("PlayerDetected")
local playerGui = game.Players.LocalPlayer.PlayerGui
local DetectionBar = playerGui:WaitForChild("DetectionGUI")
-- // Configure \\
local reactionTime = 1.8 --Once you're spotted, how long it will take for the NPC to take action. If you move out of their sight before this time runs out, nothing will happen
RunService.RenderStepped:Connect(function()
local npcToCharacter = (character.Head.Position - npc.Head.Position).Unit
local npcLook = npc.Head.CFrame.LookVector
local dotProduct = npcToCharacter:Dot(npcLook)
if dotProduct > npcSight then
--character is in field of view
characterIsInFov = true
else
-- character is not in field of view so we do not need to take any action
characterIsInFov = false
end
-- Now we can determine if the player is in the field of view or not. Now let's prevent the npc being able to see the player behind walls:
local raycastParams = RaycastParams.new()
raycastParams.FilterType = Enum.RaycastFilterType.Exclude
local list = {}
for i,v in pairs(workspace:GetDescendants()) do -- This will ignore all the chairs (you would have to set a custom state for all the chairs in your game)
if
v.Name == "Chair" or v.Name == "Part" -- It can be something simple like this!
then
table.insert(list, #(list) + 1, v)
end
end
raycastParams.FilterDescendantsInstances = list
raycastParams.IgnoreWater = true
local ray = workspace:Raycast(npc.PrimaryPart.Position, character.PrimaryPart.Position, raycastParams)
if ray and (ray.Instance.Position - npc.PrimaryPart.Position).Magnitude <= npcRange and characterIsInFov == true then -- In real life,
-- somebody can't see someone else from 1000 studs away even if it's a precise straight line! This is why we need a range.
characterIsInSight = true
DetectionBar.detectionFrame.detectionBar.Dot.Value = dotProduct
DetectionBar.detectionFrame.detectionBar:TweenSize(UDim2.new(1, 0, dotProduct*2, 0), Enum.EasingDirection.Out, Enum.EasingStyle.Linear, 0)
DetectionBar.detectionFrame.detectionBar.BackgroundColor3 = Color3.fromRGB(255, 0, 0)
wait(reactionTime)
if ray and (ray.Instance.Position - npc.PrimaryPart.Position).Magnitude <= npcRange and characterIsInFov == true then
--After a second check
detectedevent:FireServer(game.Players.LocalPlayer) --Tell the server that the player has been caught, either mark them as suspicious or start combat
end
else
characterIsInSight = false
end
--print("Character is in fov: " .. tostring(characterIsInFov), "Character is in sight: " .. tostring(characterIsInSight))
end)
If you’d like an example of what I have managed to create so far and what the issue is, I recorded a video of the system so far.
First of all this is not to be done every frame, discard the RenderStepped and run the code in while true loop which runs every 1/10th of a second (or even less; test with the value).
Second of all, you are not checking if the hit part is a descendant of a character or not; you just continue regardless of whether the hit part is a wall or not.
Third of all (oh god), after waiting for reactionTime (which is just stupid in a RenderStepped loop btw), you are reusing the last raycast rather than raycasting again.
Also as another optimization, once you realize that the player isn’t in your NPC’s FOV, you can just skip the rest of the calculation.
Maybe I should have mentioned that I’m new to scripting, sorry if you thought what I did was stupid or annoying. I tried many different things so it was bound to be a mess. I’ve taken care of your first point and put a while loop in there, and I added your version of the bar method.
How would I go about doing the second and third points? I just want to see what it should look like in case I mess it up again.
Yes, but however RenderStepped runs way too many times a second of so many calculations and will slow down the game significantly. If it must be ran so many times a second, I personally would recommend a while true loop with Heartbeat. RenderStepped should only be used for camera manipulation.
I tried it but you’re marked as suspicious before the bar can fill completely, I used the dot product result to calculate this originally so it’s never going to work with deltatime and a speed variable. Thanks for the idea though
This seems to be the best working solution for the bar. All I need now is the raycasting to be sorted out so that the bar increases when the dotproduct is 0.5 or more AND the ray casting says that you are not behind a wall or something like that (because dot product on its own doesnt care if you’re behind a wall or not)
The third point will automatically start working once you switch over to a while loop and as for the foruth one, you just have to perform the raycast onec again after waiting for reactionTime.
(also apologies if u were hurt or offended by smth i said, I was just tryna be factual and clear so ye mb)
Edit:- I got stuff switched up, for the second point just do:
if raycast.Instance.Parent:FindFirstChild("Humanoid") then
--// Do code
end
Or if you want NPCs to detect only players, then
local player = Players:GetPlayerFromCharacter(raycast.Instance.Parent)
if player then
--// Do code
end
As for the third, it will automatically start working once you switch over to a while loop.
Tried this, the bar is now a bit jumpy and doesn’t move seamlessly anymore and I am now detectable through walls but the “spotted” event doesn’t happen at all now.
And it’s all good don’t worry, it just needlessly came off as a bit rude and obnoxious.
If it’s really necessary to run code that many times a second, its recommended to use a Heartbeat loop. RenderStepped is a *very* bad idea for something like this and will drastically decrease the framerate of a game. As a general rule, RenderStepped should only be used when manipulating the camera.
Apparently, both Heartbeat and RenderStepped can both decrease FPS if performance-intensive code is placed in them. But I guess he doesn’t need Heartbeat for this.