Hello,

I'm working on a new game and in it I know I want to have a pie menu. The game is for a touch screen, so you'll hit a button and then more options will appear equally spaced around your finger, kind of like this:

I'm not into code yet as I'm still working on other things, but I am starting to think about what the best approach would be. Ideas? I had two thoughts,

I could refresh my sine/cosine math, and use them to calculate the position of the sub menu objects based on a radius from the main object. That's probably the way to go?

Or I could have the main object that has child containers that are the width of the radius. The pivot point of the containers to the far left of the container, and aligned with the center of the main object. Then the submenu object would be anchored to the far right of the container. Then I could rotate each container x degrees. The problem with this approach would be that then the submenu object would also be rotated (I think) and I'd like the label to still be orientated normal. Then maybe if I just set the rotation to *-1 of the rotation of it's parent all will be well?

in Engine

I would definately go with the sinus/cosinus approach. Especially since you want circular buttons (so no need to actually rotate the buttons themselves).

You can brush up on how to do it or just grab another tutorial online. Not Godot specific but something like this will help you out:

But there are also a lot of similiar tutorials online.

by (14 points)

I know the question is a bit old, but thought I would contribute some code to anyone else looking for radial menus like I was. Be warned mine is very rough and could use a lot of improvements, but it works and I think is a good starting place at least.

Usage
First create a container node, the base one that doesn't actually position anything. Next add a script to it and copy what I have below:

Warning: I made some tweaks to this (removed extra unused code, added comments) without testing so it may not run immediately.

``````extends Container

export var button_radius = 100 #in godot position units
export var radial_width = 50 #in godot position units

# Called when the node enters the scene tree for the first time.
place_buttons()

#Repositions the buttons
func place_buttons():
var buttons = get_children()

#Stop before we cause problems when no buttons are available
if buttons.size() == 0:
return

#Amount to change the angle for each button
var angle_offset = (2*PI)/buttons.size() #in degrees

var angle = 0 #in radians
for btn in buttons:
#calculate the x and y positions for the button at that angle

#Note: A bit confused but somehow godot corrects the sign on it's own so that isn't needed.

#set button's position
#>we want to center the element on the circle.
#>to do this we need to offset the calculated x and y respectively by half the height and width
var corner_pos = Vector2(x, -y)-(btn.get_size()/2) #Screen coordinates so calculated y must be negated
btn.set_position(corner_pos)

angle += angle_offset

#utility function for adding buttons and recalculating their positions
#TODO: Should probably just use a signal to run place_button on any tree change
place_buttons()
``````

Result
The result isn't viewable in the editor, but if you run the project it will position any buttons in the container so they are centered and spread evenly on an invisible line at the configured radius. Screenshot below:

by (30 points)

No need to mess around with sin/cos. Vectors can do the job for you. All this:

``````    var x = cos(angle)*button_radius
var corner_pos = Vector2(x, -y)-(btn.get_size()/2) #Screen coordinates so calculated y must be negated
btn.set_position(corner_pos)
angle += angle_offset
``````

Can be replaced with this:

``````    btn.rect_position = Vector2(button_radius, 0).rotated(angle)
angle += angle_offset
``````

Note that you can also make this a tool script if you want to see the result in the editor.

Ah thanks, that's good to know. Took 20-30 minutes dredging up trigonometry memory to solve that.

Though your code is not completely correct for what I was wanting. I think for a radial menu you would want the buttons centered on the circle from the given radius. Basically I just replaced my trig calcs with the vector then used that for the centering formula

``````for btn in buttons:
#calculates the buttons location on the circle

#set button's position
#>we want to center the element on the circle.
#>to do this we need to offset the calculated x and y respectively by half the height and width
btn.rect_position = circle_pos-(btn.get_size()/2)

angle += angle_offset
``````

I haven't gotten to a tutorial for making tools yet. Once I do that and solve another issue I'll update my answer. Of course there are other improvements that could be made, but that should make a nice basic version of a radial container.

Thanks for the Feedback!

Issue with the button-based pie menus is that when the text grows, the alignment goes all wobbly like so:

So I tried to fix it. The following is me trying to get that to work (and failing, kinda)

I wanted to avoid manually repositioning them so I tried setting the button grow direction:

``````for btn in buttons:
if angle == PI*1.5 or angle == PI*0.5 or angle == PI*-0.5:
btn.grow_horizontal = Control.GROW_DIRECTION_BOTH
elif angle >= PI*0.5 and angle < PI*1.5:
btn.grow_horizontal = Control.GROW_DIRECTION_BEGIN
else:
btn.grow_horizontal = Control.GROW_DIRECTION_END
angle += angle_offset
``````

As you can see, this works... with a caveat. For some reason, the next time you call it the `.grow_horizontal` property always acts as if it's back to `GROW_DIRECTION_END`, so you're back to how it was before. I'm not sure if this is a bug with buttons or not... In my code, I was calling `queue_free()` the buttons and making new ones as children. Yet, the buttons would somehow keep some sort of state between this...

I inspected the buttons of pie menu before and after: the difference was that `margin` changed. I don't know enough about how margin gets set yet to figure this out! Does anyone have any insight?

Here's what actually does work... on the second time it's called:

``````for btn in buttons:
btn.grow_horizontal = Control.GROW_DIRECTION_END