Radial menu doesn't work

Hi there, I’m currently trying to make a radial menu for my game using an old tutorial from the Dev Wiki, which has been removed and managed to retrieve it using archive.org, here is the link: Creating a Radial Menu (archive.org)
The original tutorial was meant to work with controllers so I had to readapt it to work with PC (and eventually mobile), which is totally possible since it’s mentioned in the article itself. As you can see I made a few changes, such as adding an offset so that the BuildMenu() function would take into account the AnchorPoint of the buttons of the menu.

The problem is the following: for some reason the checkButton() function, whose job is to return a button from the menu based on the mouse X,Y position, keeps returning the last button of the menu, regardless of the mouse position, completely ignoring the others. I’m not sure why this is happening, math isn’t my strongest suit. I believe the math is correct, but it’s also true that I edited the formulas in BuildMenu() so that the AnchorPoint would offset the buttons correctly, so maybe that’s what’s causing the issue?

local Service = require(game:GetService("ReplicatedStorage").Utils.Modules.Services)

-- UI ELEMENTS --
local IconsFolder = script:WaitForChild("Icons")
local Menu = script.Parent:WaitForChild("Menu")
local MenuIcons = Menu:WaitForChild("Icons")
local Current = script.Parent:WaitForChild("Current")
local buttonHolderTemplate = script:WaitForChild("ButtonTemplate")


-- MENU VARIABLES --
local menuOpen = false
local ANGLE_OFFSET = 90
local RADIUS = 0.55
local menuItems = {}


