Your game needs a “level select” menu, where the user can choose from a grid of options.
As shown in the example above, we’ll make a scrolling grid of level “boxes” that the player can choose from. Let’s start with the individual level boxes:
Here’s the node setup:
LevelBox: PanelContainer Label MarginContainer TextureRect
The TextureRect is for displaying the lock icon, and the Label for displaying the level number. When one is showing, the other is hidden.
You can style these as you like, here’s an example:
Make sure to set the box’s Rect Min Size in the Inspector. We’re using
(110, 110) in the example, but it depends on what size layout you’re going for.
Now add a script and connect the
extends PanelContainer signal level_selected var locked = true setget set_locked var level_num = 1 setget set_level onready var label = $Label onready var lock = $MarginContainer func set_locked(value): locked = value lock.visible = value label.visible = not value func set_level(value): level_num = value label.text = str(level_num) func _on_LevelBox_gui_input(event): if locked: return if event is InputEventMouseButton and event.pressed: print("Clicked level ", level_num) emit_signal("level_selected", level_num)
print() statement can help you test that the click is being detected.
Once you have the box scene completed, add a new scene with a
GridContainer. Add any number of
LevelBox instances under it, making sure to set the Columns value. Here’s one with 6 columns:
In this example Custom Constants/HSeparation and VSeparation are set to
Save this scene as
LevelGrid. We’ll use multiple instances to display the desired number of levels.
Now we can put together the final menu.
Here’s the basic layout we’re going for:
We’ll create it with these nodes:
LevelMenu: MarginContainer VBoxContainer Title: Label HBoxContainer BackButton: TextureButton ClipControl: Control NextButton: TextureButton
Adjust the node properties:
Keep Aspect Centered
ClipControl node is where the grid goes. Enabling Clip Content means that if the contents are larger than the control, they’ll be cropped. That will allow us to make a horizontally scrolling set of grids. Add an
ClipControl, and instance 3 (or more)
LevelGrids inside it.
Make sure to set Custom Constants/Separation to
Your layout should look something like this (we’ve disabled Clip Content in order to show what’s happening):
With Clip Content, the three grids are all there, but the
ClipControl only shows one at a time.
Now, to scroll the menu, we need to shift the
710 pixels to the left/right.
110 (width of each LevelBox) * 6 (grid columns) + 10 (grid spacing) * 5 == 710
You may be wondering why we’re not using a
ScrollContainer here. You certainly can, but we don’t want continuous scrolling, and we don’t want to see a scrollbar.
Add a script to the
LevelMenu and connect the
pressed signals of the two buttons.
extends MarginContainer var num_grids = 1 var current_grid = 1 var grid_width = 710 onready var gridbox = $VBoxContainer/HBoxContainer/ClipControl/GridBox onready var tween = $Tween func _ready(): # Number all the level boxes and unlock them # Replace with your game's level/unlocks/etc. # You can also connect the "level_selected" signals num_grids = gridbox.get_child_count() for grid in gridbox.get_children(): for box in grid.get_children(): var num = box.get_position_in_parent() + 1 + 18 * grid.get_position_in_parent() box.level_num = num box.locked = false func _on_BackButton_pressed(): if current_grid > 1: current_grid -= 1 gridbox.rect_position.x += grid_width func _on_NextButton_pressed(): if current_grid < num_grids: current_grid += 1 gridbox.rect_position.x -= grid_width
When you run the scene, you’ll find that clicking “Next” will advance to the next menu, but the “Back” button doesn’t work.
The problem is scene order.
BackButton is above
ClipControl in the scene tree. That means that when the grid is shifted to the left, it is on top of the button. Unfortunately, there’s not a Clip Input property, but there is a way. Attach a script to
extends Control func _clips_input(): return true
Now everything should be working as expected.
Download the example project to see the whole thing in action, including some tweens for the scrolling action (tweens make everything better).
Download the project file here: level_select.zip