Creating a Radial Menu

Creating a Radial Menu

15 min

A radial menu is a style of menu often found in console games due to its ease of use with analog thumbstick controllers. In a radial menu, the selectable options are arranged in a ring. When the user pushes the analog stick, the option in the direction the user pushed will be selected.

This tutorial will cover how to implement a radial menu, with an emphasis on gamepads, although the same approach can be used for mouse/keyboard or mobile games.

Setting up Menu

Menus can be created manually or through code. This tutorial uses code so it can easily be put into any game:

Radial menus typically have two states: opened and closed. This code first defines positions and sizes for the main frame of the menu. With the current values, the open state of the menu will be centered on the screen and take up half of the screen’s width and height. You can adjust menuOpenPosition and menuOpenSize to adjust how much of the screen the menu takes up and where it is centered.

Next, the script creates a template for the menu items. In this case, a TextButton is used, as it is simple. You should change this to use a more complicated element that fits the style of your game and interface. Keep in mind the code will animate the menu opening by changing its size, so TextButton/TextScaled makes sure the text dynamically changes size with the button.

Finally, the code creates a ScreenGui and Frame. The Frame’s GuiObject/Size and GuiObject/Position are set to menuClosedSize and menuClosedPosition respectively, as the menu should be hidden initially.

Creating Buttons

After the menu screen and frame are set up, the script then automatically creates buttons arranged in a ring. There are several constants you can use to configure how many buttons appear in the ring and how they are oriented.

The newMenuItem function is used to create new menu items and to add these items to a table that will be used later when determining which item to select. The function takes three arguments: name indicates the name of the option (and is also used to label it in the GUI), angle sets where on the ring the option is centered, and range sets how wide the input arc is.


The function first creates a clone of the itemTemplate that was created earlier. It also sets the position of the button based on angle. The function also stores a 2D vector made from the input angle. This will be used later to compare with user input to figure out which item is selected.

for i = 1, NUM_OPTIONS do
	local angle = (360 / NUM_OPTIONS) * (i - 1)
	local name = "Option" .. i
	newMenuItem(name, angle, 360 / NUM_OPTIONS)

After the newMenuItem function is declared, the code then executes a loop to create the menu items. The constant NUM_OPTIONS determines how many menu options to create. The angle for each item is determined by dividing 360 (the number of degrees in a circle) by NUM_OPTIONS and then multiplying by the current option. The range is also determined by dividing 360 by NUM_OPTIONS. These values are all passed into newMenuItem to create the button.

Opening the Menu

In this tutorial the menu is toggled open and closed with the left bumper. ContextActionService can be used to bind this button to a custom function that opens and closes the menu.

-- The menu uses left bumper which the backpack also uses. Disabling backpack UI to prevent conflict
game.StarterGui:SetCoreGuiEnabled(Enum.CoreGuiType.Backpack, false)

local function toggleMenu(actionName, inputState)
	if inputState == Enum.UserInputState.Begin then
	elseif inputState == Enum.UserInputState.End then

ContextActionService:BindAction("ToggleMenu", toggleMenu, false, Enum.KeyCode.ButtonL1)

The default Roblox control script binds the bumpers to toggle between items in the backpack. In order to rebind this input to the radial menu, the backpack UI has to be disabled using StarterGui/SetCoreGuiEnabled. If you use a different input than the bumpers for your radial menu, you should be mindful of the Roblox defaults to prevent any conflicts.

The toggleMenu function is bound to the left bumper using ContextActionService/BindAction. toggleMenu checks inputState to see what state the bumper is in. If it is in the Begin state, then the user just started pressing the bumper and the menu needs to be opened. Otherwise, if it is in the End state, the menu needs to be closed.

local function openMenu()
	-- Store GuiNavigationEnabled and AutoSelectGuiEnabled and then set both to false
	wasGuiNavigationEnabled = GuiService.GuiNavigationEnabled		
	wasAutoSelectGuiEnabled = GuiService.AutoSelectGuiEnabled
	GuiService.GuiNavigationEnabled = false
	GuiService.AutoSelectGuiEnabled = false
	-- Bind onThumbstickMoved function
	ContextActionService:BindAction("RadialMenu", onThumbstickMoved, false, Enum.KeyCode.Thumbstick1)		
	-- Make sure frame is visible and play opening animation
	menuFrame.Visible = true
	menuFrame:TweenSizeAndPosition(menuOpenSize, menuOpenPosition, Enum.EasingDirection.Out, Enum.EasingStyle.Quart, .5, true)
	menuOpen = true	

The openMenu function needs to do several things. First, it needs to disable GuiService/GuiNavigationEnabled and GuiService/AutoSelectGuiEnabled and store the values so that they can be restored later. These settings work well for other menu types, but are not well suited for radial menus.

The angle between the vectors is then compared to half the item’s range. If it is less than half the item’s range, that means the thumbstick vector is somewhere in the arc defined for that item. If this is the case, getButtonFromVector returns the item’s button.

If getButtonFromVector returns a button, onThumbstickMoved sets the GuiService/SelectedObject to that button. This highlights the button so the user knows it has been selected.

At the end of onThumbstickMoved, if the thumbstick was not pushed past the deadzone, the SelectedObject is set to nil to clear the selection.

Closing the Menu

When the menu closes, the code needs to call a function based on the option that was selected (if any).

-- Function to call when item is selected. This is where you should put your custom code
-- to implement your menu
local function onMenuSelect(option)
	print(option, "selected")

local function closeMenu()
	-- Restore GuiNavigationEnabled and AutoSelectGuiEnabled
	GuiService.GuiNavigationEnabled	= wasGuiNavigationEnabled
	GuiService.AutoSelectGuiEnabled = wasAutoSelectGuiEnabled	

	-- Unbind onThumbstickMoved function		
	if GuiService.SelectedObject then
		-- If there is a selection when the menu closed, this is the option the user wanted
	-- Clear selected object and play closing animation
	GuiService.SelectedObject = nil
	menuFrame:TweenSizeAndPosition(menuClosedSize, menuClosedPosition, Enum.EasingDirection.Out, Enum.EasingStyle.Quart, .4, true,
			-- Callback function at end of the animation. If the user hasn't reopened the menu hide it
			if not menuOpen then
				menuFrame.Visible = false
	menuOpen = false

The closeMenu function first restores GuiNavigationEnabled and AutoSelectGuiEnabled in case the rest of the game relies on those settings. It also unbinds onThumbstickMoved so that the thumbstick can again be used for other purposes. Then, if there is a SelectedObject, the function calls onMenuSelect, passing in the SelectedObject as an argument.

onMenuSelect is where you will want to add any custom code. Based on the button that was passed in, you can write code for whichever action you need to happen when the button is selected.

After onMenuSelect is called, closeMenu then clears the current selection and plays an animation to hide the menu.

Complete Code

Below is all of the code to implement a radial menu as seen above. The code should be in a LocalScript and placed either in StarterPlayerScripts or StarterGui.

  • menu
  • console
  • gamepad