(Started work before Fm_Trick posted; this post contains a full solution.)
The root cause of the problem is the fact that the X and Y positions when the part is behind the camera are valid, but the Z value is being ignored in the point.
To start, code blocks on Discourse can be done simply by using the following:
```
--Code
```
To clean up your code to be easy to put into a LocalScript and run it, I created this:
local FRAME_SIZE = 100
local TOP_BAR_HEIGHT = 36
local Camera = game:GetService("Workspace").CurrentCamera
local RenderStepped = game:GetService("RunService").RenderStepped
--Create a target.
local CurrentTarget = Instance.new("Part")
CurrentTarget.Anchored = true
CurrentTarget.Size = Vector3.new(4,4,4)
CurrentTarget.Position = Vector3.new(0,5,0)
CurrentTarget.Parent = game:GetService("Workspace")
PrevTarget = CurrentTarget
--Create a ScreenGui and frame for the marker.
local ScreenGui = Instance.new("ScreenGui")
ScreenGui.Parent = script.Parent
local Frame = Instance.new("Frame")
Frame.BackgroundTransparency = 0.5
Frame.Size = UDim2.new(0,FRAME_SIZE,0,FRAME_SIZE)
Frame.AnchorPoint = Vector2.new(0.5,0.5)
Frame.Parent = ScreenGui
--Run the loop for setting the position.
repeat
--Get the point and clamp the values.
local Point = Camera:WorldToScreenPoint(CurrentTarget.Position)
local ViewportSize = Camera.ViewportSize
local X = math.clamp(Point.X,FRAME_SIZE/2,ViewportSize.X - (FRAME_SIZE/2))
local Y = math.clamp(Point.Y,FRAME_SIZE/2,ViewportSize.Y - (FRAME_SIZE/2) - TOP_BAR_HEIGHT)
--Set the marker position.
Frame.Position = UDim2.new(0,X,0,Y)
RenderStepped:Wait()
until CurrentTarget == nil or PrevTarget ~= CurrentTarget
I did make a few changes, mainly:
- Moving to RenderStepped to allow it to be updated with the framerate.
- Use
math.clamp
for clamping the values.
- Used local values so another part of the code getting and setting
X
, Y, and
point` don’t have problems.
To use the negative Z value, you need to do two things:
- Set the Y value to the top or the bottom so it doesn’t appear in the middle.
- Invert the X and Y positions.
The first is relatively obvious, unless you have some knowledge of lenses in Physics. If you think of the object as being behind a lens and the projection of it being where you want to set the marker, the image is inverted. Quick example from MS paint:
The repeat
block can be replaced with the following:
repeat
--Calculate the max and min values.
local Point = Camera:WorldToScreenPoint(CurrentTarget.Position)
local X,Y = Point.X,Point.Y
local ViewportSize = Camera.ViewportSize
local MinX,MaxX = FRAME_SIZE/2,ViewportSize.X - (FRAME_SIZE/2)
local MinY,MaxY = FRAME_SIZE/2,ViewportSize.Y - (FRAME_SIZE/2) - TOP_BAR_HEIGHT
--Correct the values if the Z is negative (target is behind the camera).
if Point.Z < 0 then
--Invert the positions.
X = ViewportSize.X - X
Y = ViewportSize.Y - Y
--Correct the position.
local PercentageY = Y/ViewportSize.Y
if PercentageY < 0.5 then
Y = MinY
else
Y = MaxY
end
end
--Set the marker position.
Frame.Position = UDim2.new(0,math.clamp(X,MinX,MaxX),0,math.clamp(Y,MinY,MaxY))
RenderStepped:Wait()
until CurrentTarget == nil or PrevTarget ~= CurrentTarget