Production Ready Plugin

Steam Input API Plugin for Unreal Engine

A complete replacement for Unreal's built-in Steam Input support, delivering zero-boilerplate integration with Enhanced Input and Slate UI systems. Built to solve real developer pain points with modern architecture.

The Problem

Unreal's Legacy Steam Input Plugin

Unreal's built-in Steam Input support was built for the old input system and never properly updated. Developers face manual key mapping, no Enhanced Input integration, broken Slate UI support, and extensive boilerplate code just to get basic functionality working.

Modern, Zero-Boilerplate Solution

A complete rewrite that automatically generates Enhanced Input keys from Steam actions, provides native Slate bindings, and gracefully handles all edge cases. Configure once, then forget—it just works.

Key Features

Zero Configuration

Register your Steam actions once in project settings. The plugin automatically generates matching Enhanced Input keys and handles all the wiring.

🎮

Universal Device Support

Seamless support for gamepads, Steam Deck, keyboard/mouse, and any custom controller configuration through Steam's input system.

🖱️

Native Mouse Simulation

Actions marked as MouseInput automatically simulate raw mouse movement through Unreal's input pipeline—perfect for cursor controls and camera panning.

🎨

Slate UI Integration

Rebindable Slate navigation actions. Developers can customize UI controls without code changes or rebuilds.

🔄

Graceful Fallback

When Steam Input is disabled, all other input systems continue working normally. No interference, no broken controls.

⚙️

Action Set Support

Full support for Steam's action sets, enabling context-aware input schemes that adapt to different gameplay states.

Technical Architecture

The plugin integrates seamlessly with Unreal's input pipeline while maintaining complete compatibility with existing systems.

Input Processing Flow

Steam Input API
Custom Input Device
Enhanced Input
Game Logic
Core Implementation: Steam Input Controller Processing
void FSteamInputController::SendControllerEvents()
{
    if (!bControllerInitialized) return;

    const double CurrentTime = FPlatformTime::Seconds();
    InputHandle_t Controllers[STEAM_INPUT_MAX_COUNT];
    const int ControllerCount = SteamInput()->GetConnectedControllers(Controllers);

    for (int i = 0; i < ControllerCount; ++i)
    {
        const InputHandle_t Controller = Controllers[i];
        FControllerState& State = ControllerStates[i];

        // Activate the current action set for this controller
        SteamInput()->ActivateActionSet(Controller, ActiveActionSet[i]);

        // Process digital actions (buttons)
        for (const auto DigitalAction : DigitalActions)
        {
            const InputDigitalActionData_t ActionData =
                SteamInput()->GetDigitalActionData(Controller, DigitalAction.Key);

            bool PreviousState = State.DigitalStatusMap.FindOrAdd(DigitalAction.Value);

            if (!PreviousState && ActionData.bState)
            {
                // Button pressed
                MessageHandler->OnControllerButtonPressed(
                    DigitalAction.Value, UserId, DeviceId, false);
            }
            else if (PreviousState && !ActionData.bState)
            {
                // Button released
                MessageHandler->OnControllerButtonReleased(
                    DigitalAction.Value, UserId, DeviceId, false);
            }

            State.DigitalStatusMap.FindOrAdd(DigitalAction.Value) = ActionData.bState;
        }

        // Process analog actions (sticks, triggers, mouse input)
        for (const auto AnalogAction : AnalogActions)
        {
            const InputAnalogActionData_t ActionData =
                SteamInput()->GetAnalogActionData(Controller, AnalogAction.Key);

            if (AnalogAction.Value.Value == EKeyType::MouseInput)
            {
                // Direct mouse input simulation
                MessageHandler->OnRawMouseMove(ActionData.x, ActionData.y);
            }
            else
            {
                // Standard analog input (triggers, sticks)
                MessageHandler->OnControllerAnalog(
                    AnalogAction.Value.Key, UserId, DeviceId, ActionData.x);
            }
        }
    }
}

Real-World Implementation Highlights

  • Multi-Module Architecture: SteamToolsCore, SteamToolsInput, and SteamAchievements modules
  • Custom Type System: Blueprint-compatible wrappers (FInputHandle, FControllerActionHandle)
  • Enhanced Input Integration: Automatic key generation with proper FKeyDetails classification
  • Mouse Input Simulation: OnRawMouseMove integration for authentic cursor/camera control
  • Action Set Management: Runtime switching between different input contexts
  • Graceful Steam API Handling: Proper initialization checks and fallback behavior
  • Force Feedback Support: Haptic pulse integration with Unreal's FForceFeedbackValues
  • Configuration Persistence: INI-based settings with editor integration

Simple Configuration

Setting up the plugin requires minimal configuration. Just define your Steam actions and their types:

