So first I am aware that these functions exist

https://developer.roblox.com/en-us/api-reference/function/Camera/WorldToViewportPoint

https://developer.roblox.com/en-us/api-reference/function/Camera/WorldToScreenPoint

I am making this as a learning experience

```
local Camera = workspace.CurrentCamera
local Frame = script.Parent
while true do
task.wait(0.1)
local ViewportSize = Camera.ViewportSize
local AspectRatio = ViewportSize.X/ViewportSize.Y
local VerticalFoV = math.rad(Camera.FieldOfView)
local HorizontalFoV = 2*math.atan( math.tan(VerticalFoV/2) * AspectRatio )
local RelativePosition = Camera.CFrame:Inverse() * workspace.Part.Position
local Opp = RelativePosition.Y
local Adj = RelativePosition.Z
local VerticalAngle = math.atan(Opp/Adj)
local HorizontalAngle = -math.atan(RelativePosition.X/Adj)
local H_Percentage = HorizontalAngle/(HorizontalFoV/2)
local Y_Percentage = VerticalAngle/(VerticalFoV/2)
Frame.Position = UDim2.fromOffset(
(H_Percentage * ViewportSize.X/2) + ViewportSize.X/2,
(Y_Percentage * ViewportSize.Y/2) + ViewportSize.Y/2
)
print(
math.round(math.deg(VerticalAngle)),
math.round(math.deg(HorizontalAngle)),
math.deg(VerticalFoV),math.deg(HorizontalFoV)
)
if math.abs(VerticalAngle) > VerticalFoV/2 or math.abs(HorizontalAngle) > HorizontalFoV/2 then
print("not on screen anymore")
end
end
```

Currently what it does:

(the green dot should stay on the red part)

My logic is that I get the angle from the camera look vector and get a 0->1 percentage value of angle/FieldOfView, then multiply that percentage by the screen viewport size to convert it into pixels.

I don’t see where I am going wrong with this. Here the place file if needed

3D To Screen Projection.rbxl (39.4 KB)

GUI Inset property is set to false on the GUI, and the anchor point is 0.5,0.5.