Interaction Integration

CrimsonCore is where the abstract CrimsonInteraction system becomes a working interaction loop. The integration ships:

  • UCrimsonCoreGameplayAbility_Interact - concrete persistent scan + dispatch ability. Picks between top-down and forward-line-trace strategies, binds two input tags (quick-press + context-menu), broadcasts Message.Interaction.OptionsChanged for HUD widgets. Prompt presentation is switchable via PromptMode (on-actor world indicators by default).
  • UCrimsonCoreGameplayAbility_InteractionDuration - thin subclass that defaults the UIExtension slot + success tag and forwards NotifyInteractionSuccess to the target on completion. Not abstract: assign it directly as an option's InteractionActionAbility for a working default channel. Channels are press-and-forget; opt-in cancels: MaxDistanceFromTarget (walk-away, default 600) and bCancelWhenInputReleased (hold-to-mine).
  • UCrimsonCoreGameplayAbility_InteractionHandler - fire-and-forget handler ability; default PickupHandlerAbility on pickups.
  • FCrimsonInteractionOptionsChangedMessage - broadcast struct so the prompt widget can listen without coupling to the ability.
  • Collision channel Crimson_TraceChannel_Interaction + profiles Interactable_OverlapDynamic / Interactable_BlockDynamic (profiles ship in CrimsonInteraction's Config/Engine.ini; the channel and any same-name profile overrides are project-level).
  • Native tags InputTag.Interact, InputTag.Interact.OpenMenu, HUD.Interaction.Prompt, HUD.Interaction.Progress, Ability.Interaction.SuccessEvent.Default, Message.Interaction.OptionsChanged.

Quick-press vs context menu

Two input bindings on the same persistent ability: quick-press fires Options[0] immediately (Diablo / PoE pickup feel); OpenMenu opens a world-anchored context menu listing every available option via UCrimsonCoreContextMenuSubsystem::ShowContextMenuForActor. If only one option exists, OpenMenu falls through to the quick-press path so single-option pickups don't show a useless one-row menu.

cpp
// BP defaults on BP_GA_Interact:
InputTag = InputTag.Interact; // quick-press -> Options[0]
ContextMenuInputTag = InputTag.Interact.OpenMenu; // right-click / F -> context menu
ContextMenuWidgetClass = WBP_ContextMenu_World; // optional: unset falls back to
// UCrimsonUISettings::DefaultWorldContextMenuClass

Multi-action pickups

ACrimsonCorePickupActor::SecondaryActions is an Instanced TArray<UCrimsonPickupAction*> exposed in Blueprint defaults (shipped: UCrimsonPickupAction_Examine, _OpenBag, _PickUp, _TakeAll; subclass in Blueprint for more). Each available entry produces an extra FCrimsonInteractionOption in GatherInteractionOptions, so designers can add actions without C++. The primary pickup action stays as Options[0]; secondaries are Options[1..N] and reachable only through the OpenMenu input.

cpp
UCLASS(Abstract, Blueprintable, EditInlineNew, DefaultToInstanced)
class UCrimsonPickupAction : public UObject
{
UPROPERTY(EditDefaultsOnly) FText Label;
UPROPERTY(EditDefaultsOnly) FText SubText;
UPROPERTY(EditDefaultsOnly) TSubclassOf<UGameplayAbility> HandlerAbility;
virtual bool IsAvailable(const ACrimsonCorePickupActor* Pickup) const;
};

Each HandlerAbility should be a subclass of UCrimsonCoreGameplayAbility_InteractionHandler (or any ability that triggers on Ability.Interaction.Activate and reads TriggerEventData->Target).

Prompt widget - more-options hint

UCrimsonCoreInteractionPromptWidget has three optional BindWidgetOptional slots: MoreOptionsContainer (a UWidget wrapper - usually a horizontal box with a label + action widget), MoreOptionsActionWidget (a UCrimsonActionWidget), PromptSubText (a UCommonTextBlock under the prompt line for the top option's SubText; collapsed when empty or while the checklist shows), and RequirementsBox (a UVerticalBox the widget fills with one line per requirement of the top option, met or unmet, in RequirementFont with MetPrefix / UnmetPrefix and MetColor / UnmetColor; shown while something is unmet, or always with bShowRequirementsWhenMet). The shipped W_CrimsonInteractionPrompt has all of them and wears the context menu's look as a stack of separate plates in MI_UI_CtxMenu_Window (the glyph + prompt line, the reason in Panel_SubText, one plate per checklist line through RequirementChipBrush / RequirementChipPadding / RequirementChipSpacing, the more-options row), with the Crimson_TextStyle_Base / MenuRowSmall text styles, so prompt and menu read as one system, and the key glyph (ActionWidget) collapses while the top option is informational. Set OpenMenuInputAction in Class Defaults to your IA_Interact_OpenMenu. The container auto-shows only when more than one option is available, so simple pickups stay uncluttered.

Prompt widget - progress ring around the input key

No extra widget is needed: UCommonActionWidget already ships a progress layer behind the key icon. On the prompt widget's bound ActionWidget, set Progress Material Brush to a radial-fill material and Progress Material Param to its 0..1 scalar parameter - hold and duration-channel progress then renders as a ring around the input key, with no separate progress HUD element. The prompt widget drives the layer via OnActionProgress / OnActionComplete from Message.Interaction.Progress, filtered to the owning pawn (so it is multiplayer-safe on listen servers). For fully custom display, implement the On Interaction Progress event (or read CurrentProgress01).

Radial-fill material in one node
A minimal radial-fill material: RadialGradientExponential (or a SphereMask on VectorToRadialValue) compared against a scalar parameter, output to Opacity in a UI material domain. Set it as the ActionWidget's Progress Material Brush and name the parameter in Progress Material Param - the action widget creates the dynamic instance itself. Note: CommonUI only shows this layer for actions it classifies as holds (Enhanced Input Hold triggers); UCrimsonActionWidget also shows it whenever a progress material is authored, since Crimson holds are ability-timer driven on trigger-less actions.

Setup steps

  • Create a Blueprint subclass of UCrimsonCoreGameplayAbility_Interact. Set ScanStrategy (TopDown or SingleLineTrace), trace profiles, scan rate/range, and the InputTag (defaults to InputTag.Interact).
  • Add the ability to a UCrimsonAbilitySet granted on pawn spawn - auto-activates via OnAvatarSet/OnGiveAbility.
  • In your UCrimsonInputConfig, map InputTag.Interact to an IA_Interact Enhanced Input action.
  • On each interactable object (pickup, door, NPC), implement ICrimsonInteractableTarget::GatherInteractionOptions. Set InputType = Instant for press, or Hold + InputHoldDuration, or set InteractionActionAbility (your UCrimsonCoreGameplayAbility_InteractionDuration subclass) for a channeled interaction. Gate an option with Conditions (Has Item, Attribute Compare, a tag query, And / Or / Not): a failed one shows the option as a message ("Locked", "No Access (Owner: X)") with no key glyph, or hides it, and the handler / duration abilities re-check it on the server.
  • Configure the interactable's overlap component on the Interactable_OverlapDynamic profile so the scan trace finds it.

Collision channel + profiles

The channel is project-level config
The two profiles ship in CrimsonInteraction's Config/Engine.ini and merge as a plugin config layer, so there is nothing to copy. A custom trace channel is index-allocated and cannot ship in a plugin, so Crimson_TraceChannel_Interaction must live in the host project's Config/DefaultEngine.ini; to route the profiles through it, override them by name in Config/PluginOverrideEngine.ini - see the CrimsonCore README. The plugin logs LogCrimsonCore warnings on startup if the profiles aren't found.

Interactable_OverlapDynamic is set on ACrimsonCorePickupActor::InteractionSphere; Interactable_BlockDynamic is for blocking surfaces that still let the interaction trace through. The custom trace channel Crimson_TraceChannel_Interaction claims ECC_GameTraceChannel18 (highest slot, picked so it rarely collides with project-defined channels - override in the project's ini if you already use slot 18).

Prompt presentation (PromptMode)

PromptMode on the interact ability picks how "you can interact" is shown to the local player:

ModeBehavior
World Indicator (default)Floats a widget over each interactable actor in range. Zero setup: the required UCrimsonIndicatorManagerComponent is added to the local player controller on demand, and the widget class resolves per option - InteractionWidgetClass on the option, else DefaultIndicatorWidgetClass, else DefaultInteractionWidgetClass.
HUD SlotPins the single prompt widget at the HUD.Interaction.Prompt extension slot (the previous default). Needs a CrimsonUIExtensionPoint at that tag in your HUD layout.
BothWorld indicators plus the HUD prompt.
CustomNo built-in prompt. Override the PresentInteractionPrompt (activation), UpdateInteractionPrompt (options changed), and DismissInteractionPrompt (ability end) events - Blueprint or C++ - to supply your own UI.
Cleanup is automatic
The built-in paths (HUD extension handle, world indicators) are always cleaned up natively when the ability ends, regardless of mode or overrides. A Custom override only needs to tear down UI it created itself, in DismissInteractionPrompt.

HUD widgets

Extension-point slots are only needed for the paths you opt into: HUD.Interaction.Prompt when PromptMode is HUD Slot or Both (the pinned prompt widget listens on Message.Interaction.OptionsChanged to show/hide itself), and HUD.Interaction.Progress only when a duration ability sets ActionWidgetClass (it then auto-registers its progress widget there via UCrimsonUIExtensionSubsystem). The progress ring on the prompt widget's ActionWidget covers hold/channel progress with no extension slot at all.

Source locations
Concrete abilities: Plugins/CrimsonCore/Source/CrimsonCore/Public/Abilities/Interaction/. Message struct: Public/Abilities/Interaction/CrimsonInteractionOptionsChangedMessage.h. Tags: Public/CrimsonCoreTags.h. Abstract bases + scan tasks: Plugins/CrimsonInteraction/Source/CrimsonInteraction/Public/.