Enhanced Input Key Generation System
void USteamInputSettings::GenerateKey(const FName ActionName, const EKeyType KeyType) const
{
    switch (KeyType)
    {
    case EKeyType::Button:
        {
            const FKey Key{ActionName};
            if (EKeys::GetKeyDetails(Key) == nullptr)
            {
                EKeys::AddKey({Key, Key.GetDisplayName(),
                             FKeyDetails::GamepadKey, MenuCategory});
            }
        }
        break;
    case EKeyType::Analog:
        {
            const FKey Key{ActionName};
            EKeys::AddKey({Key, Key.GetDisplayName(),
                         FKeyDetails::GamepadKey | FKeyDetails::Axis1D, MenuCategory});
        }
        break;
    case EKeyType::MouseInput:
    case EKeyType::Joystick:
        {
            // Generate paired X/Y axis keys for 2D input
            const FKey KeyX{GetXAxisName(ActionName)};
            const FKey KeyY{GetYAxisName(ActionName)};
            const FKey Key{ActionName};

            EKeys::AddKey({KeyX, KeyX.GetDisplayName(),
                         FKeyDetails::GamepadKey | FKeyDetails::Axis1D, MenuCategory});
            EKeys::AddKey({KeyY, KeyY.GetDisplayName(),
                         FKeyDetails::GamepadKey | FKeyDetails::Axis1D, MenuCategory});
            EKeys::AddPairedKey({Key, Key.GetDisplayName(),
                               FKeyDetails::GamepadKey | FKeyDetails::Axis2D, MenuCategory},
                               KeyX, KeyY);
        }
        break;
    }
}
Configuration Example: Project Settings
// In USteamInputSettings class - actual configuration structure
UPROPERTY(Config, EditAnywhere, Category = "Actions")
TMap<FName, EKeyType> Keys;

// Example configuration in DefaultInput.ini:
[/Script/SteamToolsInput.SteamInputSettings]
Keys=(("Move", EKeyType::Joystick))
Keys=(("Look", EKeyType::MouseInput))
Keys=(("Jump", EKeyType::Button))
Keys=(("Interact", EKeyType::Button))
Keys=(("Fire", EKeyType::Button))
Keys=(("AimTrigger", EKeyType::Analog))

// Slate navigation bindings
KeyEventRules=(("Move_AxisY", EUINavigation::Up))
KeyEventRules=(("Move_AxisY", EUINavigation::Down))
KeyEventRules=(("Move_AxisX", EUINavigation::Left))
KeyEventRules=(("Move_AxisX", EUINavigation::Right))

KeyActionRules=(("Interact", EUINavigationAction::Accept))
KeyActionRules=(("MenuBack", EUINavigationAction::Back))

Impact & Results

100%
Enhanced Input Compatibility
0
Lines of Boilerplate Code
15+
Supported Device Types

Real-World Benefits

  • Katharsi Development: Reduced input system setup time from days to hours
  • Steam Deck Support: Native compatibility with no additional development effort
  • Controller Flexibility: Players can use any input device supported by Steam

Technical Deep Dive

Slate UI Navigation Integration

Runtime-configurable UI navigation that integrates directly with Unreal's Slate system:

Slate UI Navigation Integration
void USteamInputSettings::Internal_ApplySlateConfig() const
{
    if (FSlateApplication::IsInitialized())
    {
        const TSharedRef<FNavigationConfig> Config =
            FSlateApplication::Get().GetNavigationConfig();

        // Apply user-configured key bindings for UI navigation
        Config->KeyActionRules = KeyActionRules;   // Accept/Back actions
        Config->KeyEventRules = KeyEventRules;     // Directional navigation
    }
}

// Settings expose these for runtime customization:
UPROPERTY(Config, EditAnywhere, Category = "Slate Input")
TMap<FKey, EUINavigation> KeyEventRules;        // Up, Down, Left, Right

UPROPERTY(Config, EditAnywhere, Category = "Slate Input")
TMap<FKey, EUINavigationAction> KeyActionRules; // Accept, Back

Blueprint Function Library API

Complete Blueprint integration for runtime Steam Input management:

Blueprint Function Library API
// USteamInput class - Blueprint-accessible functions
UCLASS()
class STEAMTOOLSINPUT_API USteamInput : public UBlueprintFunctionLibrary
{
    GENERATED_BODY()
public:
    UFUNCTION(BlueprintCallable)
    static void ActivateActionSet(int32 ControllerID, FInputActionSetHandle ActionSet);

    UFUNCTION(BlueprintCallable, BlueprintPure)
    static FInputHandle GetControllerHandle(uint8 PlayerIndex);

    UFUNCTION(BlueprintCallable, BlueprintPure)
    static FInputActionSetHandle GetActionSetHandle(FName ActionSetName);

    UFUNCTION(BlueprintCallable)
    static bool ShowBindingPanel(int32 ControllerID);

    UFUNCTION(BlueprintCallable, BlueprintPure)
    static FControllerActionHandle GetActionHandle(FName ActionName);
};

Plugin Module Structure

The plugin is organized into focused modules for maintainability and extensibility:

SteamTools.uplugin - Module Definition
{
    "Modules": [
        {
            "Name": "SteamToolsCore",
            "Type": "Runtime",
            "LoadingPhase": "Default"
        },
        {
            "Name": "SteamToolsInput",
            "Type": "Runtime",
            "LoadingPhase": "Default"
        },
        {
            "Name": "SteamAchievements",
            "Type": "Runtime",
            "LoadingPhase": "Default"
        }
    ],
    "Plugins": [
        {
            "Name": "SteamShared",
            "Enabled": true
        },
        {
            "Name": "EnhancedInput",
            "Enabled": true
        }
    ]
}

Ready to Use It?

The plugin is open source and production-ready. Drop it into your project and start using Steam Input in minutes.