Skip to content

๐Ÿ’‰ Injecting Custom Device Logic (Custom DeviceManager)

rafaelvaloto edited this page Jun 17, 2026 · 2 revisions

You can also inject a custom implementation of the DeviceManager. This is useful if you want to implement your own input buffering, custom button mapping, or specialized haptic logic without modifying the middleware source.

Tip

To ensure that your custom implementation works with native Unreal Engine assets (like Haptic Feedback Effects, Force Feedback Assets, and Device Properties), your class must correctly implement or override the methods from IInputDevice and IHapticDevice.

Required Interfaces for Native Assets

If you want your custom manager to support native Unreal features, ensure it implements/overrides:

  • IHapticDevice: haptic assets.
    • SetHapticFeedbackValues: Processes frequency and amplitude values from assets.
    • GetHapticFrequencyRange: Determines the valid frequency range supported by the device.
    • GetHapticAmplitudeScale: Returns the scaling factor for amplitude mapping.
  • IInputDevice: Required for standard vibration, light color, and properties.
    • SetChannelValues / SetChannelValue: Essential for UForceFeedbackEffect assets.
    • SetLightColor / ResetLightColor: Controls the controller's LED.
    • SetDeviceProperty: Handles UInputDeviceProperty (e.g., Adaptive Triggers via Unreal 5.1+ system).
    • GetHapticDevice: Returns the IHapticDevice* interface (usually return this;).
    • IsGamepadAttached: Returns whether the device is currently connected.

Custom implementation example:

  1. Create your custom class inheriting from DeviceManager:
#include "DeviceManager.h"
#include "Modules/ModuleManager.h"
#include "WindowsDualsense_ds5w.h"

using namespace GCDevice;
class FMyCustomDeviceManager : public DeviceManager
{
public:
	using DeviceManager::DeviceManager;
	/** * Map to track the previous frame's touch state per DeviceID.
	 * Marked as 'mutable' so it can be updated within const methods.
	 */
	mutable TMap<int32, bool> DeviceTouchStates;
	    
	virtual void TouchpadImpl(FDeviceContext* Context, FInputContext& FrameInput, const FPlatformUserId UserId,
							  const FInputDeviceId InputDeviceId, float DeltaTime) const override
	{
		// --- 1. Basic Touch Mapping (Unreal Message Handler) ---
		if (Context->bEnableTouch)
		{
			bool& bWasTouchDown = DeviceTouchStates.FindOrAdd(InputDeviceId.GetId(), false);
	
			if (FrameInput.bIsTouching && !bWasTouchDown)
			{
				MessageHandler->OnTouchStarted(nullptr, FVector2D(FrameInput.TouchPosition.X, FrameInput.TouchPosition.Y), 1.0f, FrameInput.TouchId, UserId, InputDeviceId);
			}
			else if (FrameInput.bIsTouching && bWasTouchDown)
			{
				MessageHandler->OnTouchMoved(FVector2D(FrameInput.TouchPosition.X, FrameInput.TouchPosition.Y), 1.0f, FrameInput.TouchId, UserId, InputDeviceId);
			}
			else if (!FrameInput.bIsTouching && bWasTouchDown)
			{
				MessageHandler->OnTouchEnded(FVector2D(FrameInput.TouchPosition.X, FrameInput.TouchPosition.Y), FrameInput.TouchId, UserId, InputDeviceId);
			}
	
			bWasTouchDown = FrameInput.bIsTouching;
		}
		
		// --- 2. Gesture Mapping (Two-Finger Scroll) ---
		if (Context->bEnableGesture)
		{
			// Check if exactly 2 fingers are touching the pad
			if (FrameInput.bIsTouching && FrameInput.TouchFingerCount == 2)
			{
				MessageHandler->OnTouchGesture(
					EGestureEvent::Scroll,
					ScrollDelta,
					0.0f,   /* Value / Total movement if needed */
					false   /* IsInverted */
				);
			}
		}
	}

};
  1. Register your custom factory in your Game Module:
class FMyProject : public FDefaultGameModuleImpl {
public:
    virtual bool IsGameModule() const override { return true; }

    virtual void StartupModule() override {
        FWindowsDualsense_ds5wModule::SetCustomInputDeviceFactory([](const TSharedRef<FGenericApplicationMessageHandler>& InHandler)
        {
            UE_LOG(LogTemp, Log, TEXT("MyProject Game Module: Init FMyCustomDeviceManager."));
            return MakeShared<FMyCustomDeviceManager>(InHandler);
        });
    }
};

IMPLEMENT_PRIMARY_GAME_MODULE( FMyProject, MyProject, "MyProject" );
  1. Build Configuration Ensure your project's Build.cs includes the middleware module and enables C++20 support:
public class NewDeveloper : ModuleRules
{
    public NewDeveloper(ReadOnlyTargetRules Target) : base(Target)
    {
        PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;
        
        // Required for Concepts and Policy-Based architecture
        CppStandard = CppStandardVersion.Cpp20;

        PublicDependencyModuleNames.AddRange(new string[] { 
            ...
            "WindowsDualsense_ds5w" 
        });
    }
}

Back to the home