More specifically, a good way of determining if a player is in the AI’s line of sight.
I cannot think of a good solution. If I were to use raycasting, what if the ray hits a small part that technically isn’t obstructing the AI"s vision to the player? e.g, a chair. Sure, I could use multiple rays, but I don’t see how that’d be a liable solution.
Essentially I’d love for the AI to detect players within 45 degrees of its facing direction, so it’s more realistic, just not really sure right now how I could implement something that works well, also taking into account if the player is hidden behind a wall, in that case the AI obviously wouldn’t be able to see them.
Keep raycasting and if anything is on the way, and on the left there’s a dead end too, for example go right if there isn’t anything, however you have to write lots of lines to just detect if there’s an object left,right,forward.
Your best bet is to use dot product, As seen here
It allows you to set an “angle radius” which is a cone where the npc can see, I do not recommend using raycast as it’d only allow the npc to see in certain directions.
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
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
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.Blacklist
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" -- It can be something simple like this!
then
table.insert(list, table.getn(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 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
else
characterIsInSight = false
end
print("Character is in fov: " .. tostring(characterIsInFov), "Character is in sight: " .. tostring(characterIsInSight))
end)
Using the dot product approach seems sensible, so that you can at least check if the target is within the AI’s view range. Then you can raycast to see if that view is obstructed or not. Raycasts are generally expensive, but doing 1 per step or renderstep should be ok in terms of performance.
You can always choose to ignore the chair when raycasting. And/Or you can raycast towards a random direction within the AI’s view on each update. This way, the AI has a chance of looking around that small part on every update.
local RunService = game:GetService("RunService")
local Players = game:GetService("Players")
local NPC = workspace.NPC
local Head = NPC:WaitForChild("Head")
local function viewIsObstructed(direction)
-- Do some raycasting with Head.Position and direction
-- You can also randomly offset the direction within the AI's view range
-- to increase the chance of looking around small parts
end
local function getVisiblePlayers()
local visiblePlayers = {}
for _, Player in pairs(Players:GetPlayers()) do
if Player.Character then
local PlayerHead = Player.Character:FindFirstChild("Head")
if PlayerHead then
local direction = (PlayerHead.Position - Head.Position).Unit
local dotProduct = direction:Dot(Head.CFrame.LookVector)
-- Where 0.5 is a magic number that represents the range of view
if dotProduct > 0.5 and not viewIsObstructed(direction) then
table.insert(visiblePlayers, Player)
end
end
end
end
return visiblePlayers
end