Creating a Grid Menu
Creating a Grid Menu
Special care has to be taken when creating a menu system with gamepad controls in mind. With conventional interfaces, the user can simply click or tap where they want to select. For simple and quick menus, a
Articles/Creating a Radial Menu|Radial Menu works well with a gamepad, but with more complicated and conventional menus the user has to navigate a menu by moving a selector from element to element.
GuiService automatically tries to determine which element a user wants to select with a gamepad, but sometimes extra configuration is needed for the menu to work correctly.
This tutorial uses GuiService and
ContextActionService to implement a simple equipment management menu system.
The code in this tutorial creates a framework for an equipment management system. It creates a button on the screen; if the user presses A with that button selected, the game pops up a model menu showing a simple character model and a grid of items.
The first thing the code does is create the menu. The menu in your game can of course be created and stored before hand, but in this case the menu elements are explicitly created in the script.
The important thing to note about the menu is the hierarchy, as this will be used when defining selection groups. All of the buttons for the character slots are children of
CharacterFrame, and all of the grid button are children of
Opening the menu
After the menu layout has been defined, the code then binds input to open and close the menu. Gui buttons (such as
TextButtons can accept gamepad input. The event
GuiButton/MouseButton1Click will fire if a player presses the A button with the button selected.
The code defines two functions:
openEquipmentMenu is bound to the
equipmentButton which is in the upper left corner of the screen. When that function is called,
GuiService/AutoSelectGuiEnabled is disabled and the value stored for later use. When this property is enabled, the game will automatically choose an element when the select button is pressed. That behavior is needed before the menu is opened so the equipment button can be activated, but while the player is in the menu it should be disabled.
GuiObject/TweenPosition is used to animate the menu opening and closing. The last parameter of this function is a custom callback function which is called when the animation is completed. When the opening animation is finished, this function is used to bind the B button to
closeEquipmentMenu and to move the selection to the torso button. In
closeEquipmentMenu, after the closing animation is played, the B button is unbound, the selection is cleared and the original value of AutoSelectGuiEnabled is restored.
Navigating Character Slots
Roblox has automatic behavior to help a user with a gamepad navigate Gui elements. When the user presses the Select button on their gamepad, the game will create a selection around a visible GUI element that has
GuiObject/Selectable enabled. When the user presses the left thumbstick or the dpad, the game will try to find another GUI element in the direction that was pushed and move the selection there. If there is no element in the direction, then the selection will not change.
The grid in the right of the menu works well with this system (as the elements are always in a cardinal direction from one another), but the elements in the character frame will not work as well with the default system.
GuiObjects have several properties you can use to specify which element to switch to (e.g.
GuiObject/NextSelectionDown. Using these properties, the directions from each character slot can be set like so:
headFrame.NextSelectionDown = torsoFrame headFrame.NextSelectionLeft = rightArmFrame headFrame.NextSelectionRight = leftArmFrame torsoFrame.NextSelectionUp = headFrame torsoFrame.NextSelectionLeft = rightArmFrame torsoFrame.NextSelectionRight = leftArmFrame torsoFrame.NextSelectionDown = rightLegFrame rightArmFrame.NextSelectionUp = headFrame rightArmFrame.NextSelectionRight = torsoFrame rightArmFrame.NextSelectionDown = rightLegFrame leftArmFrame.NextSelectionUp = headFrame leftArmFrame.NextSelectionLeft = torsoFrame leftArmFrame.NextSelectionDown = leftLegFrame rightLegFrame.NextSelectionUp = torsoFrame rightLegFrame.NextSelectionRight = leftLegFrame rightLegFrame.NextSelectionLeft = rightArmFrame leftLegFrame.NextSelectionUp = torsoFrame leftLegFrame.NextSelectionRight = leftArmFrame leftLegFrame.NextSelectionLeft = rightLegFrame
Notice that in the above code there are several edges that are not defined. For example, the code does not define the behavior for moving right from the left arm. If a user has the left arm selected and presses right, since that behavior was not explicitly defined with
GuiObject/NextSelectionRight, the game will attempt to find a selectable GUI element to the right. This is undesirable as the user should be confined to the character slots while in that portion of the menu. While this could be defined by setting the value to nil, a much simpler way is to use a selection group.
In GuiService, a selection group is a set of GUI elements that can be navigated between. There are two ways to define selection groups:
GuiService/AddSelectionTuple. AddSelectionParent takes two arguments, a name for the selection and a GuiObject. In such a selection group, only the children of the passed in GuiObject can be navigated between. For the other function, AddSelectionTuple, you simply pass in all of the GuiObjects you want to be in the group. In this case, since all of the character slots are children of
characterFrame, the simpler function to use is AddSelectionParent.
Now, when a user enters the menu via
openEquipmentMenu, they will only be able to navigate between the children of
Next, the character slot buttons have to be bound to move the selection to the inventory grid:
Again MouseButton1Click is used to detect when the user presses the A button with one of the character slots selected. When this event fires,
onCharacterSlotClicked is called. This function first stores the character slot that was selected for later use, then binds the B button to call
exitInventoryMenu, and finally selects the first grid cell in the inventory menu.
exitInventoryMenu simply unbinds the B binding that
onCharacterSlotClicked sets up and moves the selection back to the inventory slot that was selected before.
Navigating Inventory Grid
The inventory grid is much simpler to navigate than the character frame as the default gamepad selection code works very well with grids. The only things that need to be set up for the inventory grid are the selection group and the event when the user presses A with one of the cells selected.
GuiService:AddSelectionParent("InventoryMenu", inventoryScroll) -- Player "clicked" on an inventory slot. This is where you would put code to take action -- with the currentEquipmentSlot and the SelectedObject local function onInventorySlotClicked() print("Character slot:", currentEquipmentSlot) print("Inventory cell:", GuiService.SelectedObject) -- TODO: Your code here! exitInventoryMenu() end for _, child in pairs(inventoryScroll:GetChildren()) do child.MouseButton1Click:connect(onInventorySlotClicked) end
Again, a selection group is set up with AddSelectionParent. Even though the default gamepad selection code will facilitate moving the selection among the grid, the function still needs to make sure the selection does not move outside the grid.
For every cell in the grid,
onInventorySlotClicked is bound to MouseButton1Click. This function is what you would modify to do any custom code such as equipping the item the user selected. At the end of the function
exitInventoryMenu is called to move the selection back to the character frame.
Once that function has been bound, the framework for the menu system is complete.
Project Source Code
Below is the complete source for the menu outlined in this article. To work properly, it must be inserted in a
LocalScript located in