Plugin doesn't start on Game mode

Hi,

I wrote a small plugin to get inputs from a joystick device. It works fine when I play my game in the editor, but when I try to play with a packaged version, nothing happens.

Is there some option to enable plugin in the final product ?

Thanks for your answers.

In my tutorial i go over the different types of plugin, and that may be the culprit.

In order for your plugin to be loaded and executed at runtime you will need to set its type to Runtime. This lets the Build tool know that it will need to be included into the shipping package and bound into the monolithic executable. It also means that you will need to ensure that your code works for Win32, and that you link in the correctly versioned libraries too.

I did it. Here are my main files:

JoystickDevice.uplugin:


{
    "FileVersion" : 1,
	
	"FriendlyName" : "Joystick Plugin",
	"Version" : 1,
	"VersionName" : "1.0",
	"CreatedBy" : "LUSAGE",
	"EngineVersion" : 1579795,
	"Description" : "Enable use of joystick",
	"Category" : "Runtime",
	
    "Modules" :
	
		{
			"Name" : "JoystickDevice",
			"Type" : "Runtime"
		}
	]
}

JoystickDevice.Build.cs


namespace UnrealBuildTool.Rules
{
    public class JoystickDevice : ModuleRules
    {
        public JoystickDevice(TargetInfo Target)
        {
            PublicIncludePaths .AddRange( new string] { } );
            PrivateIncludePaths.AddRange( new string] { "JoystickDevice/Private", } );

            // Unreal Engine modules
            PrivateDependencyModuleNames.AddRange(new string]
				{
					"Core",
                    "CoreUObject",
                    "InputCore",
                    "Engine",
                    "InputDevice",
				}
            );

            // Link external libraries
            if ((Target.Platform == UnrealTargetPlatform.Win64) || (Target.Platform == UnrealTargetPlatform.Win32)) 
            {
                PrivateDependencyModuleNames.AddRange(new string] { "DX11", "DX11Input" });
            }
        }
    }
}


FJoystickDeviceModule.h:


#pragma once

#include "Runtime/InputDevice/Public/IInputDeviceModule.h"

class FJoystickDeviceModule : public IInputDeviceModule {
	virtual TSharedPtr< class IInputDevice > CreateInputDevice(const TSharedRef< FGenericApplicationMessageHandler >& InMessageHandler) OVERRIDE;
};

FJoystickDeviceModule.cpp:


#include "JoystickDevice.h"
#include "FJoystickDeviceModule.h"
#include "FJoystickDevice.h"

TSharedPtr< class IInputDevice > FJoystickDeviceModule::CreateInputDevice(const TSharedRef< FGenericApplicationMessageHandler >& InMessageHandler)
{
	return MakeShareable(new FJoystickDevice(InMessageHandler));
}

IMPLEMENT_MODULE(FJoystickDeviceModule, JoystickDevice)


I also added


AdditionalPlugins.AddRange(new string] { "JoystickDevice" });

in MyProject.Target.cs and


 PublicDependencyModuleNames.AddRange(new string] {  "JoystickDevice", });

in MyProject.Build.cs.

In that case, join us on IRC - we will need to triage this in real time.

Can you post your whole plugin code? i’m really interested in create my own custom input plugin but i really cannot find much documentation about

Sure ! It’s not clean yet and it only works for Windows (I found the integration of the Windows API on the Internet, but I can’t remember where…).

FJoystickDevice.h


#pragma once

#include "Runtime/InputDevice/Public/IInputDevice.h"

namespace { // Avoid conflict with other Unreal Engine classes

	static const int32 MAX_NUM_CONTROLLER_BUTTONS = 4;

	typedef void *IOJoystickDevicePtr;
	typedef void *IOJoystickManagerPtr;

	struct FJoystickButtonInfo {
		uint8 Value;
	};

	struct FJoystickAxisInfo {
		int32 xValue   =  0;
		int32 yValue   = 0;
		int32 MinValue = 0;
		int32 MaxValue = 65535;
	};

	struct FJoystickDeviceInfo {
		IOJoystickDevicePtr DevicePtr = nullptr;
		TArray<FJoystickButtonInfo> Buttons;
		FJoystickAxisInfo AxisInfo;
	};

