Input — the Enhanced Input binding and its edge cases

The Enhanced Input binding and its edge cases.

Assumes you have finished quick-start.

This page covers the Enhanced Input binding, the settings that drive it, and the UK-VAL-034 warning.

What binds what

UUKInteractorInputSubsystem is a ULocalPlayerSubsystem in the UKInteractionClient module (ClientOnly — it never exists on a dedicated server). It is created once per ULocalPlayer, appears in no Add Component picker, and can never sit in a pawn's Blueprint component list. That is deliberate: a pawn Blueprint referencing a client-only class would reference a class that is absent from a cooked dedicated-server target.

So the quick-start is one component on your pawn — UK Interactor — and the input half wires itself up.

The subsystem binds one Enhanced Input action on the player controller's UEnhancedInputComponent:

Enhanced Input eventKit call
ETriggerEvent::StartedUUKInteractorComponent::BeginInteract()
ETriggerEvent::CompletedUUKInteractorComponent::EndInteract()
ETriggerEvent::CanceledUUKInteractorComponent::EndInteract()

It binds nothing on Triggered and nothing on Ongoing. It never calls SetInputMode, never enumerates devices through Raw Input or Game Input, and never uses FInputDeviceScope.

Why the player controller's component and not the pawn's. The controller's InputComponent is created in InitInputSystem and survives possession; the pawn's is created in PawnClientRestart and, on a network client, can arrive after the pawn-changed notification. Binding on the controller means a possession swap moves only the mapping context — the action binding is untouched.

The four knobs

All four live on UUKInteractionSettings::Client (Project Settings → Plugins → UK Interaction), not on the pawn. They are path-typed so the core module still names no Enhanced Input type.

SettingTypeDefault
InteractActionFSoftObjectPathIA_UK_Interact (shipped)
MappingContextFSoftObjectPathIMC_UK_Default (shipped; E and Gamepad_FaceButton_Bottom)
MappingContextPriorityint320
bAddMappingContextbooltrue

Soft references are loaded synchronously once, on the first bind, on the client only.


Edge cases you can hit

bAddMappingContext = false and no context of your own

Turn bAddMappingContext off when your project owns its input contexts — for example when different pawn kinds need different contexts. The action binding is unchanged; only the context add/remove is suppressed.

The consequence is the obvious one, and it is silent by design: if nothing in your project ever adds a context that maps InteractAction, no input ever arrives. BeginInteract() is never called and the kit reports it honestly rather than warning on every start-up:

const FUKInputStats Stats = Subsystem->GetInputStats();
// Stats.bBound                   == true   — the action binding exists
// Stats.bContextActive           == false  — no mapping context is active
// Stats.bEnhancedInputComponent  == true

bContextActive == false with bBound == true is exactly this state. The debug overlay (uk.Overlay) surfaces the same pair.

You can still drive the kit from your own input code at any time: BeginInteract() and EndInteract() are BlueprintCallable and input-agnostic — that is the entire point of them being separate from the subsystem.

A non-Enhanced InputComponent — UK-VAL-034

If the player controller's InputComponent is not a UEnhancedInputComponent, the kit binds nothing at all — no action binding, no mapping context — and logs exactly one Warning per bound controller on LogUKInteractionClient:

UK-VAL-034: the player controller '<name>' has a '<class>' InputComponent, not a
UEnhancedInputComponent - the UK interaction input binds NOTHING. Set
Project Settings -> Input -> Default Input Component Class to EnhancedInputComponent.
Unused while this is the case: UUKInteractionSettings::Client.InteractAction ('...'),
.MappingContext ('...'), .MappingContextPriority (0), .bAddMappingContext (true).

The fix is one project setting: Project Settings → Input → Default Input Component Class → EnhancedInputComponent. The pawn's own InputComponent is never read by the kit, so changing it has no effect on this check — the check has exactly one subject.

FUKInputStats::bEnhancedInputComponent is false in this state.

Triggers on the action — Hold, and why Canceled matters

