camera:WorldToScreenPoint behaving weird

Goal: I am trying to have a gui show over a part using world to screen point. It cant be a billboard because I plan to do something else with it after that a billboard would not allow me to do.

Problem: When the part goes off my screen, the GUI should also go off my screen, which it does sort of. However, as I get further away from the block, it reappears at the center of my screen opposite the part. I cant seem to figure out why.

Code:

local camera = workspace.CurrentCamera
local worldPoint = workspace.Target.Position
while wait() do
	local vector = camera:WorldToScreenPoint(worldPoint)
	
	script.Parent.ImageLabel.Position = UDim2.new(0,vector.X,0,vector.Y)
end

Example of Issue: Classic Baseplate - Roblox Studio (gyazo.com)

Update: So I now understand why it is behaving like this, but I still dont know how to eliminate this behavior without just hiding the UI when the part is behind me.

1 Like

Check the Z coordinate and if it’s negative then hide the gui

That would work for this, but it would be problematic for what im adding. I was going to clamp the values of the frame’s position to make it appear around the border of the screen when the part was off the screen. This would show the player what direction they were facing in relation to the part. If I hide the UI when the part is behind them, this wont work.

WorldToScreenPoint returns 2 values, the screen space vector, and a bool that tells you if the 3d space position is on the screen. You could check if OnScreen is false and then do the clamping math for when the part is off screen

local vector, onScreen = camera:WorldToScreenPoint(worldPoint)

if onScreen then
    script.Parent.ImageLabel.Position = UDim2.new(0,vector.X,0,vector.Y)
else
    -- clamp to edge
end

It would probably have to be a little bit more complex than just clamping the values

2 Likes

That is a viable option, but what would that math entail? Now that I think about it, it wouldnt be clamping at all anymore.

I would do a ray-AABB intersection from the middle of the screen like this:

image

  • red dot is WorldToScreenPoint position
  • black rect is the screen
  • blue dot is what you would need to compute

There are lots of resources online for line-AABB intersection code, this video for example or this article (but drop the check on the “arrow end” of the line), or just google “2d ray aabb intersection”

This behavior is intended. Think about it, if you dont have the z axis anymore (depth), how can you determine if its infront or behind? This code simply checks which pixel the position of that object collides with the camera viewport. In order to accomplish that effect you’re going for, its going to require a little bit more math so I’ll get back to you when I get it working.

I believe this works, however im on mobile so you’ll have to test it for yourself:

local vector, onScreen = camera:WorldToViewportPoint(worldPoint)
if vector.Z <= 0 then
    vector *= -1
end
local viewportSize = workspace.CurrentCamera.ViewportSize
vector.X = math.clamp(vector.X, 0, viewportSize.X)
vector.Y = math.clamp(vector.Y, 0, viewportSize.Y)
script.Parent.ImageLabel.Position = UDim2.new(0, vector.X, 0, vector.Y)
end

note: I HIGHLY suggest you run this in a BindToRenderStep function with a RenderPriority that runs AFTER the camera has been CFramed, or Enum.RenderPriority.Camera.Value + 1 to remove the lagging effect visible in the gifs you posted.

Edit: There’s a great tutorial on exactly what you need by @DutchDeveloper
You can see it at this link: ScreentoViewPoint Markers

1 Like