Core Concepts

Understanding how the OwnInteract system works under the hood.


Architecture

The system uses a component-based architecture with two main components:

Player / Character
└─ InteractionComponent (Detector)
    ↓ Detects & Communicates With
Interactable Object
├─ InteractableComponent (Receiver)
│   ├─ InteractionPoints[]   (optional — multi-zone)
│   └─ InteractionOptions[]  (optional — multi-choice)
└─ WidgetComponent (UI Display — auto-managed)

Two components, clear separation of concerns, fully event-driven. No base classes to inherit from — just add components to your existing Blueprints.


Interaction Flow

Complete lifecycle of a single interaction:

1. DETECTION
   Player's InteractionComponent checks for objects every interval
   └─ Uses selected Detection Method (Line / Sphere / Overlap / Cone)
   └─ Only runs on locally controlled pawns (no server overhead)

2. FOCUS
   Object found and passes all filters (tag, angle, distance, CanInteract)
   ├─ Highlight outline enabled on the object
   ├─ Widget becomes visible
   └─ OnInteractableFocused event fired on the InteractionComponent

3. INPUT
   Player presses interaction key
   └─ StartInteraction() called

4. VALIDATION
   System checks if interaction is allowed
   ├─ Is object enabled? (bIsEnabled)
   ├─ Not on cooldown? (bIsOnCooldown)
   ├─ Player in range? (server-side distance check)
   └─ CanInteract() returns true?

5. EXECUTION
   Events triggered based on Interaction Type
   └─ Server events → gameplay logic
   └─ Multicast events → visual feedback on all clients

6. UNFOCUS
   Player looks away
   ├─ Highlight outline disabled
   └─ Widget fades out and hides

Interaction Types

Instant

Behavior: Triggers immediately when input is pressed.

Use Cases: Open / close doors, pick up items, press buttons, activate switches

Events fired:

  • OnInteract (Server) — gameplay logic
  • OnInteract_Multicast (All Clients) — visual/audio feedback
Event OnInteract (Server)
└─ Open Door

Event OnInteract_Multicast (All Clients)
└─ Play Sound / Particle Effect

Hold

Behavior: Player must hold input for a specified duration.

Use Cases: Lockpicking, hacking terminals, mining, arming a device

Settings:

  • HoldDuration — seconds to hold (e.g., 2.0)
  • bReplicateHoldProgress — send progress to all clients

Events fired:

  • OnInteractionHoldStarted (Server) + _Multicast (All)
  • OnInteractionHoldProgress (Local only) — 0.0–1.0 every frame
  • OnInteractionHoldProgress_Multicast (All) — throttled, if enabled
  • OnInteractionHoldCancelled (Server) + _Multicast (All)
  • OnInteractionHoldCompleted (Server) + _Multicast (All)

Important: Use OnInteractionHoldCompleted for gameplay logic — Hold never fires OnInteract.


InstantAndHold

Behavior: Tap = Instant action. Hold past TapThreshold = Hold action.

Settings:

  • TapThreshold — e.g., 0.3 seconds
  • HoldDuration — e.g., 2.0 seconds
Player presses key
├─ Released before TapThreshold? → Tap → OnInteract
└─ Held past TapThreshold?       → Hold → OnHoldStarted, etc.

Tap/Hold decision is made locally on the client for zero latency, then confirmed to server via RPC.


Detection Methods

Method Description Best For Performance
Line Trace Single ray from camera center FPS, precision Excellent
Sphere Trace Swept sphere along ray Third-person, controllers Good
Overlap Radius sphere around player Adventure, proximity Fair
Cone Distance + angle combination VR, wide-area scanning Good

Detection runs only on locally controlled pawns at DetectionInterval — not every frame, not on the server.


Filtering Systems

Tag Filtering

RequiredTags: ["Interactable"]
IgnoredTags: ["Locked"]

Angle Filtering

Only objects within camera’s field of view (MaxDetectionAngle).

Distance Filtering

Only objects within MinInteractionDistanceInteractionDistance.

CanInteract Filtering

Override in Blueprint to return false when conditions are not met (no key, quest not active, etc.).

When CanInteract() returns false:

  • bHideWidgetWhenUnavailable = true (default) → Widget hidden
  • bHideWidgetWhenUnavailable = false → Widget shows in disabled state

Priority System

When multiple objects overlap, highest Priority wins.


Multi-Point Interactions