	struct FControllerState {
		/** Last frame's button states, so we only send events on edges */
		uint8 ButtonStates[MAX_NUM_CONTROLLER_BUTTONS];

		/** Next time a repeat event should be generated for each button */
		double NextRepeatTime[MAX_NUM_CONTROLLER_BUTTONS];

		/** Raw x analog value */
		int16 xAnalog;

		/** Raw y analog value */
		int16 yAnalog;

		/** Id of the controller */
		int32 ControllerId;

		FJoystickDeviceInfo Device;
	};
}

/**
* Joystick input interface.
*/
class FJoystickDevice : public IInputDevice
{
public:
	explicit FJoystickDevice(const TSharedRef<FGenericApplicationMessageHandler>& InMessageHandler);
	virtual ~FJoystickDevice();

	/** Tick the interface (e.g. check for new controllers) */
	virtual void Tick(float DeltaTime) OVERRIDE;

	/** Poll for controller state and send events if needed */
	virtual void SendControllerEvents() OVERRIDE;

	/** Set which MessageHandler will get the events from SendControllerEvents. */
	virtual void SetMessageHandler(const TSharedRef< FGenericApplicationMessageHandler >& InMessageHandler) OVERRIDE;

	/** Exec handler to allow console commands to be passed through for debugging */
	virtual bool Exec(UWorld* InWorld, const TCHAR* Cmd, FOutputDevice& Ar) OVERRIDE;

	/**
	* IForceFeedbackSystem pass through functions
	*/
	virtual void SetChannelValue(int32 ControllerId, FForceFeedbackChannelType ChannelType, float Value) OVERRIDE;
	virtual void SetChannelValues(int32 ControllerId, const FForceFeedbackValues &values) OVERRIDE;

private:
	/** Names of all the buttons */
	EControllerButtons::Type Buttons[MAX_NUM_CONTROLLER_BUTTONS];

	/** Controller state */
	FControllerState ControllerState;

	/** Delay before sending a repeat message after a button was first pressed */
	float InitialButtonRepeatDelay;

	/** Delay before sending a repeat message after a button has been pressed for a while */
	float ButtonRepeatDelay;

	IOJoystickManagerPtr JoystickManager;

	TSharedRef< FGenericApplicationMessageHandler > MessageHandler;
};


FJoystickDevice.cpp


#include "JoystickDevice.h"
#include "FJoystickDevice.h"
#include "JoystickUtils.h"

namespace IOJoystickDevice {
	extern IOJoystickManagerPtr CreateManager();
	extern bool OpenManager(IOJoystickManagerPtr Manager);
	extern void ReleaseManager(IOJoystickManagerPtr Manager);

	extern bool OpenDevice(IOJoystickManagerPtr Manager, FJoystickDeviceInfo &Device);
	extern void CloseDevice(IOJoystickManagerPtr Manager, FJoystickDeviceInfo &Device);
	extern bool GetDeviceState(IOJoystickManagerPtr Manager, FJoystickDeviceInfo &Device);
}

#include "win_callbacks.h"

#if !JOYSTICK_DEVICE_ENABLED

FORCEINLINE IOJoystickManagerPtr IOJoystickDevice::CreateManager() { return nullptr; }
FORCEINLINE bool IOJoystickDevice::OpenManager(IOJoystickManagerPtr Manager) { return false; }
FORCEINLINE void IOJoystickDevice::ReleaseManager(IOJoystickManagerPtr Manager) {}

FORCEINLINE bool IOJoystickDevice::OpenDevice(IOJoystickManagerPtr Manager, FJoystickDeviceInfo &Device) { Device.DevicePtr = nullptr; return false; }
FORCEINLINE void IOJoystickDevice::CloseDevice(IOJoystickManagerPtr Manager, FJoystickDeviceInfo &Device) {}
FORCEINLINE bool IOJoystickDevice::GetDeviceState(IOJoystickManagerPtr Manager, FJoystickDeviceInfo &Device) { return false; }

#endif

