Making a Frame always be on screen

I was trying to make a waypoint frame where it stays on-screen at all times, and I started using WorldToScreenPoint with the model’s primary part, but then it still gets all screwy. Is there any way I can fix this?
Here’s my code:
repeat point = camera:WorldToScreenPoint(CurrentTarget.PrimaryPart.Position) X = point.X if X > mouse.ViewSizeX -script.Parent.ImageLabel.Size.X.Offset/2 then X = mouse.ViewSizeX -script.Parent.ImageLabel.Size.X.Offset/2 elseif X < 0 then X = 0 end Y = point.Y if Y > mouse.ViewSizeY -script.Parent.ImageLabel.Size.Y.Offset/2 +36 then Y = mouse.ViewSizeY -script.Parent.ImageLabel.Size.Y.Offset/2 +36 elseif Y < 0 then Y = 0 end script.Parent.ImageLabel.Position = UDim2.new(0,X,0,Y) wait() until CurrentTarget == nil or PrevTarget ~= CurrentTarget

here’s a video of what I mean
https://gyazo.com/92034dd950ad26b282dcd6b6bd62c906
when I’m looking away, it jumps to the center of my screen, like it’s in the distance

2 Likes

Here’s an easier to read version of your code.

repeat 
	point = camera:WorldToScreenPoint(CurrentTarget.PrimaryPart.Position)
	X = point.X
	if X > mouse.ViewSizeX -script.Parent.ImageLabel.Size.X.Offset/2 then
		X = mouse.ViewSizeX -script.Parent.ImageLabel.Size.X.Offset/2
	elseif X < 0 then
		X = 0 
	end 
	Y = point.Y 
	if Y > mouse.ViewSizeY -script.Parent.ImageLabel.Size.Y.Offset/2 +36 then 
		Y = mouse.ViewSizeY -script.Parent.ImageLabel.Size.Y.Offset/2 +36 
	elseif Y < 0 then 
		Y = 0 
	end 
	script.Parent.ImageLabel.Position = UDim2.new(0,X,0,Y)
	wait() 
until 
	CurrentTarget == nil or PrevTarget ~= CurrentTarget

You can use the Z value of the Vector3 WorldToScreenPoint returns. It’s the depth from the camera. If this value is <0, then it’s behind the camera. You can simply hide the frame if it’s behind the camera, or you can do some math (which I cant help with) to determine the closest edge point and place it there.

1 Like

A)How do you make it block code, i still don’t know how
B)well i want it to be on screen constantly, so hiding it isn’t an option. I’ll try to find out how to do that, but i have no clue.

(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:

  1. Set the Y value to the top or the bottom so it doesn’t appear in the middle.
  2. 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
9 Likes

TheNexusAvenger kinda stole my thunder on this one with the god-tier solution. I’ll leave you with a youtube video by DutchDeveloper, I found it helpful for things like this, its easy to follow along with and can show you some cool features to add later on if you are interested. It teaches clamping the UI within a certain area as-well as adding custom features later on.

Video: https://www.youtube.com/watch?v=HUd2sBMrJ44

1 Like

thank you both so much, I’ll defientally give math.clamp and Dutch’s video a look-see!

Thanks for the improvements and the tutorial!