Making a part "follow" a surface gui object

Hello, I am trying to make a part’s position the same as the position of a surface GUI object., like this.

image

I really have no idea how to calculate this, and anchor points don’t make it any easier. If anyone has knowhow, please share.

I ‘might’ have a solution. Step by step
First we get the location of the box by it top left corner.

local gen = workspace.Generator.Display; 
local pos = gen.Position + Vector3.new(-(gen.Size/2).X, (gen.Size/2).Y, -(gen.Size/2).Z); 
print(pos)


Second we will get the amount to add to the Vector3 so it will be center of the Frame.
In order to get the added position we will add absoluteposition and absolutesize(divided by 2) and divided those by pixels per stud.
Next adding them(This is depend on orientation of your surfacegui) because Y is up for Vector we will instead minus Y.

local gen = workspace.Generator.Display; 
local udimpos2 = (gen.SurfaceGui.Frame.AbsolutePosition+gen.SurfaceGui.Frame.AbsoluteSize/2)/gen.SurfaceGui.PixelsPerStud; 
local pos = gen.Position + Vector3.new(-(gen.Size/2).X, (gen.Size/2).Y, -(gen.Size/2).Z) + Vector3.new(0, -udimpos2.Y, udimpos2.X); 
print(pos)

If this doesn’t explain it I’ll try to come up with another method or find someone/post that could help with it. Since you asked how to find position this is my solution.

Edit: My method works if the part doesn’t have rotation to them, I only have sold the idea.

1 Like

Sure let’s try figure this out :smiley: Here is my test setup that I used:
kuva

Essentially we can calculate the positions and sizes by using the “Board part” as a reference as well as checking SurfaceGui’s PixelsPerStud ratio. Then its just a matter of offsetting :smiley:


This is the code I used, to achieve this. I’ve only included info regarding a single face so you are free to edit it however you like. I also did not account for SurfaceGui being fixed size nor did I add rotations but both of those are fairly easy additions :smiley:


local SurfaceGui = script.Parent.Board.SurfaceGui
local Part = script.Parent.Part

local SurfaceInfo = {
	[Enum.NormalId.Front] = {
		Up = Enum.NormalId.Top,
		Left = Enum.NormalId.Right,
		Right = Enum.NormalId.Left,
		Down = Enum.NormalId.Bottom
	},
	[Enum.NormalId.Back] = {
		Up = Enum.NormalId.Top,
		Left = Enum.NormalId.Left,
		Right = Enum.NormalId.Right,
		Down = Enum.NormalId.Bottom
	},
	[Enum.NormalId.Top] = {
		Up = Enum.NormalId.Left,
		Left = Enum.NormalId.Back,
		Right = Enum.NormalId.Front,
		Down = Enum.NormalId.Right
	},
	[Enum.NormalId.Bottom] = {
		Up = Enum.NormalId.Right,
		Left = Enum.NormalId.Back,
		Right = Enum.NormalId.Front,
		Down = Enum.NormalId.Left
	},
	[Enum.NormalId.Left] = {
		Up = Enum.NormalId.Top,
		Left = Enum.NormalId.Front,
		Right = Enum.NormalId.Back,
		Down = Enum.NormalId.Bottom
	},
	[Enum.NormalId.Right] = {
		Up = Enum.NormalId.Top,
		Left = Enum.NormalId.Back,
		Right = Enum.NormalId.Front,
		Down = Enum.NormalId.Bottom
	}
}

