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-034warning.
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 event | Kit call |
|---|---|
ETriggerEvent::Started | UUKInteractorComponent::BeginInteract() |
ETriggerEvent::Completed | UUKInteractorComponent::EndInteract() |
ETriggerEvent::Canceled | UUKInteractorComponent::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.
| Setting | Type | Default |
|---|---|---|
InteractAction | FSoftObjectPath | IA_UK_Interact (shipped) |
MappingContext | FSoftObjectPath | IMC_UK_Default (shipped; E and Gamepad_FaceButton_Bottom) |
MappingContextPriority | int32 | 0 |
bAddMappingContext | bool | true |
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:
| Engine | Started after press | Completed after flush | Canceled | kit EndInteract |
|---|---|---|---|---|
| 5.6 | 1 | 1 | 0 | 1 |
| 5.7 | 1 | 1 | 0 | 1 |
| 5.8 | 1 | 1 | 0 | 1 |
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:
UInputDeviceSubsystem(engine) —GetMostRecentlyUsedHardwareDevice(PlatformUserId)at bind andOnInputHardwareDeviceChangedthereafter. Present and answering on 5.6 / 5.7 / 5.8.- A key-inference floor — a lightweight
IInputProcessorregistered withFSlateApplicationthat records only the lastFKeyseen for the owning local player, classified withFKey::IsGamepadKey()/IsTouch(). It is removed onDeinitialize.
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
}
}
Related
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.