What do you want to achieve? I want to make an npc which retreats to a random position where the damaging source position doesn’t see it
What is the issue? Everything works, except the fact that the npc might choose a random position that’s blocked off/ not even unreachable and be exposed to enemy fire
What solutions have you tried so far? I tried to look in the dev forum for help, but couldn’t find anything that could assist me in my problem.
as you can see, the green block is where the npc runs away to, but it might be in unreachable places like that big gray cube where the npc tried to run away to because the random position is in the said cube.
I think you should make a function that when the humanoid helath is changed, it chooses a random position with a for loop and check if that block / position is available with attributes.
Try raycasting like that, also add checking what ray.Position is furthest from the player and move there. This is a simple little script I made without editor, so some typos might occur
local humanoid:Humanoid = -- your npc's humanoid
local directions = {
humanoid.Parent.HumanoidRootPart.Position + Vector3.new(1,0,0),
humanoid.Parent.HumanoidRootPart.Position + Vector3.new(-1,0,0),
humanoid.Parent.HumanoidRootPart.Position + Vector3.new(0,0,-1),
humanoid.Parent.HumanoidRootPart.Position + Vector3.new(0,0,1),
}
local rays = {}
local params = RaycastParams.new()
for _, i in humanoid.Parent:GetChildren() do
if i:IsA("BasePart") then
table.insert(params.FilterDescendantsInstances, i)
end
end
params.FilterType = Enum.RaycastFilterType.Exclude
for _, dir in directions do
local r = workspace:Raycast(humanoid.Parent.HumanoidRootPart.Position, dir)
table.insert(rays, r)
end
local longest = 0
for _, r in rays do
if r then
if r.Distance > longest then longest = r.Distance end
end
end
for _, r in rays do
if r.Distance == longest then
humanoid:MoveTo(r.Position)
end
end
Nein, I already solved it myself, using a for loop
for degree = 1,360 do
if not foundSpot then
local primCF = botModel.PrimaryPart.CFrame
local dir = (primCF * CFrame.Angles(0,math.rad(degree),0)).LookVector
local rayParams = RaycastParams.new()
rayParams.FilterType = Enum.RaycastFilterType.Exclude
rayParams.FilterDescendantsInstances = {enemyModel}
local result = workspace:Raycast(botModel.PrimaryPart.Position,dir * 100,rayParams)
if result then
local newDir = CFrame.lookAt(damagePos,result.Position).LookVector
local newResult = workspace:Raycast(damagePos,newDir * 100,rayParams)
if newResult ~= nil then
if newResult.Instance then
if newResult.Instance.Parent ~= botModel then
foundSpot = true
retreatPos = newResult.Position
res = newResult
end
else
foundSpot = true
retreatPos = newResult.Position
res = newResult
end
end
end
end
end
Although this might work, raycasting 360 times is not efficient for a single npc lol.
If you’re willing to change up some code, you can create move nodes around the map, which will act as reference points for movement. Once the npc is hurt, you can raycast from each node to the attacker and get the raycast of the closest node to the player without line of sight. As the npc continues to move to that closest node, you can raycast the npc’s line of sight to the attacker until line of sight breaks; that will be your cover point.
This way, you can reduce raycasting down to however many nodes there are in the map + the raycasts done while moving to cover, whether that would be 10 or 20; much better than 360.
function findHiddenSpots(lookAtPos,enemyModel)
local hiddenSpots = {}
for i,v in pairs(workspace.HideSpots:GetChildren()) do
local dir = CFrame.lookAt(v.Position,lookAtPos).LookVector
local rayParams = RaycastParams.new()
rayParams.FilterType = Enum.RaycastFilterType.Exclude
rayParams.FilterDescendantsInstances = {enemyModel}
local result = workspace:Raycast(v.Position,dir * 9999,rayParams)
if result ~= nil then
if result.Instance then
if result.Instance.Parent ~= enemyModel then
table.insert(hiddenSpots,v)
end
end
end
end
return hiddenSpots
end
function findClosestHidingSpot(spotsTable, position, enemyModel)
local closestElement
local closestDistance = math.huge
for _, element in ipairs(spotsTable) do
local distance = (element.Position - position).magnitude
if distance < closestDistance then
local enemyPos = enemyModel.PrimaryPart.Position
local dir = CFrame.lookAt(element.Position,enemyPos).LookVector
local rayParams = RaycastParams.new()
rayParams.FilterType = Enum.RaycastFilterType.Exclude
rayParams.FilterDescendantsInstances = {enemyModel}
local result = workspace:Raycast(element.Position,dir * 9999,rayParams)
if result ~= nil then
if result.Instance then
if result.Instance.Parent ~= enemyModel then
closestElement = element
closestDistance = distance
end
end
end
end
end
if closestElement then
return closestElement
end
end
Nice, glad it works. Just some micro optimizations you can do here:
Instead of this:
local dir = CFrame.lookAt(v.Position,lookAtPos).LookVector
You can do this:
local dir = (endPosition - startPosition)
This will be the exact distance between those two points, and it’s more optimal than the direction multiplied by 9999.
Another thing is to keep one instance of your RaycastParams as a global, as you will be re-using the RayParams, but changing the FilterDescendantsInstances more often;
local rayParams = RaycastParams.new()
rayParams.FilterType = Enum.RaycastFilterType.Exclude
function findHiddenSpots(lookAtPos,enemyModel)
local hiddenSpots = {}
for i,v in pairs(workspace.HideSpots:GetChildren()) do
local dir = lookAtPos - v.Position
rayParams.FilterDescendantsInstances = {enemyModel}
local result = workspace:Raycast(v.Position,dir,rayParams)
if result ~= nil then
if result.Instance then
if result.Instance.Parent ~= enemyModel then
table.insert(hiddenSpots,v)
end
end
end
end
return hiddenSpots
end