How to make a Carousel menu?

:bust_in_silhouette: Reply From: exuin
extends Container
class_name CircularCarousel


export var starting_angle := PI / 2
export var darkness := 0.2
export var transition_time := 0.5

var tween: Tween
var queued_turns := []


func _ready() -> void:
	tween = Tween.new()
	add_child(tween)
# warning-ignore:return_value_discarded
	tween.connect("tween_all_completed", self, "on_tween_all_completed")
	for i in get_child_count() - 1:
		var c := get_child(i)
		fit_child_in_rect(c, Rect2(calculate_child_position(i), c.rect_size))
		c.modulate = calculate_child_modulate(i)


func _input(event: InputEvent) -> void:
	if event is InputEventMouseButton:
		if event.button_index in [BUTTON_WHEEL_UP, BUTTON_WHEEL_DOWN] and event.pressed:
			if event.button_index == BUTTON_WHEEL_UP:
				if tween.is_active():
					queued_turns.append("move_up")
				else:
					move_up()
			else:
				if tween.is_active():
					queued_turns.append("move_down")
				else:
					move_down()


func move_up() -> void:
	var arr := []
	for i in get_child_count() - 1:
		arr.append(null)
	for i in get_child_count() - 1:
		if not (get_child_count() - 1) % 2:
			if i == 0:
				arr[1] = get_child(i)
			elif i == get_child_count() - 2:
				arr[i - 1] = get_child(i)
			elif i % 2:
				arr[i + 2] = get_child(i)
			else:
				arr[i - 2] = get_child(i)
		else:
			if i == 0:
				arr[1] = get_child(i)
			elif i == get_child_count() - 3:
				arr[i + 1] = get_child(i)
			elif i % 2:
				arr[i + 2] = get_child(i)
			else:
				arr[i - 2] = get_child(i)
	arr.append(tween)
	for child in get_children():
		remove_child(child)
	for child in arr:
		add_child(child)
	tween_children()


func move_down() -> void:
	var arr := []
	for i in get_child_count() - 1:
		arr.append(null)
	for i in get_child_count() - 1:
		if not (get_child_count() - 1) % 2:
			if i == 1:
				arr[0] = get_child(i)
			elif i == get_child_count() - 3:
				arr[i + 1] = get_child(i)
			elif i % 2:
				arr[i - 2] = get_child(i)
			else:
				arr[i + 2] = get_child(i)
		else:
			if i == 1:
				arr[0] = get_child(i)
			elif i == get_child_count() - 2:
				arr[i - 1] = get_child(i)
			elif i % 2:
				arr[i - 2] = get_child(i)
			else:
				arr[i + 2] = get_child(i)
	arr.append(tween)
	for child in get_children():
		remove_child(child)
	for child in arr:
		add_child(child)
	tween_children()
	
	
func tween_children() -> void:
	for i in get_child_count() - 1:
		var c := get_child(i)
# warning-ignore:return_value_discarded
		tween.interpolate_property(c, "rect_position", c.rect_position, calculate_child_position(i), transition_time)
# warning-ignore:return_value_discarded
		tween.interpolate_property(c, "modulate", c.modulate, calculate_child_modulate(i), transition_time)
# warning-ignore:return_value_discarded
	tween.start()


func calculate_child_position(i: int) -> Vector2:
	var angle_sign := -1 if i % 2 else 1
	var spacing := PI / (get_child_count() - 1) * (get_child_count() - i - 2)
	var offset := PI / (get_child_count() - 1) / 2
	var angle := starting_angle + angle_sign * spacing
	if not get_child_count() == i + 2:
		if not (get_child_count() - 1) % 2:
			angle += offset
		else:
			angle -= offset
	
	if not (get_child_count() - 1) % 2 and i == 0:
		angle += offset
	var center := Vector2(cos(angle), sin(angle)) * rect_size / 2 + rect_size / 2
	var top_left: Vector2 = center - get_child(i).rect_size / 2
	return top_left


func calculate_child_modulate(i: int) -> Color:
# warning-ignore:integer_division
	return Color.white.darkened(darkness * ((get_child_count() - i - 1) / 2))


func on_tween_all_completed() -> void:
	if queued_turns:
		call(queued_turns.pop_front())

Thanks thanks, I know it sounds silly, but what Nodes and children should I use for this code? TvT, I am indebted to you.

Reygreisin | 2022-05-24 04:11

Put the script on a Container. Make sure that the child nodes are Control-extended nodes like TextureRects or Buttons.

exuin | 2022-05-24 05:34

Dude, I’m sorry to bother you so much, because you must be busy with your own personal projects, but isn’t there a way to do it with buttons instead of the mouse?

Reygreisin | 2022-05-26 03:01

Yes, just change the _input function to listen for those buttons instead of the mouse.

exuin | 2022-05-26 14:49

Friend it shows that you have knowledge and your code is good, I do not understand it at all, honestly it did not work for me and I made many mistakes when implementing it in my game, apart from changing the mouse function to key, nothing worked, anyway, thanks . It’s too complicated for my knowledge.

Reygreisin | 2022-08-23 22:47