using { /Fortnite.com/Characters } using { /Fortnite.com/Devices } using { /Fortnite.com/Game } using { /Fortnite.com/UI } using { /Verse.org/Simulation } using { /Verse.org/Colors } using { /UnrealEngine.com/Temporary/SpatialMath } using { /UnrealEngine.com/Temporary/UI } using { /UnrealEngine.com/Temporary/Diagnostics } # Dynamic, interpolated UI text is built with message functions # (this is how the Verse UI APIs accept text -- DefaultText/SetText take a # `message`, not a `string`). Same pattern our shipping code uses. CellLabel(Row : int, Col : int) : message = "R{Row}C{Col}" PromptLabel : message = "Click a button..." ClickedLabel(Row : int, Col : int) : message = "Clicked R{Row}C{Col}!" # Holds one generated button plus the grid coordinate it represents, so we can # both lay it out AND wire its click handler without recomputing positions. grid_cell := struct: Button : button_loud Row : int Col : int # A device that DYNAMICALLY GENERATES a grid of clickable button widgets in # Verse (no manual widget placement in the editor) and adds it to a player's # on-screen UI. Grounded in our shipping grid-of-buttons pattern from # Scripts/UISystem/minigame_vote_ui.verse (button_loud rows in a stack_box, # laid out inside a canvas, added via GetPlayerUI[].AddWidget, with each # button wired to OnClick().Await()). grid_button_device := class(creative_device): # When this trigger fires, we build the grid for everyone in the lobby. # Leave it unset to fall back to building the grid in OnBegin. @editable ShowGridTrigger : ?trigger_device = false # Grid dimensions. Change these two numbers and the loops below build a # completely different grid -- THIS is the "dynamic generation" point. @editable RowCount : int = 3 @editable ColumnCount : int = 4 # Live readout so you can SEE the click handlers firing on screen. var StatusText : text_block = text_block{ DefaultText := PromptLabel } OnBegin() : void = if (Trigger := ShowGridTrigger?): # Rebuild the grid for all players each time the trigger fires. loop: Trigger.TriggeredEvent.Await() for (Player : GetPlayspace().GetPlayers()): spawn{ ShowGridForPlayer(Player) } else: # No trigger configured: build it for everyone right away. for (Player : GetPlayspace().GetPlayers()): spawn{ ShowGridForPlayer(Player) } # Build the grid widget tree and add it to one player's UI. ShowGridForPlayer(Agent : agent) : void = if (FortPlayer := player[Agent], PlayerUI := GetPlayerUI[FortPlayer]): # 1. Generate the cells in nested loops. Each Row/Col pair makes one # button -- the loops ARE the dynamic generation, so changing # RowCount/ColumnCount changes the whole grid with no edits below. var Cells : []grid_cell = array{} for (Row := 0..RowCount - 1): for (Col := 0..ColumnCount - 1): NewButton := button_loud{ DefaultText := CellLabel(Row, Col) } set Cells += array{ grid_cell{ Button := NewButton, Row := Row, Col := Col } } # 2. Lay the generated cells out as a real grid: one horizontal # stack_box per row, all stacked vertically, positioned by a # canvas. (canvas + stack_box + button widgets = the visible UI.) GridWidget : canvas = MakeGrid(Cells) # 3. Add the grid to the player's UI so it actually appears on # screen. InputMode.All lets the buttons receive clicks. PlayerUI.AddWidget(GridWidget, player_ui_slot{ InputMode := ui_input_mode.All }) # 4. Wire a click handler to every generated button -- this is what # makes the grid interactive instead of an empty shell. for (Cell : Cells): spawn{ HandleButtonClicks(Cell) } # Arrange the generated cells into rows -> a visible grid inside a canvas. MakeGrid(Cells : []grid_cell) : canvas = # One horizontal stack_box per row. RowBoxes : []stack_box = for (R := 0..RowCount - 1) { stack_box{ Orientation := orientation.Horizontal } } # Drop each button into the row box for its captured Row index. for (Cell : Cells, RowBox := RowBoxes[Cell.Row]): RowBox.AddWidget( stack_box_slot: Widget := Cell.Button HorizontalAlignment := horizontal_alignment.Center VerticalAlignment := vertical_alignment.Center Padding := margin{ Left := 8.0, Right := 8.0, Top := 8.0, Bottom := 8.0 } ) # Stack the rows vertically, with the status label on top. ColumnBox : stack_box = stack_box: Orientation := orientation.Vertical Slots := array: stack_box_slot{ Widget := StatusText, HorizontalAlignment := horizontal_alignment.Center } for (RowBox : RowBoxes): ColumnBox.AddWidget( stack_box_slot: Widget := RowBox HorizontalAlignment := horizontal_alignment.Center ) # Pin the grid near the top-center of the screen. return canvas: Slots := array: canvas_slot: Widget := ColumnBox Anchors := anchors{ Minimum := vector2{ X := 0.5, Y := 0.0 }, Maximum := vector2{ X := 0.5, Y := 0.0 } } Offsets := margin{ Top := 80.0 } Alignment := vector2{ X := 0.5, Y := 0.0 } # Await clicks on one button forever and report which cell was pressed. HandleButtonClicks(Cell : grid_cell) : void = loop: ClickMessage := Cell.Button.OnClick().Await() StatusText.SetText(ClickedLabel(Cell.Row, Cell.Col)) Print("Grid button clicked: Row {Cell.Row}, Col {Cell.Col}")