Widget Guide
Complete guide to building interaction and targeting UI using the plugin's widget base classes.
Overview
The plugin provides optional widget base classes to accelerate UI development. You are not required to use them. You can build your UI entirely from scratch using domain events (OnDescriptorChanged, OnDomainCandidateChanged) and any UMG widget.
If you do use the provided base classes:
| Base Class | Gameplay Pattern | Driven By |
|---|---|---|
UERPInteractionWidgetBase | Interaction prompts (single action, multi-action list, wheel) | FERPInteractionDescriptor |
UERPChallengeWidgetBase | Challenge progress display (hold ring, press counter) | Progress float (0..1) |
UERPTargetingWidgetBase | Reticle, lock-on, target info | FERPTargetDescriptor |
Key principles:
- Widgets are passive — they receive data, they don't fetch it
- Widgets do not manage their own lifecycle — a presenter creates, shows, hides, and destroys them
- All data comes from descriptors — the data contract between game logic and UI
- All hooks are
BlueprintNativeEvent— override in Blueprint or C++
- Actor-anchored (
UERPInteractableComponent): The widget floats near the interactable object. Assign widget classes directly on the component — no presenter needed. Widget lifecycle and progress forwarding are fully automatic. - Player-side HUD (this guide): The widget is in a fixed position on the HUD, not tied to any actor's world position.
Both can coexist. A common setup is actor-anchored for interaction prompts and HUD-based for targeting reticles.
Architecture
[Perception] → [Domain Component] → [Presenter] → [Widget]
detects validates wires displays
candidates eligibility events UI
tracks state manages
fires events lifecycle
For interaction widgets anchored to the actor, UERPInteractableComponent acts as the presenter automatically. For targeting or HUD-based interaction widgets, you write the presenter. This guide shows you exactly how.
The Interaction Descriptor
Every interaction widget is driven by FERPInteractionDescriptor. Understanding its structure is essential:
Object identity (shown in title, tooltip, wheel center):
| Field | Type | Purpose |
|---|---|---|
ObjectName | FText | "Wooden Door", "Iron Chest" |
ObjectDescription | FText | Shown in rich tooltips |
ObjectIcon | TSoftObjectPtr<UTexture2D> | Interactable icon |
Actions (one entry per available interaction):
| Field on FERPInteractionAction | Type | Purpose |
|---|---|---|
ActionName | FText | "Open", "Inspect", "Loot" |
ActionDescription | FText | Tooltip for this action |
ActionIcon | TSoftObjectPtr<UTexture2D> | Per-action icon |
InputAction | UInputAction* | Key binding source — use GetInputActionDisplayKey() |
Challenge | UERPInteractionChallenge* | null = simple press; otherwise hold, multi-press, etc. |
Presentation mode:
| Field | Type | Meaning |
|---|---|---|
MultiActionMode | EERPMultiActionMode | DirectKeys = each action has its own key; Wheel = one input opens a radial selector |
Helper methods:
IsValid()— returns true if there is at least one actionGetPrimaryAction()— returnsActions[0], or null if emptyFindAction(UInputAction*)— finds an action by its InputAction pointerFindActionIndex(UInputAction*)— returns index, or INDEX_NONE
UERPInteractionWidgetBase
Base class for all interaction prompt widgets.
Events to Override
| Event | When Called | What to Implement |
|---|---|---|
OnDescriptorUpdated(Descriptor) | Descriptor changes | Update ObjectName, ObjectDescription, ObjectIcon |
OnActionsUpdated(Actions) | Action list changes | Build action rows (for multi-action) or wheel slices |
OnActionProgress(Action, Progress) | Challenge progressing | Route progress to the matching challenge sub-widget |
OnFocusChanged(bFocused) | Focus gained/lost | Show/hide animations |
Helper Methods
GetInputActionDisplayKey(InputAction) -> FText // "E", "F", "LMB", etc.
GetPrimaryActionDisplayKey() -> FText // Shortcut for Actions[0].InputAction key
GetDescriptor() -> FERPInteractionDescriptor // Read cached descriptor
IsFocused() -> bool
Pattern 1: Single-Action Prompt
Use case: "Press E to Open" — one interactable, one key.
Base class: UERPInteractionWidgetBase
Step 1: Create the Widget Blueprint
- Content Browser → Widget Blueprint
- Reparent to
ERPInteractionWidgetBase(Class Settings → Parent Class) - Name:
WBP_SinglePrompt
Step 2: Design the Layout
[HorizontalBox]
[Image: ObjectIcon]
[VerticalBox]
[TextBlock: ObjectName] // "Wooden Door"
[HorizontalBox]
[TextBlock: KeyBadge] // "[E]"
[TextBlock: ActionName] // "Open"
Step 3: Override Events
OnDescriptorUpdated — update the object identity:
Event: On Descriptor Updated (Descriptor)
-> Set Text (ObjectName): Descriptor.ObjectName
// Load icon if present
-> Branch: IsValid (Descriptor.ObjectIcon)
True:
-> Async Load Asset (Descriptor.ObjectIcon)
-> Set Brush From Texture (ObjectIcon, Loaded Texture)
-> Set Visibility (ObjectIcon): Visible
False:
-> Set Visibility (ObjectIcon): Collapsed
OnActionsUpdated — update the single action row (Actions[0]):
Event: On Actions Updated (Actions)
-> Branch: Actions is NOT empty
True:
-> Set Text (ActionName): Actions[0].ActionName
-> Set Text (KeyBadge): GetInputActionDisplayKey(Actions[0].InputAction)
OnFocusChanged (optional — default toggles visibility):
Event: On Focus Changed (bNewFocused)
-> Branch: bNewFocused
True: -> Play Animation (FadeIn)
False: -> Play Animation (FadeOut)
Step 4: Build the Presenter
Actor-anchored (simplest): Set SingleActionWidgetClass = WBP_SinglePrompt on ERPInteractableComponent. No further wiring needed.
Player-side HUD:
Event BeginPlay
-> Create Widget (WBP_SinglePrompt)
-> Add to Viewport
-> Bind OnDescriptorChanged on InteractionComponent
-> Bind OnDomainCandidateChanged on InteractionComponent
Event: OnDescriptorChanged (Candidate, Descriptor, ChannelId)
-> Widget -> UpdateFromDescriptor(Descriptor)
-> Widget -> SetFocused(true)
Event: OnDomainCandidateChanged (Previous, New, ChannelId)
-> Branch: NOT IsValid(New)
True: -> Widget -> SetFocused(false)
Input: IA_Interact (Started) -> StartInteractionAttempt(IA_Interact)
Input: IA_Interact (Completed) -> StopInteractionAttempt(IA_Interact)
Pattern 2: Multi-Action List (DirectKeys)
Use case: "E to Open / F to Inspect" — multiple keys shown in a list.
Base class: UERPInteractionWidgetBase
Step 1: Create the Widget Blueprint
- Widget Blueprint → Reparent to
ERPInteractionWidgetBase - Name:
WBP_MultiActionPrompt
Step 2: Design the Layout
[VerticalBox]
[TextBlock: ObjectName]
[VerticalBox: ActionList] // Populated dynamically from OnActionsUpdated
// Each row (created via Widget entry or custom widget):
[HorizontalBox]
[TextBlock: KeyBadge] // "[E]"
[TextBlock: ActionName] // "Open"
[ChallengeSlot] // Optional: challenge sub-widget per row
Step 3: Override Events
OnDescriptorUpdated — update the object header:
Event: On Descriptor Updated (Descriptor)
-> Set Text (ObjectName): Descriptor.ObjectName
OnActionsUpdated — build action rows:
Event: On Actions Updated (Actions)
-> Clear Children (ActionList)
-> For Each Action in Actions:
-> Create Widget (WBP_ActionRow)
-> ActionRow -> Setup(Action.ActionName, GetInputActionDisplayKey(Action.InputAction))
-> Add Child (ActionList, ActionRow)
OnActionProgress — route progress to the right row's challenge widget:
Event: On Action Progress (Action, Progress)
// Find the row corresponding to Action, forward progress to its challenge sub-widget
-> For Each ActionRow in ActionList:
-> Branch: ActionRow.InputAction == Action
True: -> ActionRow.ChallengeWidget -> SetChallengeProgress(Progress)
Step 4: Build the Presenter
Actor-anchored: Set MultiActionWidgetClass = WBP_MultiActionPrompt on ERPInteractableComponent.
Player-side HUD: Same wiring as Pattern 1, but bind OnInteractionProgress too:
Event: OnInteractionProgress (Action, Progress)
-> Widget -> OnActionProgressChanged(Action, Progress)
Pattern 3: Wheel (Radial Selector)
Use case: NPC with multiple dialogue/trade/quest options; object with contextual actions.
Base class: UERPInteractionWidgetBase
The wheel opens in response to OnWheelRequested — not OnDescriptorChanged. The player selects a slice; the widget calls SubmitWheelSelection(SelectedAction) on the interaction component.
Step 1: Create the Widget Blueprint
- Widget Blueprint → Reparent to
ERPInteractionWidgetBase - Name:
WBP_ActionWheel
Step 2: Design the Layout
[CanvasPanel]
[Image: Background]
[TextBlock: CenterLabel] // ObjectName / ObjectDescription
[WrapBox or manual layout: Slices]
// N slices, one per action, arranged radially
[Button/Image: Slice_0] -> label ActionName, icon, key badge
[Button/Image: Slice_1]
...
Step 3: Override Events
OnActionsUpdated — build wheel slices:
Event: On Actions Updated (Actions)
// Build N slices arranged radially
-> For Each Action in Actions:
-> Create Slice Widget
-> Set ActionName, ActionIcon, GetInputActionDisplayKey(Action.InputAction)
-> Store InputAction reference on the slice
Wheel selection (called from within a slice's click/confirm handler):
Slice On Confirmed:
-> Get Owning Player -> Get Interaction Component
-> InteractionComponent -> SubmitWheelSelection(Slice.StoredInputAction)
Cancel (back button / escape):
-> InteractionComponent -> CancelWheelSelection()
Step 4: Build the Presenter
The wheel widget is opened via OnWheelRequested, not OnDescriptorChanged.
Event: OnWheelRequested (Actions)
-> WheelWidget -> UpdateFromDescriptor(CurrentDescriptor) // for ObjectName/icon
-> WheelWidget -> SetVisibility(Visible)
// UpdateFromDescriptor internally calls OnActionsUpdated(Actions)
Event: OnWheelDismissed (bCancelled)
-> WheelWidget -> SetVisibility(Collapsed)
The wheel widget does not need to be added/removed — create it once at BeginPlay, toggle visibility on OnWheelRequested / OnWheelDismissed.
Pattern 4: Hold Challenge Sub-Widget
Use case: A "Hold E" ring or progress bar embedded inside an interaction prompt row.
Base class: UERPChallengeWidgetBase
This widget is embedded inside a prompt widget (Pattern 1, 2, or 3), not used standalone.
Step 1: Create the Widget Blueprint
- Widget Blueprint → Reparent to
ERPChallengeWidgetBase - Name:
WBP_HoldChallenge
Step 2: Design the Layout
[CanvasPanel / Overlay]
[CircularThrobber or ProgressBar: HoldRing] // Fills as player holds
Step 3: Override Events
Event: On Challenge Progress Changed (Progress)
-> Set Percent (HoldRing): Progress
Event: On Challenge Completed (bSuccess)
-> Branch: bSuccess
True: -> Play Animation: CompletionFlash
False: -> Play Animation: CancelledFlash
Event: On Challenge Reset
-> Set Percent (HoldRing): 0.0
Step 4: Embed in Parent Widget
In your WBP_SinglePrompt or WBP_ActionRow:
- Add a
WBP_HoldChallengewidget as a child (hidden by default) - In
OnActionsUpdated: show it only ifAction.Challengeis aUERPHoldChallenge - In
OnActionProgress: callHoldChallengeWidget -> SetChallengeProgress(Progress)
Pattern 5: Multi-Press Challenge Sub-Widget
Use case: A press counter ("3 / 5") embedded in an interaction prompt.
Base class: UERPChallengeWidgetBase
Step 1: Create the Widget Blueprint
- Widget Blueprint → Reparent to
ERPChallengeWidgetBase - Name:
WBP_MultiPressChallenge
Step 2: Design the Layout
[HorizontalBox]
[TextBlock: CounterText] // "3 / 5"
[ProgressBar: PressBar]
Step 3: Override Events
Event: On Challenge Progress Changed (Progress)
-> Set Percent (PressBar): Progress
// Counter text needs RequiredPresses — pass it when configuring this widget, or read from parent descriptor
-> Set Text (CounterText): Format("{0} / {1}", Round(Progress * RequiredPresses), RequiredPresses)
Event: On Challenge Reset
-> Set Percent (PressBar): 0.0
-> Set Text (CounterText): "0 / N"
Pattern 6: Target Reticle
Use case: Lock-on marker on the target actor (world space or screen position).
Base class: UERPTargetingWidgetBase
Step 1: Create the Widget Blueprint
- Widget Blueprint → Reparent to
ERPTargetingWidgetBase - Name:
WBP_TargetReticle
Step 2: Override Hooks
Event: On Target Active Changed (bActive)
-> Branch: bActive
True: -> Play Animation (LockOnAnim)
False: -> Play Animation (LockOffAnim)
Step 3: Build the Presenter
Event BeginPlay
-> Create Widget (WBP_TargetReticle)
-> Add to Viewport
-> TargetingComponent -> Bind OnDomainCandidateChanged
-> TargetingComponent -> Bind OnDescriptorChanged
Event: OnDomainCandidateChanged (Previous, New, ChannelId)
-> ReticleWidget -> SetTargetActor(New)
-> ReticleWidget -> SetTargetActive(IsValid(New))
Event: OnDescriptorChanged (Candidate, Descriptor, ChannelId)
-> ReticleWidget -> UpdateFromDescriptor(Descriptor)
Event Tick (position reticle):
-> Project World Location (TargetActor.GetActorLocation()) to Screen
-> Set Position in Viewport (ReticleWidget, ScreenPosition)
Choosing the Right Widget
Widget class selection on UERPInteractableComponent
| Actions.Num() | MultiActionMode | Widget Class Used |
|---|---|---|
| 1 | Any | SingleActionWidgetClass |
| 2+ | DirectKeys | MultiActionWidgetClass |
| 2+ | Wheel | WheelWidgetClass |
| (override set) | Any | WidgetClassOverride |
Player-side HUD selection
When driving from OnDescriptorChanged on the PlayerController:
Event: OnDescriptorChanged (Candidate, Descriptor, ChannelId)
-> Branch: Actions.Num() > 1 AND MultiActionMode == Wheel
True: -> Show WheelWidget (but only open on OnWheelRequested)
-> Branch: Actions.Num() > 1
True: -> Show MultiActionWidget
-> Otherwise:
-> Show SingleActionWidget
C++ Presenter Example
Minimal presenter component that wires everything automatically:
#pragma once
#include "CoreMinimal.h"
#include "Components/ActorComponent.h"
#include "Domains/Interaction/ERPInteractionDescriptor.h"
#include "ERPInteractionPresenter.generated.h"
class UERPInteractionComponent;
class UERPInteractionWidgetBase;
class UInputAction;
UCLASS(Blueprintable, meta=(BlueprintSpawnableComponent))
class YOURGAME_API UERPInteractionPresenter : public UActorComponent
{
GENERATED_BODY()
public:
UPROPERTY(EditAnywhere, Category = "Presenter")
TSubclassOf<UERPInteractionWidgetBase> WidgetClass;
protected:
virtual void BeginPlay() override;
virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override;
private:
UPROPERTY(Transient) TObjectPtr<UERPInteractionWidgetBase> Widget;
UPROPERTY(Transient) TObjectPtr<UERPInteractionComponent> InteractionComponent;
UFUNCTION()
void OnDescriptorChanged(AActor* Candidate, const FERPInteractionDescriptor& Descriptor, FName ChannelId);
UFUNCTION()
void OnCandidateChanged(AActor* Previous, AActor* New, FName ChannelId);
UFUNCTION()
void OnInteractionProgress(const UInputAction* Action, float Progress);
};
#include "ERPInteractionPresenter.h"
#include "Domains/Interaction/ERPInteractionComponent.h"
#include "UI/Interaction/ERPInteractionWidgetBase.h"
#include "GameFramework/PlayerController.h"
void UERPInteractionPresenter::BeginPlay()
{
Super::BeginPlay();
InteractionComponent = GetOwner()->FindComponentByClass<UERPInteractionComponent>();
if (!InteractionComponent || !WidgetClass) return;
APlayerController* PC = Cast<APlayerController>(GetOwner());
if (!PC) return;
Widget = CreateWidget<UERPInteractionWidgetBase>(PC, WidgetClass);
if (Widget)
{
Widget->AddToViewport();
Widget->SetFocused(false);
}
InteractionComponent->OnDescriptorChanged.AddDynamic(this, &UERPInteractionPresenter::OnDescriptorChanged);
InteractionComponent->OnDomainCandidateChanged.AddDynamic(this, &UERPInteractionPresenter::OnCandidateChanged);
InteractionComponent->OnInteractionProgress.AddDynamic(this, &UERPInteractionPresenter::OnInteractionProgress);
}
void UERPInteractionPresenter::EndPlay(const EEndPlayReason::Type EndPlayReason)
{
if (Widget) { Widget->RemoveFromParent(); Widget = nullptr; }
Super::EndPlay(EndPlayReason);
}
void UERPInteractionPresenter::OnDescriptorChanged(AActor* Candidate,
const FERPInteractionDescriptor& Descriptor, FName ChannelId)
{
if (Widget)
{
Widget->UpdateFromDescriptor(Descriptor);
Widget->SetFocused(true);
}
}
void UERPInteractionPresenter::OnCandidateChanged(AActor* Previous, AActor* New, FName ChannelId)
{
if (Widget && !New) Widget->SetFocused(false);
}
void UERPInteractionPresenter::OnInteractionProgress(const UInputAction* Action, float Progress)
{
if (Widget) Widget->OnActionProgressChanged(Action, Progress);
}
Summary
| Pattern | Widget Base | Data Source | Hosted By |
|---|---|---|---|
| Single-Action Prompt | ERPInteractionWidgetBase | Descriptor.Actions[0] | Actor (component) or HUD presenter |
| Multi-Action List | ERPInteractionWidgetBase | Descriptor.Actions[] | Actor (component) or HUD presenter |
| Wheel | ERPInteractionWidgetBase | Descriptor.Actions[] | HUD presenter (toggled via OnWheelRequested) |
| Hold Challenge | ERPChallengeWidgetBase | Progress (0..1) | Embedded in parent widget row |
| Multi-Press Challenge | ERPChallengeWidgetBase | Progress (0..1) | Embedded in parent widget row |
| Target Reticle | ERPTargetingWidgetBase | FERPTargetDescriptor | HUD presenter |
| Target Info Panel | ERPTargetingWidgetBase | FERPTargetDescriptor | HUD presenter |
Next Steps
Set up interactions with Interaction Guide.
Set up targeting with Targeting Guide.
Check complete API in API Reference.