Questions about scripting with UI elements

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:
    Screen Shot 2023-02-23 at 5.55.31 PM

  • 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

This is what I want to achieve:
Background

Thanks for helping if you can!

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.

4 Likes

Hey, thanks for the long post! It really helped me learn!

2 Likes

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
	return xCoord
end


viewport.ChildAdded:Connect(function(child)
	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
		
		print(v.Name)
		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
	end

end)

Note: I did the camera:WorldToViewportPoint() and the atan2 stuff before this post, so let me know if I did something wrong there

Sorry for the late response.

I missed a couple points in my post.

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:

game:GetService('RunService').RenderStepped:Connect(function()
	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)
end)

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

1 Like

Works so well! If I may ask, how did you learn these things?

Sometimes trial and error but a lot of the time it’s just putting fragments of information from previous projects or posts I’ve seen together.

1 Like

Thanks for letting me know! Now I know I’m doing the same things as others lol

1 Like

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.