How to know if an npc has access to a certain position?

  1. 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

  2. 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

  3. 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.

I already made the little health changed thing, I just want to know if a position is even reachable to begin with.

Raycast all directions, check which one has no obstacles in front and goes away from the player, then move the NPC in that direction.

I didn’t quite understand, could you elaborate more please?

oh now I get it, thanks for explaining

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

kinda works lmao

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.

As @umamidayo said, raycasting 360 times per update is terrible for performance.

You could just use pathfinding service

yep, sound a lot better than what I made, thanks man

1 Like

I made it just like you said:

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

works perfectly, thank you very much :pray:

1 Like

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

Good luck with your game.

alright, much appreciated man :metal:

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.