-- FUNCTIONS --
local function BuildMenu(unlockedElements)
	for i,element in pairs(unlockedElements) do
		local Icon = IconsFolder:FindFirstChild(element):Clone()
		local buttonInfo = {}
		buttonInfo.Icon = Icon

		local offsetX = (0.5 - Icon.AnchorPoint.X) * Icon.Size.X.Offset
		local offsetY = (0.5 - Icon.AnchorPoint.Y) * Icon.Size.Y.Offset

		local angle = (360 / #unlockedElements) * (i - 1)
		local angleRadians = math.rad(ANGLE_OFFSET + angle)

		buttonInfo.Position = UDim2.new(
			0.5 + RADIUS * math.cos(angleRadians) - offsetX,
			0,
			0.5 - RADIUS * math.sin(angleRadians) - offsetY,
			0)

		buttonInfo.Vector = Vector2.new(math.cos(angleRadians), math.sin(angleRadians))
		buttonInfo.Range = 360 / #unlockedElements

		--menu icon visual
		Icon.Transparency = 0
		Icon.Parent = MenuIcons

		table.insert(menuItems,buttonInfo)
	end
end


local function Init()
	local elements = {"Water","Fire","Air","Ore","Bloom","Surge"}
	BuildMenu(elements)
end

Init()

local con
local function checkButton(mousePos :Vector2)

	for i = 1, #menuItems, 1 do
		local item = menuItems[i]
		local dotProduct = mousePos.X * item.Vector.X + mousePos.Y * item.Vector.Y
		local angle = math.acos(dotProduct / mousePos.Magnitude) print(angle, math.rad(item.Range) / 2)

		if angle <= math.rad(item.Range) / 2 then
			return item.Icon
		end
	end
	return nil

end

Service.CAS:BindAction("openInfusions",function(actionName, inputState, _inputObj)
	if inputState == Enum.UserInputState.Begin then

		Current:TweenPosition(UDim2.fromScale(0.5,0.5),Enum.EasingDirection.In,Enum.EasingStyle.Linear,0.15,true,function()
			Current.Visible = false
			for i,button in pairs (menuItems) do
				local icon : Frame = button.Icon
				icon.Visible = true
				icon:TweenPosition(button.Position,Enum.EasingDirection.In,Enum.EasingStyle.Linear,0.1)
			end

			con = Service.RunS.Heartbeat:Connect(function(dt)
				local button = checkButton(Service.UIS:GetMouseLocation())
				--print(button)
			end)

		end)
	end


	if (inputState == Enum.UserInputState.End or inputState == Enum.UserInputState.Cancel) then

		for i,button in pairs (menuItems) do
			local icon : Frame = button.Icon

			icon:TweenPosition(UDim2.fromScale(0.5,0.5),Enum.EasingDirection.In,Enum.EasingStyle.Linear,0.1,false,function()
				icon.Visible = false
			end)
		end

		task.wait(0.1)

		Current.Visible = true
		Current:TweenPosition(UDim2.fromScale(0.5,0.95),Enum.EasingDirection.In,Enum.EasingStyle.Linear,0.15,true)

	end

end,true,Enum.KeyCode.Q)

Thanks in advance for the help!

3 Likes

Is the angle and math.rad(item.Range) / 2 always the same?

1 Like

item.Range is always the same yes, it’s based on the “size” of the button.


Angle isn’t always the same, no

Shouldn’t you combine Item.Angle and Item.Range because Range is only the size and if I understand correctly angle is the angle of the mouse?

To be honest - I have no idea.
As I said, I followed the code provided in the tutorial, trying to understand what I could. I suspected as well that specific part of the code was a bit weird, especially because it says this:


Not sure why it uses half the item’s range, it’s kinda weird and doesn’t make sense to me.

local function getButtonFromVector(vector)
	for i = 1, #menuItems do
		local item = menuItems[i]
		local dotProduct = vector.X * item.Vector.X + vector.Y * item.Vector.Y
		local angle = math.acos(dotProduct / vector.magnitude)
		if angle <= math.rad(item.Range) / 2 then
			return item.Button
		end
	end
	return nil
end

This is exactly how it’s done in the tutorial. Yes, angle is the angle of the mouse obtained using UserInputService:GetMouseLocation() and then the dot product with the item’s vector obtained in the BuildMenu() function.

You’re not supposed to be using the mouse location as the function parameter. The thumbstick position (which is what the tutorial uses) isn’t actually a position, it’s a Vector2 normal that represents what direction the thumbstick is pointing.

OHhhhhh, that explains a lot. I assumed it was the same type of data… so what now? Is there any way I can fix it?
You say “direction the thumbstick is pointing”: direction starting from the center of the screen?

1 Like

Try doing something similar to this (all variables should be Vector2)
local mouseVector = (mousePosition - centerOfRadialMenu).Unit

Apologies for the late reply!
So, it seems to work. It finally returns something other than the last button… the problem now is that it doesn’t return the correct button, the position seems to be completely wrong?

con = Service.RunS.Heartbeat:Connect(function(dt)
				local mousePos = Service.UIS:GetMouseLocation()
				local mouseVector = (mousePos - Menu.AbsolutePosition).Unit
				local button = checkButton(mouseVector)
				
				if button then
					print(button)
				end
			end)

This is how I edited the code based on your suggestion.
This is what happens (check console):
Gyazo GIF

The white square is the center of the menu by the way.

I actually guessed this could happen, the thumbstick position is inverted in the Y axis. Multiplying mouseVector by Vector2.new(1,-1) will fix the issue. (Also sorry for the late reply)

1 Like

https://gyazo.com/af0522d2334fdd67e3f170a600607b9b

Almost there - is the X axys inverted too? It gets some buttons right, but there still seem to be some sort of offset applied?

Code:

local mouseVector = (mousePos - Menu.AbsolutePosition).Unit * Vector2.new(1,-1)

Thank you so much for everything btw, I have absolutely no clue about controllers API so this is all new to me, assumed it shared pretty much the same logic with a mouse. Couldn’t find anything about them on the documentation either.

1 Like

I think the buttons are misplaced, try not using offsetX and offsetY when calculation the button positions. (Looks like some other stuff is wrong too but fixing this first could help)

Tried removing them, still not returning the correct button :\ I don’t think that was part of the problem, it’s just to take into account the anchor point as I said.

Should I upload the file place so you can take a better look?

Sure, that would make this a lot easier.

1 Like

radial_menu.rbxl (85.7 KB)
There you go!

I’m getting the error Utils is not a valid member of ReplicatedStorage "ReplicatedStorage"

Ooh, sorry. I completely forgot to keep the Service module.
Here is the fixed version:
radial_menu.rbxl (86.9 KB)
Can’t testplay it for some reason, it doesn’t load when i testplay, so I can’t see if it errors :skull: studio is so broken recently.

1 Like

You turned AutoLoadsCharacter or something like that off.

2 Likes

Also now there’s a new problem Infinite yield possible on 'ReplicatedStorage:WaitForChild("Utils")'

1 Like

Yep, you right lol. And now it’s warning because I forgot to comment out some stuff in the services module.
Fixed again, super sorry. Should work fine now:
radial_menu.rbxl (86.9 KB)

1 Like