Multiple distinct interaction zones on a single object — each with its own InteractionData.

Use Cases:

  • A car with separate doors, hood, trunk
  • A control panel with individual buttons
  • A safe with a dial and a handle
  • A machine with a screen, lever, and power switch

Setup:

InteractableComponent:
└─ Interaction Points:
   ├─ [0] RelativeLocation: (0, -50, 0) | Name: "Open Driver Door" | Type: Instant
   ├─ [1] RelativeLocation: (0,  50, 0) | Name: "Open Passenger Door" | Type: Instant
   └─ [2] RelativeLocation: (100, 0, 0) | Name: "Open Trunk" | Type: Hold | HoldDuration: 1.5

Each point has its own DetectionRadius — the player’s aim ray is checked against all active points, and the nearest one wins.

Runtime control:

// Disable a specific point (e.g., driver door already open)
Interactable Component → SetPointEnabled (PointIndex: 0, bEnabled: False)

Widget event:

Event OnInteractionPointFocused (PointData, PointIndex)
└─ Update widget to show this point's action name

Multi-Option Interactions

Multiple choices on a single interaction — the player scrolls through them before confirming.

Use Cases:

  • Chest: “Take All” / “Inspect” / “Leave”
  • NPC: “Talk” / “Trade” / “Follow Me”
  • Item: “Equip” / “Examine” / “Drop”
  • Terminal: “Hack” / “Shutdown” / “Reboot”

Setup:

InteractableComponent:
└─ Interaction Options:
   ├─ [0] Name: "Take All"  | Type: Instant
   ├─ [1] Name: "Inspect"   | Type: Instant
   └─ [2] Name: "Leave"     | Type: Instant

Input:

// Bind scroll wheel in Character Blueprint
Mouse Wheel Up   → InteractionComponent → CycleInteractionOption (Direction: +1)
Mouse Wheel Down → InteractionComponent → CycleInteractionOption (Direction: -1)

Widget events:

Event OnInteractionOptionsAvailable (Options, ActiveIndex)
└─ Build option list UI (show all options, highlight active)

Event OnInteractionOptionChanged (ActiveOption, ActiveIndex)
└─ Update highlighted option

The active option’s InteractionData is used when the player presses the interact key.


Highlight System

When an object receives focus, the system automatically enables a custom depth stencil outline — the classic “interactable object glow” effect.

Setup — Post Process Material: The highlight uses Unreal’s Custom Depth buffer. To see it, your Post Process Volume needs a material that reads CustomDepth and draws an outline for objects with a specific stencil value.

Configuration:

InteractableComponent:
├─ bEnableHighlight = True     (default)
└─ HighlightStencilValue = 1   (0-255, match your PP material)

Runtime toggle:

Interactable Component → SetHighlightEnabled (False)

How it works:

  • On focus → the plugin calls SetRenderCustomDepth(true) + SetCustomDepthStencilValue() on the object’s mesh
  • On unfocus → SetRenderCustomDepth(false)
  • Entirely automatic — no Blueprint wiring needed

Use different stencil values (e.g., 1 = blue, 2 = red) and a PP material that distinguishes them for colored outlines per interaction type.


Input Device Detection

The system automatically detects whether the player is using Keyboard/Mouse or Gamepad and notifies the widget so it can show the correct button hint.

How it works:

// In Character Blueprint, tell the component when input changes
InteractionComponent → SetActiveInputType (bIsGamepad: True)

// The widget receives this event automatically
Event OnInputDeviceChanged (bIsGamepad)
├─ True  → Show "A" button icon
└─ False → Show "E" key icon

Widget function:

InteractionWidget → IsGamepadActive() → bool

Typical integration with Enhanced Input:

Event OnInputDeviceChanged (from Enhanced Input Subsystem)
└─ InteractionComponent → SetActiveInputType (bIsGamepad)

Widget System

Widgets are screen-space UI elements that always face the camera. The InteractableComponent manages lifecycle and space mode automatically.

Widget Events

Event When
OnInteractionAvailable(InteractionData) Object focused
OnInteractionUnavailable() Object unfocused
OnInteractionTriggered() Instant triggered
OnHoldStarted() Hold began
OnHoldProgressUpdated(Progress) Progress 0.0–1.0
OnHoldCompleted() Hold succeeded
OnHoldCancelled() Hold released early
OnInteractionPointFocused(PointData, PointIndex) Multi-point: aim changed
OnInteractionOptionsAvailable(Options, ActiveIndex) Multi-option: options shown
OnInteractionOptionChanged(ActiveOption, ActiveIndex) Multi-option: scroll changed
OnInputDeviceChanged(bIsGamepad) Keyboard/Gamepad switch

