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.
| State | Behavior |
|---|---|
| Alt released (default) | Mouse captured by game, cursor hidden, overlays are HitTestInvisible (visible but non-interactive) |
| Alt held | Cursor appears, overlays become Visible (interactive), game also receives input |
| Alt + Right-click | Line 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.
| State | Behavior |
|---|---|
| Overlay opens with capture | Cursor appears, game input fully blocked, overlay receives all input |
| Overlay closes / releases capture | Game 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 Type | Mode | Example |
|---|---|---|
| HUD / status display | Passthrough | Health bar, minimap |
| Notifications | Passthrough | Toast messages |
| Context menus | Passthrough | Right-click menus (quick select) |
| Asset browser / menus | Exclusive capture | Tab menu with search box |
| Chat / text input | Exclusive capture | In-game chat overlay |
| Settings / dialogs | Exclusive capture | Configuration 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():
- Key down — Mark
bIsAltInteracting, flip visible non-capturing overlays to interactive, then apply state (cursor shown,GameAndUImode so movement/look keep working) - Key up — Dismiss context menus, restore the overlays, then apply state (cursor hidden,
GameOnlymode) - Right-click while Alt held — Perform a line trace, check if the hit actor implements
IContextMenuProvider, and open the context menu overlay viaUContextMenuManager
Exclusive Capture System
Input capture is managed by the overlay subsystem with a delegate pattern:
UWebOverlay::SetCapturesInput(true)marks the overlay as input-capturingUWebOverlaySubsystem::UpdateInputCaptureState()checks if any overlay is capturingOnInputCaptureChangeddelegate fires, handled byAOverlayInputPlayerController- The coordinator re-runs
ApplyOverlayInputState(), switching toFInputModeUIOnly(capture) or back toFInputModeGameOnly/FInputModeGameAndUI(release)
The viewport mouse-capture mode is set to
CaptureDuringMouseDown(notCapturePermanently) inDefaultInput.iniso the cursor and UI focus never fight the viewport's auto-recapture. Pure gameplay still has full free-look becauseFInputModeGameOnlycaptures the mouse on its own.
Input Mapping
The system uses Unreal's Enhanced Input System:
- IMC_Default — Standard gameplay input (movement, camera, actions)
- IMC_MouseLook — Separate context for when cursor is active during Alt hold
Overlay Focus vs Input Capture
These are two separate concepts:
| Method | Effect |
|---|---|
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
| Key | Action |
|---|---|
| R | Rotate the active axis by + snap (default +15°) |
| Shift + R | Rotate the active axis by − snap |
| X | Select the Pitch (X) axis for subsequent R presses |
| Y | Select the Roll (Y) axis |
| Z | Select 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 |
| Backspace | Reset 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:
- An axis selector (X / Y / Z) that highlights the active axis
- Live readouts of Pitch / Yaw / Roll in degrees
- − / + step buttons that respect the current snap size
- A snap selector (1° / 5° / 15° / 45° / 90° / free) that switches increments
- A reset button that zeroes all three axes
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:
AGridsCharacter::ServerRequestBlockPlacealready accepts anFRotator— the previewed rotation is sent with the placement RPC.AReplicatedBlock::BlockRotationis replicated (DOREPLIFETIME) with anOnRep_BlockTransformthat applies the authoritative rotation to late-joiners and recovers from any dropped initial-spawn transform.UGridAuthoritySubsystem::SaveBlocknow accepts the full rotator and POSTsrotation_yaw,rotation_pitch,rotation_rollto the edge API. A legacyrotationfield (== yaw) is also sent for backwards compatibility.- On zone server startup,
AGridsServerGameMode::LoadBlocksFromDbreads all three axes (falling back to legacyrotationwhen the new fields are absent) and spawns blocks with the exact rotation they had.
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:
| Action | Behavior |
|---|---|
| Edit | Enters build mode (if needed), selects the block, and opens the Edit Object panel |
| Duplicate | Re-places an identical block (same shape, texture, rotation, scale) |
| Delete | Removes 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:
- Face target — choose All faces or a single face. Face buttons auto-hide for shapes with fewer faces (
GetFaceCountForShape— e.g. a sphere has 1 face, a cube has 6). - Texture… — opens the asset picker; the chosen marketplace texture is applied to the target face(s).
- Tint — eight color swatches multiply the base color of the target face(s).
- Effect — a preset material look: None, Glow (subtle emissive), Neon (bright emissive), Metal (metallic + glossy), or Matte (fully rough).
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.
- Toggle it with the grid button in the build toolbar (
build:grid-toggle,{ enabled }). gridVisibleis included inbuild-mode:enterso the overlay reflects the current state.
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.
| Tool | Behaviour |
|---|---|
| Block | The original placement preview — LMB places one block at the crosshair. |
| Wall | CAD-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:
- Length presets (2/4/6/8 m) — per-segment wall length (
build:wall-length,{ meters }). - A Bend slider — curve offset in cm (
build:wall-bend,{ bend }), with a straighten button. - A live readout of the in-progress run length and angle, plus a Cancel button (
build:wall-cancel).
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:
- distance — total run length in meters, at the run midpoint.
- angle — signed bend angle in degrees, at the anchor (only when bent).
- tag — segment count, at the cursor end.
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.
| Keybind | Action |
|---|---|
| Ctrl+Z | Undo (re-applies the inverse command) |
| Ctrl+Y / Ctrl+Shift+Z | Redo |
| Ctrl+C | Copy the current selection to the clipboard |
| Ctrl+V | Paste (each paste steps the offset so copies don't stack) |
| Ctrl+D | Duplicate the selection in place (offset one cell) |
| Ctrl+Shift+D | Array — four copies along +X at 1 m spacing |
| 1 / 2 / 3 | Select / 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:
- Hold Alt — the cursor appears and a cyan aim ray draws from the player toward whatever the cursor points at, with a green box around the block under the cursor (the hover target). Releasing Alt hides the ray and box.
- Alt + left-click — selects the hovered block. Shift+Alt+click adds/removes from a multi-selection. Clicking empty space clears the selection.
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:
- The name is read from the replicated
DisplayUsernameand projected to screen above the pawn's head. - A small backdrop tile plus a 4-direction text outline keeps names legible against any background.
- A pulsing green voice icon appears to the left of the name while that player is talking (driven by the replicated
AGridsPlayerState::bIsTalking).
Distance, fade & occlusion
By default nameplates behave like real spatial labels rather than pure wallhacks:
- Names fade out with distance, starting at
FadeStartDistance(default 2500 cm). - Names are culled past
grids.Nameplates.MaxDistance(default 8000 cm). - Names are hidden when occluded by world geometry (a line trace from the view to the player's head).
CVars
| CVar | Default | Effect |
|---|---|---|
grids.Nameplates.Enabled | 1 | Master toggle for all nameplate drawing |
grids.Nameplates.MaxDistance | 8000 | Max draw distance in cm |
grids.Nameplates.SeeThrough | 0 | When 1, skips the occlusion trace so names render through walls (pure ESP) |
grids.Nameplates.ShowOwn | 0 | When 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):
| Mode | Behavior |
|---|---|
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 ID | Default key | Category |
|---|---|---|
voice.pushtotalk | V | Voice |
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.