You don’t use CFrames for UI. You use Vector2s, UDim2s, and simple trigonometry.
First, to evenly distribute the buttons so that there is an equal amount of space between each of them, you just divide 360 by the number of buttons. That’s going to be the angle increment.
local buttonsAmount: number = 8
local angleIncrement: number = 360/buttonsAmount
for i = 0, buttonsAmount-1 do --start at zero so the first button will be on 0 degrees
local angle: number = math.rad(angleIncrement*i)
...
end
Next, we need to use the unit circle. If the radius was just one, the unit circle tells us that the position is just Vector2.new(math.cos(θ), math.sin(θ))
. This vector conveniently will always have a length of one so we can use it as the direction to get positions that are further away from the circle. Then we just need to multiply the radius to the direction to get the final position of the button.
for i = 0, buttonsAmount-1 do
local angle: number = math.rad(angleIncrement*i)
local x, y = math.cos(angle), math.sin(angle)
local position: Vector2 = Vector2.new(x, y)*radius
...
end
But to actually use it you need to do a lot more. The position we have right now is the displacement from the center of the circle. Some more math is involved to turn that Vector2 into an actually useable UDim2 position, but I’ll leave that in the demo file below for you to goof around in:
radialMenu.rbxl (49.8 KB)
Code inside the demo
local buttonsAmount: number = 9 --number of buttons to add
local angleOffset: number = -90 --makes the first button start at the top center of the circle
local sui = script.Parent
local buttonEx = script:WaitForChild('TextButton')
local main = sui:WaitForChild('CanvasGroup')
local frame = main:WaitForChild('Frame')
local stroke = frame:WaitForChild('UIStroke')
local absoluteRadius: number = stroke.Thickness*.5
local angleIncrement: number = 360/buttonsAmount
for i = 0, buttonsAmount-1 do
local angle: number = math.rad(angleIncrement*i + angleOffset)
local x, y: number = math.cos(angle), math.sin(angle)
--currently, the direction is in the interval [-1, 1] for both axes.
--we need to convert it into the interval [0, 1] for the UDim to work properly.
local x01, y01: number = (x+1)*.5, (y+1)*.5
--the position will use both scale and offset!
--scale is [0, 1] so we don't need to do anything. offset however, will depend on the thickness of the circle.
--the offset is to move the button into the actual circle part of the frame, since the circle is just a stroke outline.
local position: UDim2 = UDim2.new(x01, x*absoluteRadius, y01, y*absoluteRadius)
local new = buttonEx:Clone()
new.Position = position
new.Parent = frame
end