Crosshair, Reticle, and Interaction Prompts¶
A crosshair is local screen presentation. An interaction prompt is usually the visible result of a local trace asking a world target what the player can do.
At a glance
Use this for: center reticles, "Press E" prompts, pickup labels,
target outlines, interact hints, locked/blocked feedback -
Main owner: local PlayerController/HUD for the widget; local pawn or
controller for the trace; target actor/component for the prompt data -
Can fail by: stale targets, wrong trace, hidden targets, duplicate
widget creation, or trusting client prompts as server authority -
Official docs: Create Widget,
Add to Viewport,
and Line Trace By Channel
The one-minute version¶
- The crosshair/reticle widget usually belongs to the local player's HUD. Create it once and update it; do not create a new widget every time the trace changes.
- A prompt target comes from a trace, overlap, selection system, or explicit focus. For first-person games, a camera-centered line trace is the usual first version.
- The prompt text should come from the target through a clear contract, often a
Blueprint Interface such as
BPI_Interactable.GetInteractionPrompt. - UI shows "what the local player appears able to do." The server still validates the real interaction when the player presses the input.
- Prompt priority matters when several targets are hit or overlapped. Pick one rule and make it visible in debugging.
- Hide prompts for invalid, blocked, too-far, dead, hidden, same-team, or line-of-sight-failed targets instead of letting stale text linger.
Split the pieces¶
Do not put the whole system in the widget. A clean first version has three parts:
Local PlayerController / HUD:
creates WBP_Reticle once
exposes UpdatePrompt(PromptData)
Local pawn/controller interaction trace:
finds current candidate target
asks target for prompt data
sends prompt data to WBP_Reticle
Interactable target actor/component:
owns display name, action text, allowed range, icon, priority, enabled state
The widget draws. The trace selects. The target describes itself. The server validates actions.
Fixed reticle vs target prompt¶
A fixed reticle can be pure viewport UI:
PlayerController BeginPlay
-> Create Widget WBP_Reticle
-> Store ReticleWidget
-> Add to Viewport
Then interaction data updates that existing widget:
Interaction trace result changed
-> ReticleWidget.SetPromptText("Open Door")
-> ReticleWidget.SetPromptVisible(true)
That is different from a world-space label over the door. The reticle stays in the center of the screen. The prompt can still describe whatever the center trace found.
The trace path¶
For a first-person or over-the-shoulder interaction:
Every 0.05 seconds, or on Tick while interaction is enabled:
CameraLocation = get player camera location
CameraForward = get camera forward vector
End = CameraLocation + CameraForward * InteractionRange
LineTraceByChannel(Start = CameraLocation, End = End)
if hit actor implements BPI_Interactable:
ask CanShowPrompt / GetPromptData
else:
clear prompt
You can run this every frame for a small prototype, but a short timer interval is usually enough for prompts and easier to reason about. Do not hide expensive world searches in a widget binding.
For mouse-driven games, the target may come from cursor hit testing instead of center-screen camera tracing. The same ownership rule applies: find the target outside the widget, then push display data into the widget.
Ask the target what to show¶
Use a contract so the trace does not cast through every possible target class:
BPI_Interactable:
CanInteract(PlayerPawn) -> boolean
GetInteractionPrompt(PlayerPawn) -> PromptData
ServerInteract(PlayerController or Pawn) request path handled elsewhere
FInteractionPromptData:
DisplayText
SubText
Icon
Priority
bBlocked
BlockedReason
Examples:
| Target | Prompt |
|---|---|
| Door unlocked | Open |
| Door locked | Locked or Requires Keycard |
| Pickup | Pick up Medkit |
| Generator | Repair Generator plus progress text |
| Friendly NPC | Talk |
| Dead or disabled target | no prompt, or a blocked prompt if useful |
If an actor can have many interactable parts, put the interface on components or return component-specific data from the hit result.
Priority and stale prompts¶
Prompt systems fail when they do not decide which candidate wins.
Common priority rules:
- center-screen blocking hit beats broad overlap candidates;
- closer target beats farther target;
- "usable now" beats "blocked" if both are visible;
- explicit focus/highlight beats background pickups;
- target-defined priority handles quest items or critical prompts.
Write the rule as one function:
ChooseBestPrompt(Candidates):
remove invalid / hidden / out-of-range / line-of-sight-failed targets
sort by Priority, then distance, then screen-center angle
return first candidate or None
When the best target changes, clear the old one before showing the new one:
if CurrentTarget != NewTarget:
CurrentTarget = NewTarget
ReticleWidget.UpdatePrompt(NewPrompt or Empty)
Never let "last frame had a door" keep drawing "Open Door" after the trace now hits a wall.
Input devices and wording¶
Prompt text is local presentation, so it should adapt to local input device:
Keyboard/mouse: "Press E"
Gamepad: "Press X"
Touch: "Tap"
VR: "Trigger"
Keep the action meaning separate from the physical key:
PromptData.Action = Interact
HUD asks input settings/icon table for the current local display key
That avoids hard-coding "Press E" into every door and pickup. The target says
"Interact with me"; the local UI says which button currently performs
Interact.
Multiplayer boundary¶
The prompt is a local prediction of usefulness. The interaction itself is a server-authoritative request when gameplay state changes:
Client local trace:
sees Door_12
shows "Open Door"
Client presses Interact:
owned pawn/controller sends ServerTryInteract(Door_12)
Server:
validates distance, line of sight, lock state, team, alive state
changes door/objective/inventory state
Clients:
replicated state updates world, prompt eligibility, sounds, and UI
The local trace can be wrong by the time the server receives the request. Handle server rejection with a small local feedback path, then refresh the prompt from current state.
When it fails, and what failure looks like¶
| Symptom | Likely cause |
|---|---|
| Prompt never appears | Trace starts from pawn body instead of camera, trace channel misses target, target lacks interface, or range is too short. |
| Prompt stays after looking away | You update on hit but never clear on miss or invalid target. |
| Prompt flickers between targets | No stable priority, trace hits child components unpredictably, or range/visibility gates fight each other. |
| Pressing interact does nothing | The prompt is local UI, but no input event/server request is wired. |
| Client can open doors through walls | Server trusted local prompt instead of validating line of sight/range. |
| Prompt shows hidden/dead objects | Target state is stale or the eligibility filter ignores hidden/dead/disabled state. |
| UI duplicates | Create Widget runs every time a target is found instead of once during HUD setup. |
| Split-screen prompt shows the wrong player key | Widget or projection uses player index 0 instead of the owning local player. |
Debug with a line trace debug draw, printed candidate actor names, current prompt data, and the exact machine that handles the final interact request.
What prompts do not do¶
- A prompt does not prove the server will accept the action.
- A reticle widget does not find targets by itself.
- A line trace does not know which text, icon, or input key should be shown.
- A Blueprint Interface does not store state or replicate by itself.
- A hidden prompt does not disable target collision or gameplay interaction.
- Showing "Press E" does not bind the
Interactinput action.
Lookalikes - which one do I want?¶
| Tool | Use when | Watch out |
|---|---|---|
| Viewport reticle widget | Crosshair/prompt belongs to the local screen. | Create once; update display data. |
| Widget Component label | Text or health bar should sit over/in a world actor. | It is actor-attached UI, not a fixed crosshair. |
| Line Trace By Channel | You need a precise center/camera target. | It only returns hit data; prompt rules are yours. |
| Overlap list | A nearby-use system should choose among actors in range. | Needs priority and stale-target cleanup. |
| Widget Interaction Component | You need to click a world-space UMG panel. | It simulates UI input, not generic gameplay prompt selection. |
| Blueprint Interface | Many target classes should answer the same prompt/interact questions. | It is a call contract, not authority or saved state. |
Rule of thumb: the HUD owns the reticle, the trace owns target selection, the target owns prompt data, and the server owns final gameplay results.
Going deeper¶
- Create Widget + Add to Viewport - create and store the local reticle/HUD once.
- Line Trace By Channel - camera trace start/end math, channels, hit results, and debug drawing.
- Blueprint Interfaces - prompt and interact contracts without casting through every target class.
- Widget Interaction Component - when the target is a clickable world UMG panel, not a simple interactable actor.
- First weapon fire pattern and impact cleanup - the same camera/authority split applied to weapon hits.
- Blueprint Custom Events as RPCs - client requests and server validation.
- Official docs: Create Widget, Add to Viewport, Line Trace By Channel, and UWidgetLayoutLibrary.
- Engine source (requires engine access - we do not reproduce it here):
viewport widget creation/display uses
UUserWidgetandUWidgetBlueprintLibraryunderEngine/Source/Runtime/UMG/; widget-space projection helpers such asUWidgetLayoutLibrary::ProjectWorldLocationToWidgetPositionlive inEngine/Source/Runtime/UMG/Private/WidgetLayoutLibrary.cpp; line traces route throughUWorld::LineTraceSingleByChannelinEngine/Source/Runtime/Engine/Private/Collision/WorldCollision.cpp.