Context Menu
Surfaces a dynamic list of actions on any target — items (Use / Drop / Split), doors (Lock / Unlock / Open / Close), NPCs, world pickups — with either a modal cursor-anchored popup or a world-anchored panel that tracks a USceneComponent (Cyberpunk scan-mode / Dishonored body-interaction feel).
UCrimsonCoreContextMenuSubsystem is the only place that imports all of them.Plugin pieces involved
| Plugin | Provides |
|---|---|
CrimsonCommon | UCrimsonContextAction (+ _GrantAbility / _FromInteractionOption), UCrimsonContextActionSet (reusable action Data Asset), UCrimsonContextActionGatherer (the single gather path), ICrimsonContextActionProvider, ICrimsonContextActionContributor + UCrimsonContextActionContributorRegistry (inject actions from other systems), FCrimsonContextActionContext, FCrimsonContextActionHandle, UCrimsonContextActionPlayerComponent, native tags |
CrimsonUI | UCrimsonContextMenuSubsystem, widget bases (_Modal + _World + _Radial), entry widget (native click/hover), screen + entry VMs, confirmation-dialog routing, UCrimsonUISettings default widget classes |
CrimsonInventory | UCrimsonInventoryItemFragment_ContextActions, UCrimsonInventoryItemTypeActionSet, GetContextActionsForItem helper |
CrimsonInteraction | UCrimsonInteractionStatics::GetContextActionsForActor (wraps existing FCrimsonInteractionOptions) |
CrimsonCore | UCrimsonCoreContextMenuSubsystem — ShowContextMenuForItem / ShowContextMenuForActor (the public entry point) + ExecuteDefaultActionForItem / ForActor (double-click / tap fast path) |
Integration steps
1. Enable the plugins
Open Edit → Plugins, search Crimson, and tick all five so each is enabled: CrimsonCommon, CrimsonUI, CrimsonInventory, CrimsonInteraction, and CrimsonCore. CrimsonCore depends on them, so the Plugins window flags them as required. Restart the editor when prompted — for a Blueprint project that's the whole install, with no .uproject or .Build.cs to edit.
2. Register the UI.Layer.ContextMenu layer
Open your UCrimsonHUDLayout Blueprint. Add a UCommonActivatableWidgetStack named e.g. Stack_ContextMenu. Wire RegisterLayer to attach it under the native tag.
// C++ — anywhere in your HUD layout init (typically RegisterLayers):#include "ContextMenu/CrimsonContextMenuTags.h"RegisterLayer(CrimsonContextMenuTags::UI_Layer_ContextMenu, Stack_ContextMenu);
UI.Layer.ContextMenu and the stack widget reference. Same effect.3. Create the menu widget Blueprints
Three widgets need BP subclasses. C++ bases own all logic; BP supplies visuals.
| C++ base | Suggested BP name | Required bindings |
|---|---|---|
UCrimsonContextMenuWidget_Modal | WBP_ContextMenu_Modal | ContentPanel (required BindWidget; top-left anchor, auto size) + EntryBox (Dynamic Entry Box, auto-populated by C++) inside it |
UCrimsonContextMenuWidget_World | WBP_ContextMenu_World | Same as Modal - ContentPanel (required) tracks the world target each tick; EntryBox inside it |
UCrimsonContextMenuEntryWidget | WBP_ContextMenuEntry | Layout for one action button. Bind to EntryViewModel's DisplayName / Tooltip / DisabledReason / Icon / bIsEnabled / bIsDestructive / bIsFocused via MVVM. Hover-to-focus and click-to-confirm are wired natively |
Inside ContentPanel, add a Dynamic Entry Box named exactly EntryBox and set its Entry Widget Class to WBP_ContextMenuEntry. C++ creates the entries automatically on activation and refresh - no MVVM view binding needed. (A UListView named EntryList is also supported for long scrollable menus, but a virtualizing list inside the auto-sized panel must be given a bounded height or it generates only one row.) A missing ContentPanel is a Blueprint compile error; missing both entry hosts logs a warning at activation.
4. Wire input bindings in the menu widget BP
Mouse needs no wiring — entries confirm on click and focus on hover natively. For gamepad/keyboard, the widget base exposes BlueprintCallable methods — in OnActivated, register UI action bindings for each of these tags.
| UI action tag | Calls |
|---|---|
UI.Action.ContextMenu.Confirm | ConfirmFocusedEntry |
UI.Action.ContextMenu.Cancel | RequestClose |
UI.Action.ContextMenu.Cycle.Next | CycleNext |
UI.Action.ContextMenu.Cycle.Previous | CyclePrevious |
UI.Action.ContextMenu.Navigate.Up/Down | Handled by CommonUI focus chain — no binding needed |
5. Map the UI action tags to UInputAction assets
In your project's UCommonUIInputData (or UCrimsonInputConfig, whichever feeds the CommonUI action router), add one row per tag.
UI.Action.ContextMenu.Confirm -> IA_UI_ConfirmUI.Action.ContextMenu.Cancel -> IA_UI_CancelUI.Action.ContextMenu.Navigate.Up -> IA_UI_NavigateUpUI.Action.ContextMenu.Navigate.Down -> IA_UI_NavigateDownUI.Action.ContextMenu.Cycle.Next -> IA_UI_CycleNext (RB / E)UI.Action.ContextMenu.Cycle.Previous -> IA_UI_CyclePrevious (LB / Q)
6. Attach the player component
UCrimsonContextActionPlayerComponent carries the Server_ExecuteContextAction RPC. Choose one attachment path; the component sets SetIsReplicatedByDefault(true) automatically.
// Option A — C++ subclass of your player controller:ACrimsonPlayerController::ACrimsonPlayerController(){ContextActionComponent = CreateDefaultSubobject<UCrimsonContextActionPlayerComponent>(TEXT("ContextActionComponent"));}
Option B — add a GFA_AddComponents entry to your experience definition targeting APlayerController.
Option C — add the component in your PC Blueprint's Components panel.
7. Configure per-item-type defaults (optional)
For "all weapons get Inspect/Drop", "all consumables get Use/Drop":
| Step | Action |
|---|---|
| 1 | Content Browser → Miscellaneous → Data Asset → CrimsonInventoryItemTypeActionSet |
| 2 | Add rows mapping Item.Type.* tags to instanced action lists |
| 3 | Project Settings → Crimson → Crimson Inventory → Default Context Action Type Set → soft ref the asset |
8. Author per-item actions
Open any UCrimsonInventoryItemDefinition and add UCrimsonInventoryItemFragment_ContextActions to its Fragments array. Populate the fragment's Actions array with instanced subclasses of UCrimsonContextAction.
GameplayTag per action (e.g. ContextAction.Item.Use). It names the action on the wire: server-side resolution matches GetActionId() (the tag name, or the class name when unset) plus its occurrence among same-id entries — robust against unrelated list shifts. Duplicate tags within one provider are legal but rely on stable relative order.9. Show the menu from your gameplay code
// Inventory item — modal, cursor-anchored:ULocalPlayer* LP = PC->GetLocalPlayer();UCrimsonCoreContextMenuSubsystem* CM = LP->GetSubsystem<UCrimsonCoreContextMenuSubsystem>();FCrimsonContextMenuRequest Request;Request.PresentationMode = ECrimsonContextMenuPresentationMode::Modal;Request.Header = FText::FromString(TEXT("Item Actions"));Request.bAnchorToCursor = true;// Widget class is optional — unset falls back to// UCrimsonUISettings::DefaultModalContextMenuClass (Project Settings -> Crimson -> Crimson UI).CM->ShowContextMenuForItem(ItemInstance, Request);
// Door / NPC — world-anchored:FCrimsonContextMenuRequest Request;Request.PresentationMode = ECrimsonContextMenuPresentationMode::World;Request.Header = FText::FromString(TEXT("Door"));// Request.WorldWidgetClass optional (falls back to UCrimsonUISettings::DefaultWorldContextMenuClass).// Request.TargetAnchor is auto-filled to Actor->GetRootComponent() if not set.CM->ShowContextMenuForActor(FocusedDoor, Request);
Typical trigger points: UCrimsonInventorySlotWidget::OnContextMenuRequested on right-click, a 'Hold E to see actions' branch on your interaction ability, or a direct HUD button. For the double-click / tap fast path that skips the menu, call ExecuteDefaultActionForItem / ExecuteDefaultActionForActor — it dispatches the first enabled action flagged bIsDefault (else the first enabled action).
Discovery rules
| Target type | How actions are gathered |
|---|---|
UCrimsonInventoryItemInstance | ICrimsonContextActionProvider is auto-implemented; merges Fragment + type registry (fragment wins by ActionTag); sorted by SortPriority. The registry is always the project default — no per-call override (it would desync the server re-gather) |
Actor implementing ICrimsonContextActionProvider | Used directly (your custom gather) |
Actor implementing only ICrimsonInteractableTarget | The gatherer wraps each FCrimsonInteractionOption from the actor and its interactable components in UCrimsonContextAction_FromInteractionOption — no actor code changes required |
| Any target (additionally) | Contributors registered with UCrimsonContextActionContributorRegistry inject extra actions after the target's own gather (quests, trading, ...) - on client and server alike |
FCrimsonContextActionHandle { Provider, ActionId, Occurrence, Payload }. The server re-runs UCrimsonContextActionGatherer::GatherActions (never trusts the client array), asks the provider's ValidateContextActionRequest (ownership), resolves by id + occurrence, re-checks GetAvailability == Enabled and ValidateExecution (range for interactables), and only then calls ExecuteAction(Context, Payload) under HasAuthority(). Stale requests drop with a log — never a kick.Out of the box: ACrimsonInteractableActor
ACrimsonInteractableActor ships with the full flow pre-wired: it implements ICrimsonContextActionProvider, and with bShowContextMenuOnInteract (default on) a successful interaction opens the world-anchored menu on the interacting player. Author actions as Crimson Context Action Set Data Assets referenced in ContextActionSets - a plain asset picker, shareable across every door/lever, with asset edits propagating to all of them. There is deliberately no inline action array on the actor (Instanced arrays on level-placed actors keep stale per-instance copies).
| Property / function | What it does |
|---|---|
ContextActionSets | The actions this actor offers - references to UCrimsonContextActionSet Data Assets. |
bShowContextMenuOnInteract | Open the menu automatically after a successful interaction (on the interacting player only). |
bAddTestActionsWhenNoneAuthored | While no set is referenced, gather two transient Debug Message test actions so the round-trip is testable the moment the actor is placed. Disable for production actors. |
ContextMenuHeader | Title text of the menu. |
ShowContextMenu(Instigator) | BlueprintCallable manual trigger (no-op unless the instigator is the locally controlled player) - use with the bool off for custom flows. |
EffectWhileOpen to an Infinite GameplayEffect whose Granted Tags add e.g. Camera.Blocked, and put that tag in your camera-turn abilities' ActivationBlockedTags. The GE is applied to the owning pawn's ASC when the menu opens and removed on every close path (pick, click-outside, walk-away, target death), so the block can never leak. LooseTagsWhileOpen is the lightweight tag-only alternative. Mouse-look is already suspended while a menu is open (menus run GameMouseCaptureMode = NoCapture).World-menu lifetime and feel are per-asset options on the widget Blueprint (UCrimsonContextMenuWidget_World class defaults): AutoCloseDistance closes the menu when the pawn walks away (the server-side crimson.ContextMenu.MaxExecuteDistance check backs it up), bCloseOnClickOutside gives the dialog-border dismiss pattern, and bScaleWithDistance / bFadeWhenOccluded fake depth and stop the menu drawing through walls.
Subclassing UCrimsonContextAction
UCLASS(DisplayName = "Drop Item")class UMyAction_DropItem : public UCrimsonContextAction{GENERATED_BODY()public:virtual bool IsAvailable_Implementation(const FCrimsonContextActionContext& Context) const override{return Cast<UCrimsonInventoryItemInstance>(Context.Target.Get()) != nullptr;}virtual void ExecuteAction_Implementation(const FCrimsonContextActionContext& Context, const FInstancedStruct& Payload) override{// Runs under HasAuthority() on the server — do the deed here.// Payload is empty for plain clicks; ConfirmEntryWithPayload can pass arguments.}};
Or create a Blueprint subclass directly — UCrimsonContextAction is Blueprintable, EditInlineNew, DefaultToInstanced, so designers can author actions inline on item fragments without C++.
Common pitfalls
InputConfig to Default while the parent layer is Game — no UI input flows. Either leave the C++ default (GameAndMenu) or set the BP explicitly.UCrimsonContextActionPlayerComponent not attached to the player controller. Check via PC->FindComponentByClass<UCrimsonContextActionPlayerComponent>() returning non-null.GetAvailability and GatherContextActions implementations are deterministic — no per-machine state, no random ordering.crimson.ContextMenu.MaxExecuteDistance (default 1000 cm; <= 0 disables).Request.TargetAnchor is unset. ShowContextMenuForActor auto-fills it from the actor's root, but if you call UCrimsonContextMenuSubsystem::ShowContextMenu directly you must set it manually.Request.bAnchorToCursor = false and provide a fixed ScreenAnchor (e.g. screen center for gamepad-only flows).Plugins/CrimsonCommon/Source/CrimsonCommon/Public/ContextMenu/. Subsystem + widgets + VMs: Plugins/CrimsonUI/Source/CrimsonUI/Public/ContextMenu/. Item fragment + type registry: Plugins/CrimsonInventory/Source/CrimsonInventory/Public/Fragments/ + Public/CrimsonInventoryItemTypeActionSet.h. Interaction helper: Plugins/CrimsonInteraction/Source/CrimsonInteraction/Public/CrimsonInteractionStatics.h. Orchestrator: Plugins/CrimsonCore/Source/CrimsonCore/Public/ContextMenu/CrimsonCoreContextMenuSubsystem.h.