Skip to main content

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 ClassGameplay PatternDriven By
UERPInteractionWidgetBaseInteraction prompts (single action, multi-action list, wheel)FERPInteractionDescriptor
UERPChallengeWidgetBaseChallenge progress display (hold ring, press counter)Progress float (0..1)
UERPTargetingWidgetBaseReticle, lock-on, target infoFERPTargetDescriptor

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 vs. HUD widgets
  • 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):

FieldTypePurpose
ObjectNameFText"Wooden Door", "Iron Chest"
ObjectDescriptionFTextShown in rich tooltips
ObjectIconTSoftObjectPtr<UTexture2D>Interactable icon

Actions (one entry per available interaction):

Field on FERPInteractionActionTypePurpose
ActionNameFText"Open", "Inspect", "Loot"
ActionDescriptionFTextTooltip for this action
ActionIconTSoftObjectPtr<UTexture2D>Per-action icon
InputActionUInputAction*Key binding source — use GetInputActionDisplayKey()
ChallengeUERPInteractionChallenge*null = simple press; otherwise hold, multi-press, etc.

Presentation mode:

FieldTypeMeaning
MultiActionModeEERPMultiActionModeDirectKeys = each action has its own key; Wheel = one input opens a radial selector

Helper methods:

  • IsValid() — returns true if there is at least one action
  • GetPrimaryAction() — returns Actions[0], or null if empty
  • FindAction(UInputAction*) — finds an action by its InputAction pointer
  • FindActionIndex(UInputAction*) — returns index, or INDEX_NONE

UERPInteractionWidgetBase

Base class for all interaction prompt widgets.

Events to Override

EventWhen CalledWhat to Implement
OnDescriptorUpdated(Descriptor)Descriptor changesUpdate ObjectName, ObjectDescription, ObjectIcon
OnActionsUpdated(Actions)Action list changesBuild action rows (for multi-action) or wheel slices
OnActionProgress(Action, Progress)Challenge progressingRoute progress to the matching challenge sub-widget
OnFocusChanged(bFocused)Focus gained/lostShow/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

  1. Content Browser → Widget Blueprint
  2. Reparent to ERPInteractionWidgetBase (Class Settings → Parent Class)
  3. 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

  1. Widget Blueprint → Reparent to ERPInteractionWidgetBase
  2. 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

  1. Widget Blueprint → Reparent to ERPInteractionWidgetBase
  2. 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

  1. Widget Blueprint → Reparent to ERPChallengeWidgetBase
  2. 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_HoldChallenge widget as a child (hidden by default)
  • In OnActionsUpdated: show it only if Action.Challenge is a UERPHoldChallenge
  • In OnActionProgress: call HoldChallengeWidget -> 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

  1. Widget Blueprint → Reparent to ERPChallengeWidgetBase
  2. 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

  1. Widget Blueprint → Reparent to ERPTargetingWidgetBase
  2. 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()MultiActionModeWidget Class Used
1AnySingleActionWidgetClass
2+DirectKeysMultiActionWidgetClass
2+WheelWheelWidgetClass
(override set)AnyWidgetClassOverride

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

PatternWidget BaseData SourceHosted By
Single-Action PromptERPInteractionWidgetBaseDescriptor.Actions[0]Actor (component) or HUD presenter
Multi-Action ListERPInteractionWidgetBaseDescriptor.Actions[]Actor (component) or HUD presenter
WheelERPInteractionWidgetBaseDescriptor.Actions[]HUD presenter (toggled via OnWheelRequested)
Hold ChallengeERPChallengeWidgetBaseProgress (0..1)Embedded in parent widget row
Multi-Press ChallengeERPChallengeWidgetBaseProgress (0..1)Embedded in parent widget row
Target ReticleERPTargetingWidgetBaseFERPTargetDescriptorHUD presenter
Target Info PanelERPTargetingWidgetBaseFERPTargetDescriptorHUD presenter

Next Steps

Set up interactions with Interaction Guide.

Set up targeting with Targeting Guide.

Check complete API in API Reference.