So I’ve working on a system that makes an NPC find and move to cover relative to the nearest player’s position. The moving to cover works fine but finding cover is difficult. I would like to know what would be the most effective way to locate the nearest cover from the NPC. I would also like to know how would I go about allowing the NPC to use cover standing or crouched based on height. (Note for a cover to be valid the NPC must be able to peek off it and get a clear line of sight to the player). Any help is appreciated.
You can use something like a CollectionService to tag different kind of cover for example
The Yellow outline cover has a “Low” tag
The Green outline cover has a “High” tag
You can also tag the object that can be used as cover with the “cover” tag
This way the NPC can differentiate the map part and the cover part
Better yet. Because now the part has been tagged you can making so the NPC will be more likely to hide in a full cover which offers more protection
Thank for the reply but I don’t really want to manually tag all possible cover. Is there any way for the NPC to determine objects that can be used for cover in front of it without predetermining the possible cover?
you can use Vector3.Magnitude
to check if the object is high enough to be considered half or full cover
I made a really basic find cover system a while back, its certainly not the most efficient way but it meant I didn’t have to go and tag stuff all over my game.
Basically the way it works is it would generate points in a circle around the NPC. It would then raycast from those points to wherever the danger is (e.g. enemy player or another NPC), if the raycast failed it meant there was no line of sight and therefore it was a ‘place of cover’.
If it couldn’t find any places for cover, it would increase the radius of the points in the circle around the npc and try again. Raycasts are cheap so this isn’t that bad. I would increase the radius about 4 studs each time.
You of course set an upper limit, say a radius of 10 studs. You can also get better accuracy if instead of using 1 raycast to check if its cover, if you instead use a few in more of a grid formation (or maybe use blockcast or spherecast) so that you can be more sure it covers the entire body.
You can also use raycasts to simply check if the cover is short or tall if you want them to crouch or stand.
This worked really well for me, hope it does for you too (:
Thanks for your reply and sorry for the really delayed response but could you give me an explanation of how the grid system would work? I tried creating a semi circle and raycasting toward the player but my results weren’t accurate and sometimes completely wrong when the player stands at certain locations.
Thanks for the reply I will keep in mind to use this when it comes to determining the height of the cover. Although Right now I am basically trying to create procedurally generated cover points that an NPC can use.
I am not sure what you mean.
It is not a grid system.
Here is some pseudo code that might help?
>generate points in a circle around character
>raycast from each point to the target to hide from
>if raycast is nil, it means there is line of sight, not a good place to hide
>if raycast is not nil, it means it is hidden from the target, return this point
You can generate points in a circle around a character by just
local character = ...
local nPoints = 8
local radius = 4
for i = 1, nPoints do
local angle = (math.pi*2 / nPoints) * i -- math.pi is 180 degrees in radians, so 2 pi is 360 degrees
local point = character.PrimaryPart.CFrame * CFrame.Angles(0, angle, 0) * CFrame.new(0, 0, radius)
end
Oh sorry I meant raycasting in a grid formation like you said earlier, I just wanted to know more on that. And also like I said when I tried raycasting in a circle around the character and towards the player the result was not accurate.
For raycasting in a grid, I mean like blockcast WorldRoot | Documentation - Roblox Creator Hub
What do you mean not accurate? Can I see your code.
I saw blog that actually uses raycast in a grid formation to find cover points so i assumed you meant that. Anyways here is the code:
local Players = game:GetService("Players")
local NoRayCast = 180
local humRoot
local maxDistance = 100
local Cover = {}
function FindNearestPlr(NPC)
local nearestPlayer, nearestDistance, nearestChar
for _, player in pairs(Players:GetPlayers()) do
local character = player.Character
local distance = player:DistanceFromCharacter(humRoot.Position)
if not character or
distance > maxDistance or
(nearestDistance and distance >= nearestDistance)
then
continue
end
nearestDistance = distance
nearestPlayer = player
nearestChar = player.Character
end
print("The nearest player is", nearestPlayer)
FindCover(NPC, nearestChar)
return nearestChar
end
function FindCover(NPC, ClosestPlr)
if ClosestPlr then
local Config = NPC.Config
local NPC_CFrame = NPC.HumanoidRootPart.CFrame
local Radius = 100
local RayPrams = RaycastParams.new()
RayPrams.FilterType = Enum.RaycastFilterType.Exclude
RayPrams.FilterDescendantsInstances = {game.Workspace.RayCastObj:GetChildren(), game.Workspace.SWAT_NPC:GetChildren()}
for r = 0,1, 1/NoRayCast do
local part = Instance.new("Part", workspace.RayCastObj)
part.Anchored = true
part.Size = Vector3.new(1, 1, 1)
part.BrickColor = BrickColor.new("Really red")
part.CFrame = NPC_CFrame * CFrame.Angles(0, math.rad(r*180 - 90), 0) * CFrame.new(0,0, Radius)
local Result = workspace:Raycast(part.Position, ClosestPlr.HumanoidRootPart.Position - part.Position, RayPrams)
if Result and Result.Instance:IsDescendantOf(ClosestPlr) == false then
table.insert(Cover, Result.Instance)
end
end
print(Cover)
end
return Cover
end
for i, NPC in ipairs(game.Workspace.SWAT_NPC:GetChildren()) do
wait(5)
humRoot = NPC.HumanoidRootPart
FindNearestPlr(NPC)
end
The Code creates a Circle from the NPCs position and a raycast is created at each part that points towards the player. When I say that the result is not accurate i meant that sometimes when there is cover that have different rotations or when the player is too close to cover the raycast will return a cover that will not cover the NPC. The table that is printed Contains all the raycast results. (The creation of parts are just for visualization btw.)
So there are a few odd things going on in your code but I figure its easier for me to just give you a basic working example and let you take your time and look at that, feel free to send me any questions.
In a localscript:
local RunService = game:GetService("RunService")
local character = workspace:WaitForChild("Askavix") -- change this
local target = workspace:WaitForChild("Rig") -- change this
-- this is just for showing where the cover position is
local coverBeacon = Instance.new("Part")
coverBeacon.Size = Vector3.new(1, 1, 1)
coverBeacon.Anchored = true
coverBeacon.Color = Color3.fromRGB(49, 183, 255)
coverBeacon.CanCollide = false
coverBeacon.Name = "CoverBeacon"
coverBeacon.Parent = workspace
local function findCover(hider, seeker, raycastParams)
-- hider is the person trying to find cover
-- seeker is the target to find cover from
local hiderRoot = hider:FindFirstChild("HumanoidRootPart")
local seekerRoot = seeker:FindFirstChild("HumanoidRootPart")
if not hiderRoot then
return
end
if not seekerRoot then
return
end
local radius = 4 -- how far we expand the search each time
local nPoints = 8 -- how many points we will check in a circle
local maxRadiusSteps = 5 -- how many times it will increase the radius before giving up
local radiusStep = 0 -- counter for the amount of times we have expanded the search
local coverPosition
repeat
radiusStep += 1
for i = 1, nPoints do
local angle = math.pi*2/nPoints * i
local cf = hiderRoot.CFrame * CFrame.Angles(0, angle, 0) * CFrame.new(0, 0, radius*radiusStep)
local origin = cf.Position
local direction = (seekerRoot.Position - origin)
if not raycastParams then
raycastParams = RaycastParams.new()
raycastParams.FilterType = Enum.RaycastFilterType.Exclude
raycastParams.FilterDescendantsInstances = {hider, seeker, coverBeacon}
end
local raycastResult = workspace:Raycast(origin, direction, raycastParams)
-- using blockcast would be better for a player shape, but you would want to make sure it isnt clipping into the ground at all, otherwise you will get inaccurate results
if raycastResult then -- meaning we dont have line of sight, so its a suitable cover position
coverPosition = origin
break
end
end
until coverPosition or radiusStep > maxRadiusSteps
return coverPosition
end
-- visualize the cover
RunService.Heartbeat:Connect(function()
local coverPosition = findCover(character, target)
if coverPosition then
coverBeacon.Position = coverPosition
coverBeacon.Transparency = 0
else
coverBeacon.Transparency = 1
end
end)