function CreatePart(element: GuiBase2d)
	local ReferencePart: BasePart = SurfaceGui.Adornee or SurfaceGui.Parent
	
	local info = SurfaceInfo[SurfaceGui.Face]
	local Direction = Vector3.FromNormalId(SurfaceGui.Face)
	local Up = Vector3.FromNormalId(info.Up)
	local Left = Vector3.FromNormalId(info.Left)
	local Right = Vector3.FromNormalId(info.Right)
	local Down = Vector3.FromNormalId(info.Down)
	
	local Size = ReferencePart.Size
	
	local NewPart = Part:Clone()
	local ElementCorner = element.AbsolutePosition / SurfaceGui.PixelsPerStud
	local ElementSize = element.AbsoluteSize / SurfaceGui.PixelsPerStud
	
	local NewSize = Left * ElementSize.X + Up * ElementSize.Y + Direction
	local PositiveSize = Vector3.new(
		math.sign(NewSize.X) * NewSize.X,
		math.sign(NewSize.Y) * NewSize.Y,
		math.sign(NewSize.Z) * NewSize.Z
	)
	NewPart.Size = PositiveSize
	local CF = CFrame.new(
		(Direction + Up + Left) * Size/2 -- Move to "top left"
			-- Offset by element corner position
			+ Right * ElementCorner.X
			+ Down * ElementCorner.Y
			-- Size offset
			+ Right * ElementSize.X/2
			+ Down * ElementSize.Y/2
			+ Direction/2 -- I've set depth size to 1 stud
	) * CFrame.fromAxisAngle(-Direction, math.rad(element.AbsoluteRotation))
	
	NewPart.CFrame = ReferencePart.CFrame:ToWorldSpace(CF)
	
	NewPart.Parent = script.Parent
end

for i,v in SurfaceGui:GetChildren() do
	if v:IsA("GuiBase2d") then
		CreatePart(v)
	end
end

Hopefully this gets you started :smiley: If you have any additional questions, feel free to ask!

EDIT: Cleaned up the code a little to make it more readable.
EDIT2: Added rest of the faces and rotation support

kuva

5 Likes

This works well, however, the part seems to move on the Z axis instead of the X axis.

Basically, when turning the ui object left and right, the part goes back and forth

This is because the direction depends on which face the surface gui is on :smiley: Those need to be taken into account.

One way you could do this, is get the percentage of the frame’s absolute position within the surface gui using inverselerp. then have the part pointtoobjectspace of the surfacegui part and then lerp between the size of the part with the percentage you got before.

How would I take faces into account with this code?

local udimpos2 = (UI.Frame.AbsolutePosition+UI.Frame.AbsoluteSize/2)/UI.PixelsPerStud; 
local pos = surfacePart.Position + Vector3.new(-(surfacePart.Size/2).X, (surfacePart.Size/2).Y, -(surfacePart.Size/2).Z) + Vector3.new(0, -udimpos2.Y, udimpos2.X);

Here, I wrote the code for you. This should work with static objects (not moving) however if you got the ui constantly moving the part may fall behind.

local board = Instance.new'Part'
board.Size = Vector3.new(10, 10, 0.1)
board.Anchored = true
board.Parent = workspace

local surfaceGui = Instance.new'SurfaceGui'
surfaceGui.Face = Enum.NormalId.Back
surfaceGui.Adornee = board
surfaceGui.Parent = board

local frame = Instance.new'Frame'
frame.Size = UDim2.new(0.3, 0, 0.3, 0)
frame.AnchorPoint = Vector2.new(0.5, 0.5)
frame.Position = UDim2.new(0.5, 0, 0.5, 0)
frame.BackgroundColor3 = Color3.new(0, 0, 0)
frame.Parent = surfaceGui

local part = Instance.new'Part'
part.Size = Vector3.new(1, 1, 1)
part.Anchored = true
part.Parent = script

local rs = game:GetService'RunService'

local function inverseLerp(a, b, t)
    return (t - a) / (b - a)
end

local function lerp(a, b, t)
    return a + (b - a) * t
end

local function get2Dto3D_CF(part)
    local boardSizeX = board.Size.X
    local boardSizeY = board.Size.Y

    local framePosPercentageX = inverseLerp(0, surfaceGui.AbsoluteSize.X, frame.AbsolutePosition.X + frame.AbsoluteSize.X / 2)
    local framePosPercentageY = inverseLerp(0, surfaceGui.AbsoluteSize.Y, frame.AbsolutePosition.Y + frame.AbsoluteSize.Y / 2)

    local partPosX = lerp(-boardSizeX / 2, boardSizeX / 2, framePosPercentageX)
    local partPosY = -lerp(-boardSizeY / 2, boardSizeY / 2, framePosPercentageY)

    local zOffset = 1

    return part.CFrame:ToObjectSpace(board.CFrame) * CFrame.new(partPosX, partPosY, zOffset)
end

frame.Position = UDim2.new(math.sin(tick() * 2) * 0.5 + 0.5, 0, math.cos(tick() * 2) * 0.5 + 0.5, 0)

part.CFrame = get2Dto3D_CF(part)

image

1 Like