FJoystickDevice::FJoystickDevice(const TSharedRef<FGenericApplicationMessageHandler>& InMessageHandler) :
MessageHandler(InMessageHandler)
{
	FMemory::Memzero(&ControllerState, sizeof(FControllerState));

	InitialButtonRepeatDelay = 0.2f;
	ButtonRepeatDelay = 0.1f;

	Buttons[0] = EControllerButtons::FaceButtonBottom;
	Buttons[1] = EControllerButtons::FaceButtonTop;
	Buttons[2] = EControllerButtons::SpecialRight;
	Buttons[3] = EControllerButtons::FaceButtonRight;

	// Init Joystick Manager
	JoystickManager = IOJoystickDevice::CreateManager();
	if (!JoystickManager)
		return;

	if (!IOJoystickDevice::OpenManager(JoystickManager)) {
		IOJoystickDevice::ReleaseManager(JoystickManager);
		JoystickManager = nullptr;
		JD_CERR("Failed to open a joystick manager!");
		return;
	}

	JD_COUT("Joystick manager successfully created.");
}

FJoystickDevice::~FJoystickDevice()
{
	if (ControllerState.Device.DevicePtr)
		IOJoystickDevice::CloseDevice(JoystickManager, ControllerState.Device);
	if (JoystickManager)
		IOJoystickDevice::ReleaseManager(JoystickManager);
}

void FJoystickDevice::Tick(float DeltaTime)
{
	// Get the joystick device if it's not already aquiered
	if (JoystickManager && !ControllerState.Device.DevicePtr) 
		if (!IOJoystickDevice::OpenDevice(JoystickManager, ControllerState.Device)) {
			JD_CERR("Failed to connect with a joystick!");
		} else {
			JD_COUT("Joystick successfully connected.");
			if (GEngine)
				GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Yellow, TEXT("Joystick successfully connected."));
		}
	if (GEngine)
		GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Red, TEXT("Tick Test."));
}

void FJoystickDevice::SendControllerEvents()
{
	if (!JoystickManager || !ControllerState.Device.DevicePtr)
		return;
	 
	if (!IOJoystickDevice::GetDeviceState(JoystickManager, ControllerState.Device)) {
		JD_CERR("Failed to get joystick state!");
		return;
	}

	float x =   2.0f * float(ControllerState.Device.AxisInfo.xValue - ControllerState.Device.AxisInfo.MinValue) / float(ControllerState.Device.AxisInfo.MaxValue - ControllerState.Device.AxisInfo.MinValue) - 1.0f;
	float y = -(2.0f * float(ControllerState.Device.AxisInfo.yValue - ControllerState.Device.AxisInfo.MinValue) / float(ControllerState.Device.AxisInfo.MaxValue - ControllerState.Device.AxisInfo.MinValue) - 1.0f);

	JD_COUT("Joystick analog: (%f | %f)", x, y);

	MessageHandler->OnControllerAnalog(EControllerButtons::LeftAnalogX, ControllerState.ControllerId, x);
	MessageHandler->OnControllerAnalog(EControllerButtons::LeftAnalogY, ControllerState.ControllerId, y);

	const double CurrentTime = FPlatformTime::Seconds();

	for (int i = 0; i < MAX_NUM_CONTROLLER_BUTTONS; ++i) {
		uint8   currentState	= ControllerState.Device.Buttons*.Value;
		uint8&  previousState	= ControllerState.ButtonStates*;
		double& nextRepeatTime	= ControllerState.NextRepeatTime*;

		if (currentState != previousState) {
			if (currentState) {
				MessageHandler->OnControllerButtonPressed(Buttons*, ControllerState.ControllerId, false);
				nextRepeatTime = CurrentTime + InitialButtonRepeatDelay;
				JD_COUT("Button %d is pressed", i);
			}
			else {
				MessageHandler->OnControllerButtonReleased(Buttons*, ControllerState.ControllerId, false);
				JD_COUT("Button %d is released", i);
			}
		} else if (currentState && nextRepeatTime <= CurrentTime) {
			MessageHandler->OnControllerButtonPressed(Buttons*, ControllerState.ControllerId, true);
			nextRepeatTime = CurrentTime + ButtonRepeatDelay;
		}

		previousState = currentState;
	}
}