The kit's model is press and release edges; hold duration and the gauge are computed in the core from the behaviour's hold parameters, never from an Enhanced Input Hold trigger. Your action may still carry any trigger you like, and the binding survives it — because the kit binds both Completed and Canceled.

That second binding is not decorative. With an explicit trigger such as Hold(0.5 s), releasing the key before the threshold emits Canceled, not Completed. A kit that bound only Completed would leave the interaction open forever on every early release. The test that would have caught it is UK.Interaction.Client.Input.test_a_hold_trigger_released_early_still_ends_through_canceled — a Completed-only binding scores 0 there and fails.

Pressed is unsupported

Every Enhanced Input trigger the kit was designed against survives the press/release model, with one exception: Pressed. It fires Completed on the next frame, which the kit reads as a release one frame after the press. Do not put a Pressed trigger on InteractAction. Down, Hold, Tap, Pulse and the chorded/blocker triggers are fine.

FlushPressedKeys while a press is outstanding — measured

The engine can flush pressed keys under you (focus loss, a mode change, SetInputMode, a level transition). The question was whether the outstanding action still delivers an end edge, or whether the kit needs to poll the action instance while a press is outstanding.

Measured on all three engines:

EngineStarted after pressCompleted after flushCanceledkit EndInteract
5.61101
5.71101
5.81101

Completed is delivered on every supported engine, so the kit does not poll. The binding it already carries closes the request. The measuring test is kept as a pinning test rather than deleted: if a future engine stops delivering that edge, it records completed=0 and the kit will need to poll.

Unrelated but adjacent: EnhancedInput.IgnoreHeldKeysOnFlush (default true) governs whether the key re-fires after a flush. That is a different question and the kit does not change it.

Possession changes

The action binding lives on the player controller and survives possession. On a pawn swap the kit removes the mapping context for the old pawn and adds it for the new one (when bAddMappingContext); the binding itself is never rebuilt. The old pawn's interactor goes Inert — which ends an outstanding interaction and then clears focus, in that order — and the new pawn's interactor gathers on its first tick.

A pawn possessed before the local player's subsystem collection initialises is still bound: Initialize evaluates the controller and pawn already present rather than waiting for a change notification.

Multiple local players

Each ULocalPlayer gets its own subsystem instance and its own binding. A second local player's key never reports on player zero. Split-screen is supported by construction but not yet tested.


Device kind — for prompt glyphs

The subsystem classifies the last input device into EUKInputDeviceKind ∈ {KeyboardMouse, Gamepad, Touch, Unknown} and broadcasts OnInputDeviceKindChanged(Kind) on every classified change (the initial Unknown → X transition counts). The prompt layer (Docs/prompt.md) consumes it to swap glyphs.

Two sources, in order:

  1. UInputDeviceSubsystem (engine) — GetMostRecentlyUsedHardwareDevice(PlatformUserId) at bind and OnInputHardwareDeviceChanged thereafter. Present and answering on 5.6 / 5.7 / 5.8.
  2. A key-inference floor — a lightweight IInputProcessor registered with FSlateApplication that records only the last FKey seen for the owning local player, classified with FKey::IsGamepadKey() / IsTouch(). It is removed on Deinitialize.

The floor exists because "the subsystem answers" and "the answer is right" are different things: the engine's device subsystem is fed by FSlateApplication::ProcessKeyDownEvent, by the per-device scope whose default flipped on 5.8, and by Game Input for Windows — none of which the kit controls.

Which machine? On Input Device Kind Changed — Owning client only.

Reading the state

if (const ULocalPlayer* LP = PC->GetLocalPlayer())
{
    if (const UUKInteractorInputSubsystem* Input = LP->GetSubsystem<UUKInteractorInputSubsystem>())
    {
        const FUKInputStats Stats = Input->GetInputStats();
        // bBound, bContextActive, bEnhancedInputComponent, LastDeviceKind
    }
}
  • Docs/detection.md — what the interactor is focusing when you press the key.
  • Docs/movement.md — the velocity hook holds read.
  • Docs/install.md — where the plugin's modules live per target.