Widget events are always fired locally on the owning client — never over the network.

Widget Unavailable State

bHideWidgetWhenUnavailable = True  → Widget hidden when CanInteract() = false
bHideWidgetWhenUnavailable = False → Widget visible in disabled state

Multiplayer Architecture

Server Authority

1. Client presses "E" → StartInteraction()
2. ServerStartInteraction(Actor, PointIndex, OptionIndex) RPC sent
3. Server validates (distance, enabled, cooldown, CanInteract)
4. OnInteract fires (Server) + OnInteract_Multicast (All Clients)

Server vs Local Execution

bRequiresServerAuthority Behavior
true (default) Server validates + executes, multicasts to clients
false Executes immediately on local client, no RPC

Event Authority Reference

Event Runs On
OnInteract Server
OnInteract_Multicast All Clients
OnHoldStarted/Cancelled/Completed Server
OnHold*_Multicast All Clients
OnHoldProgress Local Client Only
OnHoldProgress_Multicast All Clients (if bReplicateHoldProgress)
Widget Events Always Local

AI Support

Add InteractionComponent to an AI Character and set the target manually — no detection needed:

// AI Controller or Behavior Tree
InteractionComponent → SetInteractionTarget (TargetActor)
InteractionComponent → StartInteraction()

This bypasses the detection system entirely and directly starts an interaction with the specified actor.


Best Practices

Performance

Do Don’t
DetectionInterval = 0.1 (default) Set below 0.05
Use Tag Filtering Detect everything and filter later
Disable bShowDebug in shipping Ship with debug enabled
InteractionComponent only on players / AI Add it to interactable objects

Design

Do Don’t
Clear InteractionName (“Open Door”) Generic labels (“E”)
HoldDuration 1–3 seconds Over 5 seconds (frustrating)
Use Priority for important objects Leave all at 0
OnInteract_Multicast for sounds/VFX Sounds only in server OnInteract
Different stencil values per object type Same stencil for everything

Common Patterns

Key-Required Door (CanInteract + disabled widget state)

Function CanInteract (Actor) → bool
└─ Return: Player HasItem "Key"

// bHideWidgetWhenUnavailable = false
Event OnInteractionAvailable
└─ CanInteract? True → "Open Door" / False → "Requires Key"

Multi-Zone Object (InteractionPoints)

InteractableComponent → Interaction Points:
├─ [0] "Enter Vehicle"   — driver door position
└─ [1] "Open Trunk"      — rear position

Loot Context Menu (InteractionOptions + Scroll)

InteractableComponent → Interaction Options:
├─ [0] "Take All"   — Instant
├─ [1] "Inspect"    — Instant
└─ [2] "Leave"      — Instant

Character Input:
├─ Scroll Up   → CycleInteractionOption(+1)
└─ Scroll Down → CycleInteractionOption(-1)

Colored Highlights per Type

Door:       HighlightStencilValue = 1  → Blue outline (PP material)
Enemy:      HighlightStencilValue = 2  → Red outline
Collectible: HighlightStencilValue = 3 → Yellow outline

Next Steps


Collision Channel {#collision-channel}

The plugin uses ECC_Visibility as its trace channel by default. This works out of the box because most Static Mesh presets block Visibility.

Why you may want a dedicated channel

ECC_Visibility is shared with other engine systems (cameras, AI perception, foliage culling). In complex projects this can cause unintended cross-system interference:

  • An invisible blocker placed for AI sight lines blocks interaction traces too
  • Meshes intentionally transparent to cameras become unexpectedly interactable
  • It is harder to reason about which objects should or should not be detectable

How to set up a custom Interaction channel

  1. Project Settings → Engine → Collision → New Trace Channel
    • Name: Interaction
    • Default Response: Ignore
  2. Set the response on every interactable mesh to Block for your new channel
  3. In InteractionComponent swap the trace channel in PerformLineTrace, PerformSphereTrace, PerformOverlap, and PerformConeDetection from ECC_Visibility to ECC_GameTraceChannel1 (or whichever slot was assigned)

Note: This is an advanced customization. The default ECC_Visibility setup is perfectly fine for most projects.


← Getting Started | API Reference →