void FJoystickDevice::SetMessageHandler(const TSharedRef< FGenericApplicationMessageHandler >& InMessageHandler)
{
	MessageHandler = InMessageHandler;
}

bool FJoystickDevice::Exec(UWorld* InWorld, const TCHAR* Cmd, FOutputDevice& Ar)
{
	return false; 
}

void FJoystickDevice::SetChannelValue(int32 ControllerId, FForceFeedbackChannelType ChannelType, float Value)
{
	// Not implemented yet (and probably not necessary in our context, patient don't need aptic feedback)
}

void FJoystickDevice::SetChannelValues(int32 ControllerId, const FForceFeedbackValues &values)
{
	// Not implemented yet (and probably not necessary in our context, patient don't need aptic feedback)
}


win_callbacks.h


#pragma once

#if PLATFORM_WINDOWS

#define JOYSTICK_DEVICE_ENABLED 1

#define STRICT 
#define DIRECTINPUT_VERSION 0x0800 
#define _CRT_SECURE_NO_DEPRECATE 
#ifndef _WIN32_DCOM 
#define _WIN32_DCOM 
#endif 

#include "AllowWindowsPlatformTypes.h"

#include <windows.h> 
#include <stdio.h> 
#include <tchar.h> 
#include <commctrl.h> 
#include <basetsd.h> 

#pragma warning(push) 
#pragma warning(disable:6000 28251) 
#include <dinput.h> 
#pragma warning(pop) 

#include <dinputd.h> 
#include <assert.h> 
#include <oleauto.h> 
#include <shellapi.h> 
#include <wbemidl.h>

#include "JoystickDevice.h"
#include "JoystickUtils.h"
#include "FJoystickDevice.h"

namespace {

	struct XINPUT_DEVICE_NODE {
		DWORD dwVidPid;
		XINPUT_DEVICE_NODE* pNext;
	};

	struct FJoystickManager {
		XINPUT_DEVICE_NODE*     pXInputDeviceList = nullptr;
		LPDIRECTINPUT8          pDI = nullptr;
		LPDIRECTINPUTJOYCONFIG8 pJoyConfig = nullptr;
		DIJOYCONFIG				PreferredJoyCfg;
		bool					bFilterOutXinputDevices = false;

		struct {
			DIJOYCONFIG* pPreferredJoyCfg;
			bool bPreferredJoyCfgValid;
		} enumContext;
	};

	struct FJoystickHandler {
		FJoystickManager *Manager;
		FJoystickDeviceInfo &DeviceInfo;

		FORCEINLINE FJoystickHandler(FJoystickManager *InManager, FJoystickDeviceInfo &InDeviceInfo) :
			Manager(InManager),
			DeviceInfo(InDeviceInfo)
		{}
	};

