Loading...
Loading...
Use this skill when implementing player input with Unreal Engine's Enhanced Input system. Also use when the user mentions 'Enhanced Input', 'input', 'input action', 'InputAction', 'mapping context', 'InputMappingContext', 'input binding', 'key binding', 'input trigger', 'input modifier', 'gamepad', or 'keyboard'. Covers ETriggerEvent, built-in triggers (Hold, Tap, Pulse, ChordAction, Combo), built-in modifiers (DeadZone, Scalar, Negate, SwizzleAxis), and custom trigger/modifier authoring. See references/input-action-reference.md for the full catalogue. For UI input modes, see ue-ui-umg-slate.
npx skill4agent add quodsoler/unreal-engine-skills ue-input-system.agents/ue-project-context.mdEnhancedInput.uproject{ "Name": "EnhancedInput", "Enabled": true }Build.cs"EnhancedInput"PublicDependencyModuleNamesDefaultInput.ini[/Script/Engine.InputSettings]
DefaultPlayerInputClass=/Script/EnhancedInput.EnhancedPlayerInput
DefaultInputComponentClass=/Script/EnhancedInput.EnhancedInputComponentUInputAction : UDataAssetInputAction.hEInputActionValueType ValueType = EInputActionValueType::Boolean;
// Boolean | Axis1D (float) | Axis2D (FVector2D) | Axis3D (FVector)
EInputActionAccumulationBehavior AccumulationBehavior
= EInputActionAccumulationBehavior::TakeHighestAbsoluteValue;
// TakeHighestAbsoluteValue — highest magnitude wins across all mappings to this action
// Cumulative — all mapping values sum (W + S cancel each other for WASD)
bool bConsumeInput = true; // blocks lower-priority Enhanced Input mappings to same keys
TArray<TObjectPtr<UInputTrigger>> Triggers; // applied AFTER per-mapping triggers
TArray<TObjectPtr<UInputModifier>> Modifiers; // applied AFTER per-mapping modifiersUInputMappingContext : UDataAssetDefaultKeyMappings.MappingsTArray<FEnhancedActionKeyMapping>MappingProfileOverridesRegistrationTrackingModeUntrackedCountRegistrations// MyCharacter.h — declare assets and handlers
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Input")
TObjectPtr<UInputMappingContext> DefaultMappingContext;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Input")
TObjectPtr<UInputAction> MoveAction;
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Input")
TObjectPtr<UInputAction> JumpAction;
void Move(const FInputActionValue& Value);
void StartJump();
void StopJump();// MyCharacter.cpp
#include "EnhancedInputComponent.h"
#include "EnhancedInputSubsystems.h"
void AMyCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
Super::SetupPlayerInputComponent(PlayerInputComponent);
UEnhancedInputComponent* EIC = Cast<UEnhancedInputComponent>(PlayerInputComponent);
if (!EIC) { return; }
EIC->BindAction(MoveAction, ETriggerEvent::Triggered, this, &AMyCharacter::Move);
EIC->BindAction(JumpAction, ETriggerEvent::Started, this, &AMyCharacter::StartJump);
EIC->BindAction(JumpAction, ETriggerEvent::Completed, this, &AMyCharacter::StopJump);
}
void AMyCharacter::BeginPlay()
{
Super::BeginPlay();
if (APlayerController* PC = Cast<APlayerController>(GetController()))
{
if (auto* Sub = ULocalPlayer::GetSubsystem<UEnhancedInputLocalPlayerSubsystem>(
PC->GetLocalPlayer()))
{
Sub->AddMappingContext(DefaultMappingContext, 0); // priority 0 = lowest
}
}
}BindAction// No params — press/release without value needed
void AMyCharacter::StartJump() { Jump(); }
// FInputActionValue — for axis values
void AMyCharacter::Move(const FInputActionValue& Value)
{
const FVector2D Input = Value.Get<FVector2D>();
AddMovementInput(GetActorForwardVector(), Input.Y);
AddMovementInput(GetActorRightVector(), Input.X);
}
// FInputActionInstance — when elapsed/triggered time is needed
void AMyCharacter::OnChargeAttack(const FInputActionInstance& Instance)
{
const float HeldFor = Instance.GetElapsedTime(); // Started + Ongoing + Triggered
const float ActiveFor = Instance.GetTriggeredTime(); // Triggered only
}
// Lambda variant
EIC->BindActionValueLambda(InteractAction, ETriggerEvent::Triggered,
[this](const FInputActionValue& Value) { TryInteract(); });FEnhancedInputActionEventBinding& B =
EIC->BindAction(DebugAction, ETriggerEvent::Started, this, &AMyCharacter::DebugToggle);
uint32 Handle = B.GetHandle();
// ...
EIC->RemoveBindingByHandle(Handle); // remove one binding
EIC->ClearBindingsForObject(this); // remove all bindings for an objectInputTriggers.h| Event | State Transition | Use for |
|---|---|---|
| None -> Ongoing/Triggered | First frame of input; press-once actions |
| *->Triggered, Triggered->Triggered | Every active frame; continuous movement |
| Ongoing->Ongoing | Held but not yet triggered (charge build-up) |
| Ongoing->None | Released before trigger threshold |
| Triggered->None | Input released after triggering; stop continuous actions |
CompletedOngoingreferences/input-action-reference.md| Class | Name | Behavior |
|---|---|---|
| Down | Every frame input exceeds threshold (implicit default) |
| Pressed | Once on first actuation; holding does not repeat |
| Released | Once when input drops below threshold after actuation |
| Hold | After |
| Hold And Release | On release after holding |
| Tap | Released within |
| Repeated Tap | N taps within |
| Pulse | Repeatedly at |
| Chorded Action | Only fires while |
| Combo (Beta) | All |
ExplicitImplicitBlocker| Class | Name | Effect |
|---|---|---|
| Dead Zone | Zero input below |
| Scalar | Multiply per axis by |
| Scale By Delta Time | Multiply by frame DeltaTime |
| Negate | Invert selected axes ( |
| Swizzle Input Axis Values | Reorder axes; |
| Smooth | Rolling average over recent samples |
| Smooth Delta | Smoothed normalized delta; configurable interpolation ( |
| Response Curve - Exponential | `sign(x)* |
| Response Curve - User Defined | Separate |
| FOV Scaling | Scale by camera FOV for consistent angular speed across zoom levels |
| To World Space | 2D axis -> world space (up/down = world X, left/right = world Y) |
AccumulationBehavior = CumulativeWSwizzleAxis(YXZ)SSwizzleAxis(YXZ)Negate(bY)DANegate(bX)DeadZone(Radial, LowerThreshold=0.2)[Scalar(0.4,0.4,1), Smooth, FOVScaling]// Higher integer = higher priority; wins key conflicts
Subsystem->AddMappingContext(GameplayIMC, 0);
Subsystem->AddMappingContext(VehicleIMC, 1);
// FModifyContextOptions — prevent ghost inputs on switch
FModifyContextOptions Opts;
Opts.bIgnoreAllPressedKeysUntilRelease = true;
Subsystem->AddMappingContext(UIIMC, 2, Opts);
// Remove on mode exit
Subsystem->RemoveMappingContext(VehicleIMC);bConsumeInput = trueUInputActionbConsumeInputUEnhancedInputLocalPlayerSubsystemULocalPlayer// Access subsystem for a specific local player (e.g., player 2)
if (ULocalPlayer* LP = PlayerController->GetLocalPlayer())
{
if (auto* Sub = ULocalPlayer::GetSubsystem<UEnhancedInputLocalPlayerSubsystem>(LP))
{
Sub->AddMappingContext(PlayerTwoIMC, 0);
}
}UInputTriggerUpdateState_ImplementationETriggerState::None / Ongoing / TriggeredUCLASS(EditInlineNew, meta=(DisplayName="Double Click"))
class MYGAME_API UDoubleClickTrigger : public UInputTrigger
{
GENERATED_BODY()
public:
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Trigger Settings")
float DoubleClickThreshold = 0.3f;
protected:
virtual ETriggerType GetTriggerType_Implementation() const override
{ return ETriggerType::Explicit; }
virtual ETriggerState UpdateState_Implementation(
const UEnhancedPlayerInput* PlayerInput, FInputActionValue ModifiedValue, float DeltaTime) override;
private:
float LastPressTime = 0.f;
bool bWasActuated = false;
};
ETriggerState UDoubleClickTrigger::UpdateState_Implementation(
const UEnhancedPlayerInput* PlayerInput, FInputActionValue ModifiedValue, float DeltaTime)
{
const bool bActuated = IsActuated(ModifiedValue); // helper: magnitude >= ActuationThreshold
const float Now = PlayerInput->GetWorld()->GetTimeSeconds();
if (bActuated && !bWasActuated)
{
if ((Now - LastPressTime) <= DoubleClickThreshold)
{ LastPressTime = 0.f; bWasActuated = bActuated; return ETriggerState::Triggered; }
LastPressTime = Now;
}
bWasActuated = bActuated;
return ETriggerState::None;
}UInputTriggerTimedBaseHeldDurationCalculateHeldDurationUInputModifierModifyRaw_ImplementationUCLASS(EditInlineNew, meta=(DisplayName="Clamp Magnitude"))
class MYGAME_API UClampMagnitudeModifier : public UInputModifier
{
GENERATED_BODY()
public:
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category=Settings)
float MaxMagnitude = 1.0f;
protected:
virtual FInputActionValue ModifyRaw_Implementation(
const UEnhancedPlayerInput* PlayerInput, FInputActionValue CurrentValue, float DeltaTime) override
{
FVector V = CurrentValue.Get<FVector>();
if (V.SizeSquared() > MaxMagnitude * MaxMagnitude)
V = V.GetSafeNormal() * MaxMagnitude;
return FInputActionValue(CurrentValue.GetValueType(), V);
}
};BeginPlayPossessSetupPlayerInputComponentBindAction(FName,...)BindAxis= deleteDefaultInputComponentClassDefaultInput.iniTriggeredStartedCompletedOngoingCompletedDeadZone(Radial)SwizzleAxis(YXZ)DownHoldTriggeredStartedStartedTriggeredHoldInputComponent->BindActionInputComponent->BindAxisUEnhancedInputComponent::BindActionUInputActionValueTypeUInputMappingContextDefaultInput.iniActionMappingsAxisMappingsDefaultInputComponentClassDefaultInput.iniAPlayerController::SetInputMode()PC->SetInputMode(FInputModeUIOnly()); // cursor captured by UI, no game input
PC->SetInputMode(FInputModeGameAndUI()); // both UI and game receive input
PC->SetInputMode(FInputModeGameOnly()); // full game input, UI events suppressedUCommonActivatableWidgetSetInputModeue-ui-umg-slateue-gameplay-frameworkPossessUnPossessSetupPlayerInputComponentue-ui-umg-slateSetInputMode(FInputModeUIOnly())FInputModeGameAndUI()UCommonActivatableWidgetue-gameplay-abilities