How to make a Radial UI

Using trigonometry you can get the mouse angle to use on the frames I developed this code:

local RunService = game:GetService("RunService")
local UserInputService = game:GetService("UserInputService")
local Players = game:GetService("Players")

local PI = math.pi
local TAU = 2*PI

local MOUSE_CLICK = Enum.UserInputType.MouseButton1

local GRAY = Color3.new(0.5, 0.5, 0.5)
local WHITE = Color3.new(1, 1, 1)

local OFFSET = 1/4 -- since angle 0 is pointing to the right, we can begin our origin 90 degrees counter clockwise so its perfectly up
-- we normalized offset so we have to use decimals (0, 1)
-- 1/4 is one quarter counter clockwise

local baseFrame = script.Parent
local radialLimit = baseFrame.AbsoluteSize.X/2 -- we assume the baseframe is the limit (forgot to divide by 2, whoopsies!)

local mouse = Players.LocalPlayer:GetMouse() -- to account for gui Inset offset (you couldve hard coded it but I like the abstraction)

local list = { -- going counter clockwise
	baseFrame.RadialLeft,
	baseFrame.RadialBottom,
	baseFrame.RadialRight
}

local selected
local checkConn
local clickConn

local function selectRadial(clicked)
	if not clicked then return end
	
	print(clicked)
end

local function onHover(hovered)
	if not hovered then return end
	
	hovered.ImageColor3 = GRAY
end

local function onHoverLeave(hovered)
	if not hovered then return end

	hovered.ImageColor3 = WHITE
end

local function activate() -- I would assume you would activate and unavtivate with like a keybind or someth[ing]
	
	checkConn = RunService.RenderStepped:Connect(function() 
		local center = baseFrame.AbsolutePosition + baseFrame.AbsoluteSize/2
		
		local direction = Vector2.new(mouse.X, mouse.Y) - center
		
		if direction.Magnitude <= radialLimit then -- checking if out of bounds
			local angle = math.atan2(-direction.Y, direction.X)/TAU -- we flip Y cause of how Gui is positioned
			-- we divide by TAU to normalize the units so its [0, 1]
			angle = (angle - OFFSET)%1
			
			local index = math.floor(angle*#list) + 1 
			-- this works because the amount we have in the list is 
			
			local pickedFrame = list[index]
			
			if selected ~= pickedFrame then -- its a new pick so we can hover
				onHoverLeave(selected) -- make sure to unhover the one we had before
				onHover(pickedFrame)
				
				selected = pickedFrame
			end
			
		elseif selected then
			onHoverLeave(selected)
			selected = nil
		end
	end)
	
	clickConn = UserInputService.InputBegan:Connect(function(inputType, isTyping)
		if isTyping or inputType.UserInputType ~= MOUSE_CLICK then return end
		
		selectRadial(selected)
	end)
	
end; activate()

local function unactivate()
	if checkConn then
		checkConn:Disconnect()
		clickConn:Disconnect()
		selected = nil
	end
end

end result


Radial Menu Scripted.rbxl (57.1 KB)

For this behaviour you might not want to use Image buttons at all, and also make sure there is no guiInset cause that screws up the direction its poiting

9 Likes