	/**
	* Enum each PNP device using WMI and check each device ID to see if it contains "IG_" (ex. "VID_045E&PID_028E&IG_00").
	* If it does, then it's an XInput device. Unfortunately this information can not be found by just using DirectInput.
	* Checking against a VID/PID of 0x028E/0x045E won't find 3rd party or future XInput devices.
	*
	* This function stores the list of xinput devices in a linked list at pXInputDeviceList, and IsXInputDevice() searchs that linked list.
	*/
	FORCEINLINE HRESULT SetupForIsXInputDevice(FJoystickManager *Manager) {
		IWbemServices*			pIWbemServices = nullptr;
		IEnumWbemClassObject*	pEnumDevices = nullptr;
		IWbemLocator*			pIWbemLocator = nullptr;
		IWbemClassObject*		pDevices[20] = { 0 };
		BSTR					bstrDeviceID = nullptr;
		BSTR					bstrClassName = nullptr;
		BSTR					bstrNamespace = nullptr;
		DWORD					uReturned = 0;
		bool					bCleanupCOM = false;
		UINT					iDevice = 0;
		VARIANT					var;
		HRESULT					hr;

		// CoInit if needed 
		bCleanupCOM = SUCCEEDED(CoInitialize(nullptr));

		// Create WMI 
		if (FAILED(hr = CoCreateInstance(__uuidof(WbemLocator), nullptr, CLSCTX_INPROC_SERVER, __uuidof(IWbemLocator), (LPVOID*)&pIWbemLocator)) || !pIWbemLocator) {
			JD_CERR("Failed to create COM instance!");
			goto LCleanup;
		}

		// Create BSTRs for WMI 
		bstrNamespace = SysAllocString(L"\\\\.\\root\\cimv2"); if (!bstrNamespace) goto LCleanup;
		bstrDeviceID = SysAllocString(L"DeviceID");           if (!bstrDeviceID)  goto LCleanup;
		bstrClassName = SysAllocString(L"Win32_PNPEntity");    if (!bstrClassName) goto LCleanup;

		// Connect to WMI  
		if (FAILED(hr = pIWbemLocator->ConnectServer(bstrNamespace, nullptr, nullptr, 0L, 0L, nullptr, nullptr, &pIWbemServices)) || !pIWbemServices) {
			JD_CERR("Failed to connect WMI server!");
			goto LCleanup;
		}

		// Switch security level to IMPERSONATE 
		CoSetProxyBlanket(pIWbemServices, RPC_C_AUTHN_WINNT, RPC_C_AUTHZ_NONE, nullptr, RPC_C_AUTHN_LEVEL_CALL, RPC_C_IMP_LEVEL_IMPERSONATE, nullptr, 0);

		// Get list of Win32_PNPEntity devices 
		if (FAILED(hr = pIWbemServices->CreateInstanceEnum(bstrClassName, 0, nullptr, &pEnumDevices)) || !pEnumDevices) {
			JD_CERR("Failed to get the list of Win32_PNPEntity devices!");
			goto LCleanup;
		}

		// Loop over all devices 
		for (;;) {
			// Get 20 at a time 
			if (FAILED(hr = pEnumDevices->Next(10000, 20, pDevices, &uReturned))) {
				JD_CERR("Failed to get devices!");
				goto LCleanup;
			}
			if (uReturned == 0)
				break;

			for (iDevice = 0; iDevice < uReturned; ++iDevice) {
				if (!pDevices[iDevice])
					continue;

				// For each device, get its device ID 
				if (SUCCEEDED(pDevices[iDevice]->Get(bstrDeviceID, 0L, &var, nullptr, nullptr)) && var.vt == VT_BSTR && var.bstrVal) {
					// Check if the device ID contains "IG_".  If it does, then it's an XInput device 
					// Unfortunately this information can not be found by just using DirectInput  
					if (wcsstr(var.bstrVal, L"IG_")) {
						// If it does, then get the VID/PID from var.bstrVal 
						DWORD dwPid = 0, dwVid = 0;
						WCHAR* strVid = wcsstr(var.bstrVal, L"VID_");
						if (strVid && swscanf(strVid, L"VID_%4X", &dwVid) != 1)
							dwVid = 0;
						WCHAR* strPid = wcsstr(var.bstrVal, L"PID_");
						if (strPid && swscanf(strPid, L"PID_%4X", &dwPid) != 1)
							dwPid = 0;

						DWORD dwVidPid = MAKELONG(dwVid, dwPid);

						// Add the VID/PID to a linked list 
						XINPUT_DEVICE_NODE* pNewNode = new XINPUT_DEVICE_NODE;
						if (pNewNode) {
							pNewNode->dwVidPid = dwVidPid;
							pNewNode->pNext = Manager->pXInputDeviceList;
							Manager->pXInputDeviceList = pNewNode;
						}
					}
				}
				SAFE_RELEASE(pDevices[iDevice]);
			}
		}

	LCleanup:
		if (bstrNamespace)
			SysFreeString(bstrNamespace);
		if (bstrDeviceID)
			SysFreeString(bstrDeviceID);
		if (bstrClassName)
			SysFreeString(bstrClassName);
		for (iDevice = 0; iDevice < 20; iDevice++)
			SAFE_RELEASE(pDevices[iDevice]);
		SAFE_RELEASE(pEnumDevices);
		SAFE_RELEASE(pIWbemLocator);
		SAFE_RELEASE(pIWbemServices);

		return hr;
	}

