How to make a Radial UI

Hello, I would like to create a Radial UI like in Counter Blox. I want to make one that has 3 selections in a circle like this:

image

Example of counter blox:

ezgif-5-1fe4c70048

I’ve been looking at existing posts and tutorials but I just can’t seem to understand how to do it.

Most / all of the ones I find involve telling me to use an image UI and make sure it’s placed correctly with no overlap but I’m trying to do a selection of 3 options, not 4 therefore I can’t evenly split it like that.

I would be grateful for any support whether it’s just pointing me in the right place to look or an example.

5 Likes

Just make some images and rotate them?

1 Like

I’m sorry if I wasn’t clear enough in my initial post but the frames overlap when I do that. This makes it bug out when you hover over the overlapping area so this option isn’t a good fit for me.

Example:
image_2024-07-02_000705960

Did you perhaps mean something else?

2 Likes

I think it would be very hard to do on Roblox (I’m not good at UI so that’s probably why I think that) so I think you would be better of using something else to make the UI such as InkScape or another vector art creator.

Though my approach to do something like that on Roblox Studio would to create a circle as normal and then add lines that match the color of the background on top of the circle so it divides it into radial pieces. I’m not sure if you can use Boolean operations on UI but if you could, you could subtract those lines from the circle to divide it if you wanted to go for a cleaner approach.

1 Like

you could try making 1 image label with the whole picture then get the angle of the mouse from the middle of the image and distance, determine which button is being hovered and then apply a highlight overlay

1 Like

10/10 feature request very useful

1 Like

can you rotate UI buttons?

so you would have one image and then make the buttons separate instead of making them image buttons


(sorry for the imperfect squares)

You would have 2 buttons that do the same thing so it wouldn’t matter wich sub-button was clicked.

1 Like

You’re just doing it wrong then. Here is how it looks for me:

image

Here is the explorer view:
image

Each image is parented to a frame of size {1,0},{1,0} and each image is of size {0.5,0},{0.5,0} located at the top left corner of the frame. The frames are rotated 90 * index degrees so, first frame is rotated 0, second 90, third 180 and forth 270. Then each textlabel is parented to each image, scaled properly and then rotated in the opposite direction to the frame.

2 Likes

What about making each “slice” of the circle seperate and using .MouseHovered to detect when the mouse is over it

1 Like

yes, but I think the issue was when the mouse hovers over the overlap. Even if all the slices are separate the image will still be square. So its “hit box” will also be square.

3 Likes

mb i forgot the hitbox isnt the actual image

2 Likes

Does this still work with 3 buttons instead of 4?

2 Likes

Yeah, I just preferred to make a 4 one because it’s more easier to make in a photo editor.

2 Likes

Oh oh waittt I got it now…

@HiddenIsANinja

Basically for such a system you have to use a custom system, basically for this you need to first get the angle of the mouse. To do this just do:

local difference = mousePosition - selector.AbsolutePosition + selector.AbsoluteSize/2
local angle = math.atan2(difference.Y,differnece.X)

After that use the difference variable to see if the mouse is within selector.AbsoluteSize.Magnitude/2 after which you can just do whatever you want.

4 Likes

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

5 Likes

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