I got asked to write this script for someone and figured that other people might potentially want it if they run into the issue. It isn’t perfect code, but it works for the situation and usecase. Free to use without attribution.
--!strict
local module = {}
function module.GUIPositionToWorldPosition(GuiBase:GuiBase2d, Offset:Vector2): CFrame | nil
Offset = Vector2.new(math.clamp(Offset.X+GuiBase.Anchor.X, 0, 1), math.clamp(Offset.Y+GuiBase.Anchor.X, 0, 1))
local SurfaceGUI:SurfaceGui? = GuiBase:FindFirstAncestorOfClass("SurfaceGui")
local BasePart:BasePart? = GuiBase:FindFirstAncestorOfClass("Part") or GuiBase:FindFirstAncestorOfClass("UnionOperation") or GuiBase:FindFirstAncestorOfClass("MeshPart")
if not (SurfaceGUI and BasePart) then
warn(not SurfaceGUI and not BasePart and "There is no SurfaceGUI or BasePart parented to this GuiBase" or
not SurfaceGUI and "There is not SurfaceGUI parented to this GuiBase." or
not BasePart and "There is not SurfaceGUI parented to this GuiBase.")
return nil
end
local Normal = Vector3.FromNormalId(SurfaceGUI.Face)
local Size = BasePart.Size
local BasePartOffset = BasePart.Size*Normal/2
local SizeOffset = Vector3.new(Normal.X == 0 and -Size.X/2 or 0,Normal.Y == 0 and -Size.Y/2 or 0,Normal.Z == 0 and Size.Z/2 or 0)
return BasePart.CFrame*CFrame.new(BasePartOffset+SizeOffset)*CFrame.new(Vector3.new(-(GuiBase.AbsolutePosition.Y+GuiBase.AbsolutePosition.Y*Offset.Y)/SurfaceGUI.PixelsPerStud,0,-(GuiBase.AbsolutePosition.X+GuiBase.AbsolutePosition.X*Offset.Y)/SurfaceGUI.PixelsPerStud))
end
return module