	/**
	* Returns true if the DirectInput device is also an XInput device.
	* Call SetupForIsXInputDevice() before, and CleanupForIsXInputDevice() after
	*/
	FORCEINLINE bool IsXInputDevice(const GUID* pGuidProductFromDirectInput, FJoystickManager *Manager) {
		// Check each xinput device to see if this device's vid/pid matches 
		XINPUT_DEVICE_NODE* pNode = Manager->pXInputDeviceList;
		while (pNode) {
			if (pNode->dwVidPid == pGuidProductFromDirectInput->Data1)
				return true;
			pNode = pNode->pNext;
		}

		return false;
	}

	/**
	* Cleanup needed for IsXInputDevice()
	*/
	FORCEINLINE void CleanupForIsXInputDevice(FJoystickManager *Manager) {
		// Cleanup linked list 
		XINPUT_DEVICE_NODE* pNode = Manager->pXInputDeviceList;
		while (pNode) {
			XINPUT_DEVICE_NODE* pDelete = pNode;
			pNode = pNode->pNext;
			delete pDelete;
		}
	}

	/**
	* Called once for each enumerated joystick.
	* If we find one, create a device interface on it so we can play with it.
	*/
	static BOOL CALLBACK EnumJoysticksCallback(const DIDEVICEINSTANCE* pdidInstance, VOID* pContext) {
		FJoystickHandler *Handler = reinterpret_cast<FJoystickHandler*>(pContext);
		auto Manager = Handler->Manager;
		auto &pJoystick = reinterpret_cast<LPDIRECTINPUTDEVICE8&>(Handler->DeviceInfo.DevicePtr);

		if (Manager->bFilterOutXinputDevices && IsXInputDevice(&pdidInstance->guidProduct, Manager)) {
			JD_COUT("This joystick is not a XInputDevice.");
			return DIENUM_CONTINUE;
		}

		// Skip anything other than the perferred joystick device as defined by the control panel.   
		// Instead you could store all the enumerated joysticks and let the user pick. 
		if (Manager->enumContext.bPreferredJoyCfgValid && !IsEqualGUID(pdidInstance->guidInstance, Manager->enumContext.pPreferredJoyCfg->guidInstance)) {
			JD_COUT("This joystick doesn\'t match the requiered configuration.");
			return DIENUM_CONTINUE;
		}

		// Obtain an interface to the enumerated joystick. 
		// If it failed, then we can't use this joystick. (Maybe the user unplugged it while we were in the middle of enumerating it.) 
		if (FAILED(Manager->pDI->CreateDevice(pdidInstance->guidInstance, &pJoystick, nullptr))) {
			JD_COUT("Failed to create this joystick device.");
			return DIENUM_CONTINUE;
		}

		// Stop enumeration. Note: we're just taking the first joystick we get. You could store all the enumerated joysticks and let the user pick. 
		JD_COUT("End of the joystick enumeration");
		return DIENUM_STOP;
	}

	/**
	* Callback function for enumerating objects (axes, buttons, POVs) on a joystick.
	* This function enables user interface elements for objects that are found to exist, and scales axes min/max values.
	*/
	static BOOL CALLBACK EnumObjectsCallback(const DIDEVICEOBJECTINSTANCE* pdidoi, VOID* pContext) {
		FJoystickHandler *Handler = reinterpret_cast<FJoystickHandler*>(pContext);
		auto &DeviceInfo = Handler->DeviceInfo;
		auto &pJoystick = reinterpret_cast<LPDIRECTINPUTDEVICE8&>(Handler->DeviceInfo.DevicePtr);

		// For axes that are returned, set the DIPROP_RANGE property for the 
		// enumerated axis in order to scale min/max values. 
		if (pdidoi->dwType & DIDFT_AXIS) {
			DIPROPRANGE diprg;
			diprg.diph.dwSize = sizeof(DIPROPRANGE);
			diprg.diph.dwHeaderSize = sizeof(DIPROPHEADER);
			diprg.diph.dwHow = DIPH_BYID;
			diprg.diph.dwObj = pdidoi->dwType; // Specify the enumerated axis 

			// Get the range for the axis 
			HRESULT hr = pJoystick->GetProperty(DIPROP_RANGE, &diprg.diph);
			switch (hr) {
			case DIERR_INVALIDPARAM:
				JD_CERR("Failed to set joystick property: Invalid parameter!");
				return DIENUM_STOP;
			case DIERR_NOTINITIALIZED:
				JD_CERR("Failed to set joystick property: Not initialized!");
				return DIENUM_STOP;
			case DIERR_OBJECTNOTFOUND:
				JD_CERR("Failed to set joystick property: Object not found!");
				return DIENUM_STOP;
			case DIERR_UNSUPPORTED:
				JD_CERR("Failed to set joystick property: Unknown error code (%#x)!", hr);
				return DIENUM_STOP;
			}

			DeviceInfo.AxisInfo.MinValue = diprg.lMin;
			DeviceInfo.AxisInfo.MaxValue = diprg.lMax;
		}

		return DIENUM_CONTINUE;
	}
}