Have you looked at the example I posted for you?

I am going to be trying it! Thank you for the help

1 Like

Worked like a charm! Thank you very much!

1 Like

No problem! It was fun figuring it out :smiley: Good luck on your project!

Hey, I just noticed it seems to not be orientated correctly on different faces

image
With front as the face


With bottom as the face

With right as the face

TLDR; I am wondering if you know how to make it so the part faces in the same direction as the GUI object, like in the first picture

The current version of the code makes it so if SurfaceGui is on Left face, then the created part will also be facing in a way where the outside face is Left. Are you hoping that the created parts are always facing in the Front face?

I would like the part to rotate so that the images always stays like in the first picture, without changing the face

Which face is the picture on? Front?

Yes

WAAAAAAAAAAAAAAAAAAAAAA

Right, well here is a modified version where the front face is always facing outwards


local SurfaceGui = script.Parent.Board.SurfaceGui
local Part = script.Parent.Part

local SurfaceInfo = {
	[Enum.NormalId.Front] = {
		Up = Enum.NormalId.Top,
		Left = Enum.NormalId.Right,
		Right = Enum.NormalId.Left,
		Down = Enum.NormalId.Bottom
	},
	[Enum.NormalId.Back] = {
		Up = Enum.NormalId.Top,
		Left = Enum.NormalId.Left,
		Right = Enum.NormalId.Right,
		Down = Enum.NormalId.Bottom
	},
	[Enum.NormalId.Top] = {
		Up = Enum.NormalId.Left,
		Left = Enum.NormalId.Back,
		Right = Enum.NormalId.Front,
		Down = Enum.NormalId.Right
	},
	[Enum.NormalId.Bottom] = {
		Up = Enum.NormalId.Right,
		Left = Enum.NormalId.Back,
		Right = Enum.NormalId.Front,
		Down = Enum.NormalId.Left
	},
	[Enum.NormalId.Left] = {
		Up = Enum.NormalId.Top,
		Left = Enum.NormalId.Front,
		Right = Enum.NormalId.Back,
		Down = Enum.NormalId.Bottom
	},
	[Enum.NormalId.Right] = {
		Up = Enum.NormalId.Top,
		Left = Enum.NormalId.Back,
		Right = Enum.NormalId.Front,
		Down = Enum.NormalId.Bottom
	}
}

function CreatePart(element: GuiBase2d)
	local ReferencePart: BasePart = SurfaceGui.Adornee or SurfaceGui.Parent
	
	local info = SurfaceInfo[SurfaceGui.Face]
	local Direction = Vector3.FromNormalId(SurfaceGui.Face)
	local Up = Vector3.FromNormalId(info.Up)
	local Left = Vector3.FromNormalId(info.Left)
	local Right = Vector3.FromNormalId(info.Right)
	local Down = Vector3.FromNormalId(info.Down)
	
	local Size = ReferencePart.Size
	
	local NewPart = Part:Clone()
	local ElementCorner = element.AbsolutePosition / SurfaceGui.PixelsPerStud
	local ElementSize = element.AbsoluteSize / SurfaceGui.PixelsPerStud
	
	local DepthSize = 1
	
	local NewSize = Vector3.new(ElementSize.X, ElementSize.Y, DepthSize)
	NewPart.Size = NewSize
	
	local Position = (Direction + Up + Left) * Size/2 -- Move to "top left"
			-- Offset by element corner position
			+ Right * (ElementCorner.X + ElementSize.X/2)
			+ Down * (ElementCorner.Y + ElementSize.Y/2)
			-- Size offset
			+ (Direction * DepthSize)/2
	
	local CF = CFrame.fromMatrix(Position, Left, Up, -Direction) * CFrame.Angles(0,0,math.rad(element.AbsoluteRotation))
	
	NewPart.CFrame = ReferencePart.CFrame:ToWorldSpace(CF)
	NewPart.Parent = script.Parent
end

for _,face in Enum.NormalId:GetEnumItems() do
	SurfaceGui.Face = face
	for i,v in SurfaceGui:GetChildren() do
		if v:IsA("GuiBase2d") then
			CreatePart(v, Part)
			task.wait(1)
		end
	end
end

Hopefully that works out for you :smiley:

1 Like

Worked like a charm! Thank you very much for all the help you provided me : D

1 Like

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