|
|
|
|
Reply From: |
DomtronVox |
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
#Creates a radial container node
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.
func _ready():
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
var x = cos(angle)*button_radius
var y = sin(angle)*button_radius
#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)
#Advance to next angle position
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
func add_button(btn):
add_child(btn)
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:
No need to mess around with sin/cos. Vectors can do the job for you. All this:
var x = cos(angle)*button_radius
var y = sin(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.
kidscancode | 2019-04-05 05:27
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
var circle_pos = Vector2(button_radius, 0).rotated(angle)
#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)
#Advance to next angle position
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!
DomtronVox | 2019-04-06 00:11
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:
btn.rect_position = Vector2(button_radius, 0).rotated(angle)
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
btn.rect_position = Vector2(button_radius, 0).rotated(angle)
if angle == PI*1.5 or angle == PI*0.5 or angle == PI*-0.5:
btn.rect_position.x -= btn.rect_size.x / 2
elif angle > PI*0.5 and angle < PI*1.5:
btn.rect_position.x -= btn.rect_size.x
angle += angle_offset
DLPalindrome | 2021-09-14 21:10