FORCEINLINE IOJoystickManagerPtr IOJoystickDevice::CreateManager() {
	return new FJoystickManager();
}

FORCEINLINE void IOJoystickDevice::ReleaseManager(IOJoystickManagerPtr JoystickManager) {
	FJoystickManager* Manager = reinterpret_cast<FJoystickManager*>(JoystickManager);
	SAFE_RELEASE(Manager->pJoyConfig);
	SAFE_RELEASE(Manager->pDI);

	delete Manager;
}

FORCEINLINE bool IOJoystickDevice::OpenManager(IOJoystickManagerPtr JoystickManager) {
	FJoystickManager* Manager = reinterpret_cast<FJoystickManager*>(JoystickManager);

	// Register with the DirectInput subsystem and get a pointer to a IDirectInput interface we can use. 
	// Create a DInput object 
	if (FAILED(DirectInput8Create(GetModuleHandle(NULL), DIRECTINPUT_VERSION, IID_IDirectInput8, (VOID**)&Manager->pDI, nullptr))) {
		JD_CERR("Failed to create DInput object!");
		return false;
	}

	if (Manager->bFilterOutXinputDevices)
		SetupForIsXInputDevice(Manager);

	FMemory::Memzero(&Manager->PreferredJoyCfg, sizeof(Manager->PreferredJoyCfg));
	Manager->enumContext.pPreferredJoyCfg = &Manager->PreferredJoyCfg;
	Manager->enumContext.bPreferredJoyCfgValid = false;

	if (FAILED(Manager->pDI->QueryInterface(IID_IDirectInputJoyConfig8, (void**)&Manager->pJoyConfig))) {
		JD_CERR("Failed to query joystick interface!");
		return false;
	}
	Manager->PreferredJoyCfg.dwSize = sizeof(Manager->PreferredJoyCfg);

	return true;
}

FORCEINLINE bool IOJoystickDevice::OpenDevice(IOJoystickManagerPtr JoystickManager, FJoystickDeviceInfo &DeviceInfo) {
	FJoystickManager* Manager = reinterpret_cast<FJoystickManager*>(JoystickManager);
	auto &pJoystick = reinterpret_cast<LPDIRECTINPUTDEVICE8&>(DeviceInfo.DevicePtr);

	if (SUCCEEDED(Manager->pJoyConfig->GetConfig(0, &Manager->PreferredJoyCfg, DIJC_GUIDINSTANCE))) // This function is expected to fail if no joystick is attached 
		Manager->enumContext.bPreferredJoyCfgValid = true;

	// Look for a simple joystick we can use
	FJoystickHandler joystickHandler = { Manager, DeviceInfo };
	if (FAILED(Manager->pDI->EnumDevices(DI8DEVCLASS_GAMECTRL, EnumJoysticksCallback, &joystickHandler, DIEDFL_ATTACHEDONLY))) {
		JD_CERR("Failed to enumerate devices!");
		return false;
	}

	if (Manager->bFilterOutXinputDevices)
		CleanupForIsXInputDevice(Manager);

	// Make sure we got a joystick 
	if (!pJoystick) {
		JD_CERR("Failed to get a joystick!");
		return false;
	}

	// Set the data format to "simple joystick" - a predefined data format  
	// 
	// A data format specifies which controls on a device we are interested in, 
	// and how they should be reported. This tells DInput that we will be 
	// passing a DIJOYSTATE structure to IDirectInputDevice::GetDeviceState(). 
	if (FAILED(pJoystick->SetDataFormat(&c_dfDIJoystick))) {
		JD_CERR("Failed to initialize joystick!");
		return false;
	}

	// Enumerate the joystick objects. The callback function enabled user 
	// interface elements for objects that are found, and sets the min/max 
	// values property for discovered axes. 
	if (FAILED(pJoystick->EnumObjects(EnumObjectsCallback, &joystickHandler, DIDFT_ALL))) {
		JD_CERR("Failed to enumarate joystick objects!");
		return false;
	}

	DeviceInfo.Buttons.SetNumZeroed(MAX_NUM_CONTROLLER_BUTTONS);

	return true;
}

