#Infinite-Scrolled, Touch-Based (Vertical) Carousel Menu

97 messages · Page 1 of 1 (latest)

sterile raven
#

Hello!
I want to make an infinite-scrolling, touch-based carousel. (in Godot 4)
(if it's ok, with something than can snap after the spinning from swiping stopped)
Is there a quick step-by-step stuff with it?

sterile raven
#

(I'll be late to respond because I need to sleep soon)

split pasture
#

You can use the Parallax2D class. It has methods to create infinite scrolling ui elements.

sterile raven
#

I assume since it a 2D Node, It should be fine if I put inside a control node, yes?

#

Also, this is WIP for the step-by-step (Roughly)

  • Make a Container for the scrolling menu
  • Make a separate scene for the items (which would be instantiated automically)
  • the separate scene will be contained in an array

That's all that I can think of at the moment, so.... yep
still quite stuck.

sterile raven
#

Right, I've tried it with using Parallax2D, and it kinda works (was testing it using the autoscroll inside the Parallax2D),
now how can I make it touch-scrollable, and also snap when the carousel finish spinning (from swiping)?

split pasture
#

There’s no built in method for that on Parallax2D

split pasture
#

Set the position of the items in your carousel manually to the position you want it to snap to

#

There are ways you can do this using by making your own grid for example

sterile raven
#

Ah, I see...

#

Node-wise, how should I setup my carousel? (in this case, it's a vertical carousel)

#

Is it like this?

sterile raven
sterile raven
#

(If it's ok, are you online later? I plan to quickly finish this menu so if we could arrange time where we could be online together for tackling this problem, would that be okay?)

split pasture
#

I’ll be online now for a while so I can help you out

#

Do you need to hold data about the items in your carousel or do you just simply want it to scroll and repeat the texture?

sterile raven
#

I need it to hold data about the item in my carousel

#

Also, sorry for the late reply.

long shell
#

First add a Parallax2D

#

Then under the Parallax2D add a GridContainer as a child

#

Next add the carousel objects as a child of the GridContainer

#

Set the repeat_size property of the Parallax2D to the size property of your GridContainer.

#

Finally set the autoscroll property of the Parallax2D

sterile raven
sterile raven
#

Ok, It works with the auto scroll (for testing)

split pasture
#

The most straightforward way to achieve what you’re trying to do is to set the positions of each child node under GridContainer manually

#

First go into project settings and under ‘Pointing’ you enable emulate touch from mouse

#

Then under your GridContainer attach a new script

#

Add the built in _unhandled_input function

long shell
#
  if event.is_mouse_button_pressed(MOUSE_BUTTON_LEFT):
    Viewport.get_mouse_position```
#

Use that to get the position of where your player is touching the screen

#

Then set the position of each of the child nodes manually to replicate the snap

sterile raven
#

If, for example:
I use Instantiate (For each child, containing the Map), how can I set the position on the child node?
Knowing that the GridContainer automatically sort the Item (which in my case, I need it vertically).

long shell
#

Because GridContainer doesnt allow you to manually set the positions of child nodes

#

Then implement something like this into the script attatched to your control node


@export var snap_positions: Array[Vector2] = [Vector2(28, 30), Vector2(40, 64)]
@export var snap_distance: float

@onready var child_nodes: Array[Node] = get_children()

func _unhandled_input(event: InputEvent) -> void:
    if event.is_action_released("mouse button left"):
        check_snap_distance()


func check_snap_distance() -> void:
    for child: Node in child_nodes:
        for snap_position: Vector2 in snap_positions:
            if child.position.x - snap_distance == snap_position.x:
                child.position.x = snap_position.x
            elif child.position.x + snap_distance == snap_position.x:
                child.position.x = snap_position.x
#

Here when the player releases their finger from the screen, _unhandled_input is called

#

Then check_snap_distance is called

#

This function checks the position.x of each child node and if it is within snapping distance of your snap_positions

#

It will set the position.x of the child node to your snap_position

#

(Since were not inheriting from GridContainer your child nodes will not be laid out in a grid format in the editor, so youll have to manually do it)

#

Unless you want to implement your own custom system to lay out a grid, but its not really a straightforward process and its a bit complicated because then I would have to basically show you how to create your own customizable version of GridContainer

long shell
#

Yea well you can call the class whatever youd like

#

But that class is basically gonna have to calculate a grid and lay it out based off the number of columns

sterile raven
#

How about the VBoxContainer?

long shell
#

Nah the VBoxContainer also doesnt allow you to manually set the positions of its child nodes

sterile raven
#

Alright, Lemme think a bit:
Since I need to manually code for each position, that means each item will be separated by item index (as in Array), then making the gap by making the item's position based on (Item's Height + Gap Size), yes?

long shell
#

You could do that, but its gonna be quite complicated. Youre gonna have to calculate the rows based off the columns and the size of your Control

#

Then youre gonna have to set the size of each child node to match the size of your Control and set its position aswell

#

Another way you could do it is to use the GridContainer

#

But then offset the entire texture based on snap_positions

#

Instead of setting the positions of each child_node maually

#

Youre not gonna have to sorted children from your Control node

#

But you dont really need to have the children sorted from your Control

#

If you want the player to interact with the children you can just make the child nodes under your GridContainer a button or something

#

And have each child node's pressed() signal connect to your parent node to detect when the player interacts with the item in your carousel

sterile raven
long shell
#

Wait first what are you going to try and do, option 1 or option 2. Are you going to set the child positions manually or use Godot's built in GridContainer.

sterile raven
#

Which one do you think it's more complicated? gdthinking

long shell
#

Option 1 is definitely way more complicated trust me

#

But it depends on what youre trying to do

#

Are the items in your carousal going to be the same every single time the texture is repeated?

#

Or are you going to try and change the item when the texture is about to repeat

sterile raven
#

Here's my concept so far

#

When player finish choosing the map, they press the choose map button to select it.

long shell
#

@export var snap_positions: Array[Vector2] = [Vector2(28, 30), Vector2(40, 64)]
@export var snap_distance: float

@onready var child_nodes: Array[Node] = get_children()

func _unhandled_input(event: InputEvent) -> void:
    if event.is_action_released("mouse button left"):
        check_snap_distance()


func check_snap_distance() -> void:
    for snap_position: Vector2 in snap_positions:
        if position.y - snap_distance == snap_position.y:
            position.y = snap_position.y
        elif position.y + snap_distance == snap_position.y:
            position.y = snap_position.y

#

This will snap the entire grid with all its child nodes

#

Just set the elements in the snap_positions array to the position of each child node

sterile raven
long shell
#

Attatch this script to your GridContainer

sterile raven
sterile raven
long shell
#

Yes the position

#

Not size

sterile raven
#

Not sure if I setting it correctly...

sterile raven
long shell
#
extends GridContainer


@export var snap_distance: float = 10

var initial_positions: Array[Vector2]: set = set_initial_positions


func _ready() -> void:
    initial_positions = []


func snap() -> void:
    for pos: Vector2 in initial_positions:
        if global_position.y - snap_distance == pos.y:
            global_position.y = snap_distance


func set_initial_positions(array: Array[Vector2]) -> void:
    initial_positions = array
    for child: Node in get_children():
        initial_positions.append(child.global_position)
#

This is for your GridContainer

#

Attatch another script to your Parallax2D

#

And check for when the player releases

#

Then call snap() from your Parallax2D and set auto_scroll to Vector2.ZERO

long shell
#

Yea this one is better so you dont have to manually set the snap positions

sterile raven
#

Alright, I already replace it

#

what should I do next?

sterile raven
long shell
sterile raven
#

Ah, got it

sterile raven