Hey there, recently I’ve been working on a customization UI for my game, and I wan’t lines(thin frames), to go from the body part to it’s correct ui viewport. I know how to rotate the line toward the corresponding ui viewport, but I have 2 questions
How do you find the distance from one UI element to the other?
In my case, I wan’t to find the distance between:
How do you make the line(Frame) Start at the middle of a part in the viewport?
Whenever I play around with viewports and try making lines point to other objects, when I make the length of the frame bigger, it extends on both sides, making the line off center from the center of the part
Typically you’d do (element1.AbsolutePosition - element2.AbsolutePosition).Magnitude but since you’re working with viewports and parts, it makes obtaining element1 a bit more complicated but it’s still doable. First, you’d have to specify the viewport’s CurrentCamera, then on the viewport’s camera, call camera:WorldToViewportPoint(), passing in the part’s position which will return a Vector3. Then from the returned Vector3, construct a Vector2, and use this Vector2 in place of element1.AbsolutePosition.
For the sake of simplicity, I’ll hereon refer to vec1 as the constructed Vector2 from the camera:WorldToViewportPoint call earlier, and vec2 as element.AbsolutePosition.
Now to get the angle at which the frame should be oriented, you first need to subtract the goal from the origin (vec2 - vec1) to obtain the direction from vec1 to vec2, which is the same as vec2’s position relative to vec1. We’ll refer to this third vector as vec3. To obtain the rotation from vec3 we can call math.atan2(vec3.Y, vec3.X) (X and Y axes are switched intentionally) which will give you a rotation in radians, bear in mind GuiObject.Rotation is in degrees so you would convert the rotation to degrees by calling math.deg on that number.
Doing that, your line should now span between the centre of the part, and the top-left corner of the specified frame.
The line would start in the middle of the part. It would end at the top-left corner of your UI element though. To make it end at the centre of the UI element, you’d just add the element’s AbsoluteSize / 2 to vec2 from earlier.
Hey, @7z99, almost everything worked out.
I followed your steps, and it really helped me. I’m an intermediate scripter, so I don’t know how I didn’t know that Magnitude works for UI elements, too.
Slight issue, though.
This is my code, but the lines appear off from the part in the viewport:
local viewport = script.Parent
local size = viewport:WaitForChild("Camera").ViewportSize
local cam = viewport:WaitForChild("Camera")
local function fixAspect(xCoord)
local viewport = viewport.AbsoluteSize
local viewportAspect = viewport.Y / viewport.X
local cameraAspect = size.X / size.Y
local aspectModification = viewportAspect / cameraAspect
xCoord -= 0.5
xCoord *= aspectModification
xCoord += 0.5
if not child:IsA("Model") then return end
cam.CFrame = CFrame.new(Vector3.new(0, 0, 30), child.PrimaryPart.Position)
for i, v in ipairs(child:GetChildren()) do
if child.Name == "HumanoidRootPart" then return end
if v:IsA("Humanoid") then return end
local newLine = viewport.lineTemplate
newLine = newLine:Clone()
newLine.Name = v.Name
newLine.Visible = true
newLine.BackgroundTransparency = 0
newLine.Parent = viewport
local matchingViewport = viewport.Parent:FindFirstChild(v.Name)
local P = newLine.AbsolutePosition
local O = matchingViewport.AbsolutePosition
local X = math.abs(O.X - P.X)
local Y = math.abs(O.Y - P.Y)
local theta = math.deg(math.atan2(Y, X))
newLine.Rotation = theta
local vector, inViewport = cam:WorldToViewportPoint(v.Position)
local vec2 = Vector2.new(vector.X, vector.Y)
--print(vector, inViewport) -- debugging
newLine.Position = UDim2.fromScale(fixAspect(vector.X) / size.X, vector.Y / size.Y)
local length = (vec2 - matchingViewport.AbsolutePosition).Magnitude
newLine.Size = UDim2.fromOffset(3, length)
local pos = vec2 + matchingViewport.AbsoluteSize / 2
Note: I did the camera:WorldToViewportPoint() and the atan2 stuff before this post, so let me know if I did something wrong there
First thing is that when calling :WorldToViewportPoint on your code using a viewport frame, it actually gives you a position in scale instead of offset, you need to convert this to offset by multiplying each axis from the returned vector by the respective viewport AbsoluteSize axis.
2- You don’t need to do math.abs here, it will probably mess up instead of make it work properly actually, math.atan2 will return the correct angle and does take into account negatives and positives.
3- You need to add the viewport frame’s AbsolutePosition to get the proper point once you convert it to pixels.
All in all your code will look something like this:
local armScreenPosition = camera:WorldToViewportPoint(leftArm.Position) -- position that the centre of the body part is found (in my case I'm using just the arm)
armScreenPosition = Vector2.new(position.X * viewport.AbsoluteSize.X, position.Y * viewport.AbsoluteSize.Y) -- convert to offset
armScreenPosition += viewport.AbsolutePosition -- add the viewport's absolute position
local armLineConnectorCentre = armLineConnector.AbsolutePosition + armLineConnector.AbsoluteSize / 2 -- centre of the frame the arm should be connected to
local direction = (armLineConnectorCentre - armScreenPosition)
local distance = (direction).Magnitude :: Vector2
local centre = (armLineConnectorCentre + armScreenPosition) / 2 -- the point in the middle of the frame and the arm in pixels
-- finally, update the line:
local angle = math.atan2(direction.Y, direction.X)
line.Rotation = math.deg(angle)
line.Size = UDim2.fromOffset(distance, 3)
line.Position = UDim2.fromOffset(centre.X, centre.Y)
Ensure that your line’s AnchorPoint is 0.5, 0.5 btw^
I might’ve missed something again since it’s nearly 9 am and I haven’t slept, so if anything’s unclear I’ll get back to you