FORCEINLINE void IOJoystickDevice::CloseDevice(IOJoystickManagerPtr JoystickManager, FJoystickDeviceInfo &DeviceInfo) {
	auto &pJoystick = reinterpret_cast<LPDIRECTINPUTDEVICE8&>(DeviceInfo.DevicePtr);

	// Unacquire the device one last time just in case  
	// the app tried to exit while the device is still acquired. 
	if (pJoystick)
		pJoystick->Unacquire();

	// Release any DirectInput objects. 
	SAFE_RELEASE(pJoystick);
}

FORCEINLINE bool IOJoystickDevice::GetDeviceState(IOJoystickManagerPtr JoystickManager, FJoystickDeviceInfo &DeviceInfo) {
	FJoystickManager* Manager = reinterpret_cast<FJoystickManager*>(JoystickManager);
	auto &pJoystick = reinterpret_cast<LPDIRECTINPUTDEVICE8&>(DeviceInfo.DevicePtr);

	DIJOYSTATE js;				// DInput joystick state  

	// Poll the device to read the current state 
	if (FAILED(pJoystick->Poll())) {
		// DInput is telling us that the input stream has been 
		// interrupted. We aren't tracking any state between polls, so 
		// we don't have any special reset that needs to be done. We 
		// just re-acquire and try again. 
		HRESULT hr;
		do {
			hr = pJoystick->Acquire();
		} while (hr == DIERR_INPUTLOST);

		switch (hr) {
		case DI_OK:
			// hr may be DIERR_OTHERAPPHASPRIO or other errors.  This 
			// may occur when the app is minimized or in the process of  
			// switching, so just try again later  
			return false;
		case DIERR_INVALIDPARAM:
			JD_CERR("Failed to poll the joystick: Invalid parameter!");
			return false;
		case DIERR_NOTINITIALIZED:
			JD_CERR("Failed to poll the joystick: Not initialized!");
			return false;
		case DIERR_OTHERAPPHASPRIO:
			JD_CERR("Failed to poll the joystick: Already aquiered by another app with a higher priority!");
			return false;
		default:
			JD_CERR("Failed to poll the joystick: Unknown error code (%#x)!", hr);
			return false;
		}
	}

	// Get the input's device state 
	if (FAILED(pJoystick->GetDeviceState(sizeof(DIJOYSTATE), &js))) {
		JD_CERR("Failed to get joystick device state!");
		return false; // The device should have been acquired during the Poll() 
	}

	// Update parameters
	DeviceInfo.AxisInfo.xValue = js.lX;
	DeviceInfo.AxisInfo.yValue = js.lY;
	for (int32 i = 0; i < MAX_NUM_CONTROLLER_BUTTONS; ++i)
		DeviceInfo.Buttons*.Value = js.rgbButtons*;

	return true;
}

#include "HideWindowsPlatformTypes.h"

#endif



I solved my problem, abandoning the idea of ​​using a plugin. I just created another module in my project and it works fine.

By doing that I may have understood what the issue was. In JoystickDevice.Build.cs, I tried to link the module “InputDevice”


 PrivateDependencyModuleNames.AddRange(new string] { ..., "InputDevice", }); 

whose only has an editor dll. Then, my plugin has probably failed to load (or be built) in game mode (without error message).
Just changing “InputeDevice” by “InputCore” should do the job.