How to generate a position near the player, but outside its FOV

I’m trying to generate a random position somewhere near the player. The position has to be within a certain radius of the player. However, I want the position to be generated outside the player’s FOV. This position must be generated on the server. I already have the player’s camera FOV and CFrame.

This is my current code:

local angle = math.random() * 2 * math.pi
	
local random_distance = math.random(MIN_DIST, MAX_DIST)
		
local cframe = CFrame.new(center) * CFrame.Angles(0, angle, 0) * CFrame.new(0,0, -rndDist)

This generates a random position around the player, but it doesn’t disregard the possibility of the position being generated within the player’s FOV.

Here’s an illustration of what I mean:

Any help is greatly appreciated! :star_struck:

What I would do is take the code you have, then use WorldToViewportPoint to check if the point is in their FOV. If so, find a new point, check it, and so on and so forth.

local function generate_point()
	local angle = math.random() * 2 * math.pi
	local random_distance = math.random(MIN_DIST, MAX_DIST)
	local cframe = CFrame.new(center) * CFrame.Angles(0, angle, 0) * CFrame.new(0,0, -rndDist)
	
	local vector, onScreen = camera:WorldToViewportPoint(cframe.Position)--if you are doing this on the server, you will need to call a remotefunction to do this
	
	if onScreen then
        task.wait() --you may get unlucky and keep getting points in the FOV. If this happens, having a wait will ensure that the script doesn't time out since this *technically* is a loop.
		generate_point()
	else
		return cframe
	end
end

local random_point = generate_point()
4 Likes

I don’t think that this would be good because then I would have to send requests to the client every time the generated position is within the player’s FOV, but it might work if nothing else does :smile:

Why are you doing it on the server to begin with? No matter what you do, you are going to have to do some form of math on the client if you want an accurate check of if a part is in their FOV.

try using dot product and do some maths to compare it to the fox and then use magnitude so stuff doesnt spawn too far away

Because I’m creating an NPC that can teleport, but outside the player’s FOV.

You can change the angle to be one that excludes a certain degrees forward. E.g.

local FOV_ANGLE = 80
local HALF_ANGLE = Math.floor(FOV_ANGLE / 2)
local rng = Random.new()
local angle = rng:NextInteger(HALF_ANGLE, 360 - HALF_ANGLE)
local randomDistance = rng:NextInteger(MIN_DIST, MAX_DIST)
local cframe = playerCFrame * CFrame.Angles(0, math.rad(angle), 0) * CFrame.new(0,0, -rndDist)

The problem with using the characters CFrame is that if the game isn’t locked in first person, then the camera could be pointed somewhere different than where the character is facing.

Is your game single-player or multi-player? If it is single-player, you can just do the teleport on the client. If it is multi-player, you can do the calculations on the client and then fire a RemoteEvent to actually set the NPC’s position on the server.

If that’s the case you need to network the rotation of the camera to the server and then do the same program except using the camera’s CFrame or sentCFrame. The rootPart is the HumanoidRootPart of the character, but it can be any reference position.

local cframe = CFrame.new(rootPart.Position) * (sentCFrame - sentCFrame.Position) * CFrame.Angles(0, math.rad(angle), 0) * CFrame.new(0,0, -rndDist)

Right. I just feel like they should just do the calculations on the client and fire a RemoteEvent to the server to actually set the NPC’s position, rather than getting the camera CFrame from the client and then doing the calculations on the server. This way, they can make use of camera:WorldToViewportPoint.

So exploiters can tp the NPC whenever they want? Seems like a fundamentally bad idea.

Your method still requires the server to get the camera CFrame from the client, which can be just as easily manipulated by exploiters.

It only uses the rotation not the position, so it’ll still spawn the npcs around the player.

Still seems like a hacky method. A RemoteFunction that checks if a part is on the screen using camera:WorldToViewportPoint would still (in my opinion) be the best, despite the creator shooting down that idea originally.

  1. worldtoscreenpoint calculates if something is in the frustum which is unnessescarily specific for npcs that just need to spawn behind their back
  2. spamming worldtoscreenpoint to check randomly generated positions is a waste of resources as its significantly more expensive to do frustum checks than a dot product for example
  3. the positions can be exploited because they can only be checked on the client
1 Like

My game is supposed to be multiplayer

The targeted player occasionally sends some information to the server script through a remote event. The information contains the camera FOV and CFrame. When the NPC wants to teleport, it’ll wait for the remote event to fire. Once fired it will try to generate a random position outside the targeted player’s FOV. This code sort of works, but it’s not very reliable.

local function AngleBetween(vectorA, vectorB)
	return math.acos(math.clamp(vectorA:Dot(vectorB), -1, 1))
end

function GeneratePosition(FOV, cameraCFrame)
	local randomAngle = math.random() * 2 * math.pi
	local randomDistance = math.random(50,100)

	local cframe = CFrame.new(character.HumanoidRootPart.Position) * CFrame.Angles(0, rndAngle, 0) * CFrame.new(0,0, -dist)
	
	local lookForward = camera.CFrame.LookVector
	local lookToPoint = (cframe.Position - cameraCFrame.Position).Unit
	
	local angle = AngleBetween(lookForward, lookToPoint)

    local fovCheck = math.rad(FOV)/2

	if math.abs(angle) <= fovCheck then
		task.wait()
		return GeneratePosition(FOV, cameraCFrame)
	else
		return cframe.Position
	end
end

I would prefer not to make the function loop until it finds a position outside the FOV, but rather limit where the random positions can be generated.

here is a demo project
OutsideFOV.rbxl (34.6 KB)


this is what it looks like


and this is the script

local character = script.Parent
local fov = math.rad(workspace.CurrentCamera.FieldOfView)

while true do
	task.wait()
	local characterCFrame = character:GetPivot()
	
	local angle = math.random() * (math.pi * 2 - fov) - math.pi / 2 + fov / 2
	local distance = 10 * math.sqrt(math.random())
	local randomPosition = Vector3.new(math.cos(angle), 0, math.sin(angle)) * distance
	local position = characterCFrame.Position + characterCFrame:VectorToWorldSpace(randomPosition)
	
	local part = Instance.new("Part")
	part.Anchored = true
	part.CanCollide = false
	part.CanQuery = false
	part.CanTouch = false
	part.Position = position
	part.Size = Vector3.new(1, 1, 1)
	part.Parent = workspace
end
2 Likes

This only works if the player is in first person. I need the position to always be generated around the player while also being generated behind the camera. Anyways, thank you for your help, I appreciate it :smile: