Input & Interaction

Grids overlays support two input modes: passthrough (Alt-key) and exclusive capture. The mode determines whether game input (movement, camera, etc.) is blocked while interacting with an overlay.

Input Modes

Passthrough Mode (Alt-Key)

The default mode. Overlays are visible but non-interactive. Holding Alt enables cursor-based interaction while keeping game input active in the background. Best for HUD elements, notifications, and overlays where you want quick peek-and-interact.

StateBehavior
Alt released (default)Mouse captured by game, cursor hidden, overlays are HitTestInvisible (visible but non-interactive)
Alt heldCursor appears, overlays become Visible (interactive), game also receives input
Alt + Right-clickLine trace from cursor → checks for IContextMenuProvider on hit actor → opens context menu overlay

Exclusive Capture Mode

For modal overlays like menus, dialogs, and search UIs where typing shouldn't move the character. When an overlay captures input exclusively, the player controller switches to UI-only input mode — all keyboard and mouse input goes to the overlay, none reaches the game.

StateBehavior
Overlay opens with captureCursor appears, game input fully blocked, overlay receives all input
Overlay closes / releases captureGame input restored, cursor hidden

When to use: Any overlay with text fields, search boxes, chat input, or complex interactions where WASD/mouse should not affect the game.

Using Input Capture

From C++ (creating overlays)

UWebOverlay* Overlay = Sys->CreateOverlay("my-menu", Url, 100);
Overlay->SetInteractive(true);
Overlay->SetCapturesInput(true);  // Blocks game input while this overlay is active

From JavaScript/TypeScript (at runtime)

import { grids } from '@grids/overlay-sdk';

// Request exclusive input capture (blocks game input)
grids.captureInput();

// Release exclusive input (game input restored)
grids.releaseInput();

The capture state is tracked globally by UWebOverlaySubsystem. If any visible and interactive overlay has bCapturesInput = true, the player controller switches to UI-only input mode. When the last capturing overlay is hidden, closed, or releases capture, game input is automatically restored.

All input-mode arbitration (cursor visibility, input mode, and Slate focus) is centralized in a single base class, AOverlayInputPlayerController, via one method — ApplyOverlayInputState(). The gameplay, main-menu, and login controllers all derive from it, so there is exactly one source of truth for focus.

Choosing the Right Mode

Overlay TypeModeExample
HUD / status displayPassthroughHealth bar, minimap
NotificationsPassthroughToast messages
Context menusPassthroughRight-click menus (quick select)
Asset browser / menusExclusive captureTab menu with search box
Chat / text inputExclusive captureIn-game chat overlay
Settings / dialogsExclusive captureConfiguration panels

Implementation Details

Alt-Key System

The Alt-key logic lives in AGridsPlayerController, but the actual input-mode switch is delegated to the shared AOverlayInputPlayerController::ApplyOverlayInputState():

  1. Key down — Mark bIsAltInteracting, flip visible non-capturing overlays to interactive, then apply state (cursor shown, GameAndUI mode so movement/look keep working)
  2. Key up — Dismiss context menus, restore the overlays, then apply state (cursor hidden, GameOnly mode)
  3. Right-click while Alt held — Perform a line trace, check if the hit actor implements IContextMenuProvider, and open the context menu overlay via UContextMenuManager

Exclusive Capture System

Input capture is managed by the overlay subsystem with a delegate pattern:

  1. UWebOverlay::SetCapturesInput(true) marks the overlay as input-capturing
  2. UWebOverlaySubsystem::UpdateInputCaptureState() checks if any overlay is capturing
  3. OnInputCaptureChanged delegate fires, handled by AOverlayInputPlayerController
  4. The coordinator re-runs ApplyOverlayInputState(), switching to FInputModeUIOnly (capture) or back to FInputModeGameOnly / FInputModeGameAndUI (release)

The viewport mouse-capture mode is set to CaptureDuringMouseDown (not CapturePermanently) in DefaultInput.ini so the cursor and UI focus never fight the viewport's auto-recapture. Pure gameplay still has full free-look because FInputModeGameOnly captures the mouse on its own.

Input Mapping

The system uses Unreal's Enhanced Input System:

Overlay Focus vs Input Capture

These are two separate concepts:

MethodEffect
grids.requestFocus() / releaseFocus()Toggles Slate hit-test visibility (whether clicks reach the overlay). Game input continues.
grids.captureInput() / releaseInput()Blocks/restores game input entirely. The overlay acts as a modal dialog.

You can combine both: requestFocus() makes the overlay interactive, captureInput() additionally blocks game input.

Build Mode Controls

Pressing B enters build mode. While build mode is active, a translucent preview follows the crosshair and left-click places a block at the previewed location and rotation. Rotation is fully 3-axis and synced to the server + persisted on placement.

Rotation keys

KeyAction
RRotate the active axis by + snap (default +15°)
Shift + RRotate the active axis by − snap
XSelect the Pitch (X) axis for subsequent R presses
YSelect the Roll (Y) axis
ZSelect the Yaw (Z) axis (default)
[Decrease snap size — cycles through 1°, 5°, 15°, 45°, 90°
]Increase snap size
Shift (hold)Temporarily switch to free rotate — each R press rotates by 1° regardless of snap
BackspaceReset rotation to (0, 0, 0)

Rotation keys are only active during placement (no block selected). If you've clicked a placed block to select it, rotation keys are disabled so they don't conflict with the selection editor. Press another shape button or build:deselect to return to placement mode.

Rotation UI

The build-editor overlay's rotation panel (bottom-center of the viewport during placement) mirrors every key in clickable form:

All overlay buttons call through to the same C++ entry points as the keys, so there is only ever one source of truth for placement rotation.

Multiplayer and persistence

Rotation is included in every block placement message:

Editing Placed Objects

Any block you've placed can be re-textured and re-styled after the fact — you don't have to delete and re-place it.

Selecting a block to edit

While Alt is held (cursor active), right-click a placed block to open its context menu. The menu is provided by AReplicatedBlock (which implements IContextMenuProvider) and offers:

ActionBehavior
EditEnters build mode (if needed), selects the block, and opens the Edit Object panel
DuplicateRe-places an identical block (same shape, texture, rotation, scale)
DeleteRemoves the block and its persisted record

Selecting Edit routes through AGridsPlayerController::BeginEditBlock, which retargets the build-editor selection to the live AReplicatedBlock and opens the contextual Edit Object panel.

The Edit Object panel

The panel (SBuildEditorWidget::BuildEditPanel) lets you restyle the selected block per face:

How edits reach the server

Each control calls a controller helper (ApplyFaceTexture / ApplyFaceTint / ApplyFaceEffect) that preserves the other two attributes and sends a single RPC:

ServerRequestBlockSetFace(BlockId, FaceIndex, AssetId, TextureId, DataUrl, Tint, Effect);

A FaceIndex of -1 means "all faces" and is expanded into a per-face loop client-side. On the server, AReplicatedBlock::SetFaceOverride updates the procedural mesh section for that face (each face is its own mesh section), so the change replicates to every client via native UE replication.

Persistence

After applying a face override, the server persists it through UGridAuthoritySubsystem::SaveBlockFace, which issues:

PUT /api/zones/:zoneId/blocks/:blockId/faces
{ face_index, asset_id, texture_id, data_url, tint, effect }

The Edge API upserts the row into zone_block_faces. On zone-server startup, AGridsServerGameMode::LoadBlocksFromDb reads each block's faces array and replays the overrides via SetFaceOverride, so a block looks exactly as it was last edited — across restarts and for late-joining clients.

CAD Grid & Tools

Build mode is a lightweight in-engine CAD surface. A wireframe ground grid renders under the player and snapping aligns to its 1 m (100 cm) cells.

Ground grid

UBuildGridVisualizer (a client-only UActorComponent on AGridsPlayerController) draws a finite wireframe patch that follows the player. The patch re-centers on the player's grid cell and raycasts down for the ground height, so the grid sits on the terrain beneath you. A highlighted cell tracks the active tool's cursor — blue/valid or red/invalid.

Tools: Block and Wall

The toolbar's Tool selector switches between two tools (build:select-tool, { tool: 'block' | 'wall' }). The active tool is echoed back in build-mode:enter as tool.

ToolBehaviour
BlockThe original placement preview — LMB places one block at the crosshair.
WallCAD-style drag-to-build. LMB anchors the run start; moving the cursor previews a tiled run of wall segments; a second LMB commits the run. RMB / Esc cancels. The run's end becomes the next anchor so runs chain continuously.

UWallBuilderComponent generates segments along a quadratic Bézier from anchor → cursor with an optional perpendicular bend, so straight (bend = 0) and curved runs share one code path. Preview ghosts are green when valid and red when out of zone bounds.

The bottom-center Wall panel (shown while the wall tool is active) exposes:

Measurement labels

While a wall run is anchored, AGridsPlayerController projects measurement points to screen (throttled ~12 Hz) and pushes them to the overlay via build:measurements ({ labels: [{ id, kind, text, x, y }] }). The MeasurementLabels component renders them as absolutely-positioned CAD tags:

Multiplayer and persistence

Committed runs are sent in a single batch via AGridsCharacter::ServerRequestWallPlace(Segments, Shape, TextureId, DataUrl, AssetId). The server validates build permission and per-segment distance, spawns one AReplicatedBlock per segment (scaled cube), and persists each through UGridAuthoritySubsystem::SaveBlock exactly like single-block placement.

Editing: undo/redo, clipboard and arrays

Build mode keeps a client-side, local-session command history in UBuildHistoryComponent (a UActorComponent on AGridsPlayerController). Every place, delete and transform is recorded as a FBuildCommand with before/after FBlockSnapshots, so edits can be reversed without a server round-trip to rebuild state. History is cleared when build mode is exited.

KeybindAction
Ctrl+ZUndo (re-applies the inverse command)
Ctrl+Y / Ctrl+Shift+ZRedo
Ctrl+CCopy the current selection to the clipboard
Ctrl+VPaste (each paste steps the offset so copies don't stack)
Ctrl+DDuplicate the selection in place (offset one cell)
Ctrl+Shift+DArray — four copies along +X at 1 m spacing
1 / 2 / 3Select / Block / Wall tool

Pasted, duplicated and arrayed blocks are sent to the server in one call via AGridsCharacter::ServerRequestBlockPlaceBatch(Placements), which validates build permission, the batch size (1–256) and per-player budget before spawning and persisting each block. Face overrides are re-applied so copies keep their per-face textures.

Select tool: aim ray and selection highlight

The Select tool (hotkey 1) picks existing blocks instead of placing new ones. Because picking needs the free cursor, selection is Alt-gated:

Selected blocks get a persistent amber wireframe box that stays visible while they're selected, even after Alt is released, so you always know what's selected. Both the ray and the highlight boxes are drawn by UBuildAimVisualizer as code-built procedural meshes (no content assets, no post-process outline material), so they render identically in packaged builds. The highlight is rebuilt only when the selection or a transform changes — not per-frame.

Placement safety

Single placement now correlates a client-generated BlockId through ServerRequestBlockPlace so undo/redo can target the exact block. The server enforces a per-player block budget (MaxBlocksPerPlayer, default 10000) and rejects placements that would exceed it. On the client, UPlacementPreviewComponent::ValidatePlacement rejects positions that overlap an existing block (AABB intersection, shrunk 5 cm so face-adjacent blocks remain valid), turning the preview red.

Asset browser

SAssetBrowserWidget virtualizes its card grid with an STileView so large libraries scroll smoothly. Cards expose a favorite star, and the filter bar adds Recent and Favorites views alongside the per-type filters. Favorites and the most-recently-spawned items (capped at 16) persist between sessions in the [AssetBrowser] section of GGameUserSettingsIni.

Player Nameplates (ESP)

Floating nameplates render above every other player so you can see who is around you. They are drawn directly to the HUD canvas (no per-player widgets or overlays), so they stay cheap even with many players on screen.

AGridsHUD (registered as the client HUDClass in AGridGameMode) iterates GameState->PlayerArray each frame and, for every remote AGridsPlayerState, draws a nameplate:

Distance, fade & occlusion

By default nameplates behave like real spatial labels rather than pure wallhacks:

CVars

CVarDefaultEffect
grids.Nameplates.Enabled1Master toggle for all nameplate drawing
grids.Nameplates.MaxDistance8000Max draw distance in cm
grids.Nameplates.SeeThrough0When 1, skips the occlusion trace so names render through walls (pure ESP)
grids.Nameplates.ShowOwn0When 1, also draws the local player's own nameplate (debug)

Voice Chat

Voice chat uses Unreal's built-in networked VoIP, routed through the dedicated server. Each AGridsCharacter owns a UVOIPTalker component that is registered against its AGridsPlayerState in PossessedBy / OnRep_PlayerState, so transmission and playback work for both the server and late-joining clients.

Modes

The mode is chosen in Settings → Audio → Voice Mode and persisted in the settings JSON (audio.voice_mode):

ModeBehavior
Push to Talk (ptt, default)Transmits only while the voice key is held
Open Mic (open)Transmits continuously; the talking indicator is gated by microphone level

Voice chat can be turned off entirely with the Voice Chat toggle (audio.voice_chat_enabled).

Keybind

Action IDDefault keyCategory
voice.pushtotalkVVoice

The key is registered through UOverlayKeybindSubsystem like every other keybind, so it can be rebound from the keybinds UI.

How talking state is shared

AGridsPlayerController computes whether the local player is transmitting each tick — PTT uses the key-held state, open mic thresholds UVOIPTalker::GetVoiceLevel(). When that state changes it calls the ServerSetTalking(bool) RPC on AGridsPlayerState, which replicates bIsTalking to everyone so the voice icon in the nameplate appears and clears in sync with actual transmission. StartTalking() / StopTalking() on the player controller gate the actual audio stream.