Creating Input Devices - Epic Wiki

# Creating Input Devices

From Epic Wiki

Jump to: navigation, search

# Contents

# Overview

This page will detail how to create custom Input Device plugins in order to add support for additional controller/input types. It will also show example code for adding additional Key/Gamepad Input Names. The code will show how to fire events from the existing Input Names via a MessageHandler and how to fire events from any Key/Gamepad Input directly.

WARNING: The code is currently unstable! There is a bug with ForceFeedback that is preventing a build. I will have this fixed by 25th July unless someone fixes it before then.

(Warning: firing direct messages bypasses 'controller#, and therefore does not support local multilayer)

# Example Code

The code will show how to create a plugin for a psudo Input Device which will simulate controller#1 pressing the bottom face button every .1 seconds. It will also create a new custom Gamepad Input called "Psudo Player Weight" and fire events for this input with a value of 75.0 (kg).

In your own code you will most likely link your plugin with a static/dynamic library which communicates with you Input Device. When the engine calls FPsudoInputsInputDevice::SendControllerEvents() you can then pass on any events/polled controller states using the MessageHandler in a generic way.

# Plugin Code

# uPlugin Ddefinition

/Plugins/PsudoInputs/PsudoInputs.uplugin

{ "FileVersion" : 0,   "FriendlyName" : "Psudo Inputs Plugin" "Version" : 0, "VersionName" : "0.2", "CreatedBy" : "mspe044@gmail.com", "EngineVersion" : 1579795, "Description" : "I wish I was a real input! 😢", "Category" : "MyInputs",   "Modules" : [ { "Name" : "PsudoInputs", "Type" : "Runtime", "LoadingPhase" : "PreDefault" } ] }

# Module Build File

This is where you link to any library supporting your Input Device. There is a sample method 'LoadYourThirdPartyLibraries()' to help you do this however the call to it is currently commented out.

This method will will link to a static library in .../Plugins/PsudoInputs/Source/PsudoInputs/ThirdParty/LibraryDirName/... with include files in the sub directory .../include/ and library code in subdirectoires seprated by compile arcitecture I.E. .../Win64/VS2013/MyLibrary.lib

/Plugins/PsudoInputs/Source/PsudoInputs/PsudoInputs.Build.cs

namespace UnrealBuildTool.Rules { using System.IO;   public class PsudoInputs : ModuleRules { public PsudoInputs(TargetInfo Target) { // I chose not to use PCH, this is proberbly slower and worse PCHUsage = PCHUsageMode.NoSharedPCHs;   // ... add public include paths required here ... PublicIncludePaths.AddRange( new string[] { "PsudoInputs/Public", });   // ... add other private include paths required here ... PrivateIncludePaths.AddRange( new string[] { "PsudoInputs/Private", });   // ... add other public dependencies that you statically link with here ... PublicDependencyModuleNames.AddRange( new string[] { "Core", "CoreUObject", // Provides Actors and Structs "Engine", // Used by Actor "Slate", // Used by InputDevice to fire bespoke FKey events "InputCore", // Provides LOCTEXT and other Input features "InputDevice", // Provides IInputInterface });   // ... add private dependencies that you statically link with here ... PrivateDependencyModuleNames.AddRange( new string[] { });   // ... add any modules that your module loads dynamically here ... DynamicallyLoadedModuleNames.AddRange( new string[] { });   // !!!!!!!!!! UNCOMMENT THIS IF YOU WANT TO CALL A LIBRARY !!!!!!!!!! //LoadYourThirdPartyLibraries(Target); }   public bool LoadYourThirdPartyLibraries(TargetInfo Target) { bool isLibrarySupported = false;   // This will give oyu a relitive path to the module ../PsudoInputs/ string ModulePath = Path.GetDirectoryName(RulesCompiler.GetModuleFilename(this.GetType().Name)); // This will give you a relative path to ../PsudoInputs/ThirdParty/"LibraryDirName"/ string MyLibraryPath = Path.Combine(ModulePath, "ThirdParty", "LibraryDirName");   // Use this to keep Win32/Win64/e.t.c. library files in seprate subdirectories string ArchitecturePath = "";   // When you are building for Win64 if (Target.Platform == UnrealTargetPlatform.Win64 && WindowsPlatform.Compiler == WindowsCompiler.VisualStudio2013) { // We will look for the library in ../PsudoInputs/ThirdParty/MyLibrary/Win64/VS20##/ ArchitecturePath = Path.Combine("Win64", "VS" + WindowsPlatform.GetVisualStudioCompilerVersionName());   isLibrarySupported = true; } // When you are building for Win32 else if (Target.Platform == UnrealTargetPlatform.Win32 && WindowsPlatform.Compiler == WindowsCompiler.VisualStudio2013) { // We will look for the library in ../PsudoInputs/ThirdParty/MyLibrary/Win32/VS20##/ ArchitecturePath = Path.Combine("Win32", "VS" + WindowsPlatform.GetVisualStudioCompilerVersionName());   isLibrarySupported = true; } // Add mac/linux/mobile support in much the same way   // If the current build architecture was supported by the above if statements if (isLibrarySupported) { // Add the architecture spacific path to the library files PublicAdditionalLibraries.Add(Path.Combine(MyLibraryPath, "lib", ArchitecturePath, "MyLibrary.lib")); // Add a more generic path to the include header files PublicIncludePaths.Add(Path.Combine(MyLibraryPath, "include")); }   // Defination lets us know whether we successfully found our library! Definitions.Add(string.Format("WITH_MY_LIBRARY_PATH_USE={0}", isLibrarySupported ? 1 : 0));   return isLibrarySupported; } } }

# IPlugin Header File

/Plugins/PsudoInputs/Source/PsudoInputs/Public/PsudoInputsPlugin.h

#pragma once   #include "ModuleManager.h" #include "IInputDeviceModule.h"   #include "InputCoreTypes.h"   /** * The public interface to this module. In most cases, this interface is only public to sibling modules * within this plugin. */ class IPsudoInputsPlugin : public IInputDeviceModule { public: /** * Singleton-like access to this module's interface. This is just for convenience! * Beware of calling this during the shutdown phase, though. Your module might have been unloaded already. * * @return Returns singleton instance, loading the module on demand if needed */ static inline IPsudoInputsPlugin& Get() { return FModuleManager::LoadModuleChecked< IPsudoInputsPlugin >("PsudoInputs"); }   /** * Checks to see if this module is loaded and ready. It is only valid to call Get() if IsAvailable() returns true. * * @return True if the module is loaded and ready to use */ static inline bool IsAvailable() { return FModuleManager::Get().IsModuleLoaded( "PsudoInputs" ); }   // This is where I declare my fancy new output type // - It's pretending to be a set of scales like a poor mans Wii Balance Board) static const FKey Psudo_WeighingScales; };

# PCH File

/Plugins/PsudoInputs/Source/PsudoInputs/Private/PsudoInputsPrivatePCH

// You should place include statements to your module's private header files here. You only need to // add includes for headers that are used in most of your module's source files though.   #include "Core.h" #include "CoreUObject.h"   #include "IPsudoInputsPlugin.h"

# Plugin Header File

/Plugins/PsudoInputs/Source/PsudoInputs/Private/PsudoInputsPlugin.h

#pragma once #include "PsudoInputsPrivatePCH.h"   class FPsudoInputsPlugin : public IPsudoInputsPlugin { public: /** IPsudoInputsInterface implementation */ virtual TSharedPtr< class IInputDevice > CreateInputDevice(const TSharedRef< FGenericApplicationMessageHandler >& InMessageHandler);   // This is not required as IInputDeviceModule handels startup! //virtual void StartupModule() OVERRIDE; virtual void ShutdownModule() OVERRIDE;   TSharedPtr< class FPsudoInputsInputDevice > PsudoInputDevice; };

# Plugin Cpp File

/Plugins/PsudoInputs/Source/PsudoInputs/Private/PsudoInputsPlugin.c

#include "PsudoInputsPrivatePCH.h"   #include "Internationalization.h" // LOCTEXT #include "InputCoreTypes.h"   #include "PsudoInputsPlugin.h"   #include "Engine.h" // Are these both necessary? #include "EngineUserInterfaceClasses.h" // Are these both necessary?   #include "IPsudoInputsPlugin.h"   #include "PsudoInputs.generated.inl"   IMPLEMENT_MODULE(FPsudoInputsPlugin, PsudoInputs) DEFINE_LOG_CATEGORY_STATIC(PsudoInputsPlugin, Log, All);   #define LOCTEXT_NAMESPACE "InputKeys"   const FKey IPsudoInputsPlugin::Psudo_WeighingScales("Psudo_WeighingScales");   // This function is called by *Application.cpp after startup to instantiate the modules InputDevice TSharedPtr< class IInputDevice > FPsudoInputsPlugin::CreateInputDevice(const TSharedRef< FGenericApplicationMessageHandler >& InMessageHandler) { UE_LOG(PsudoInputsPlugin, Log, TEXT("Create Input Device"));   // EKey: use these sparingly... only when you need a button/axis/motion that doesn't fit any of the input names that already exist // - I'm defineing my poor mans Wii Balance Board, it will be called "Psudo Player Weight" in the editor EKeys::AddKey(FKeyDetails(IPsudoInputsPlugin::Psudo_WeighingScales, LOCTEXT("Psudo_WeighingScales", "Psudo Player Weight"), FKeyDetails::Axis)); const_cast<UInputSettings*>(GetDefault<UInputSettings>())->AddAxisMapping(FInputAxisKeyMapping("PsudoWeighingScales", IPsudoInputsPlugin::Psudo_WeighingScales, 1.0F));   FPsudoInputsPlugin::PsudoInputDevice = MakeShareable(new FPsudoInputsInputDevice(InMessageHandler));   // We return the IInputDevice so that the Application has a handel on it. // - The application will ask for controller updates via 'SendControllerEvents()' // - The application will update the MessageHandler if it changes via 'SetMessageHandler(...)' return FPsudoInputsPlugin::PsudoInputDevice; }   #undef LOCTEXT_NAMESPACE   // This function may be called during shutdown to clean up the module. void FPsudoInputsPlugin::ShutdownModule() { FPsudoInputsPlugin::PsudoInputDevice->~FPsudoInputsInputDevice();   UE_LOG(PsudoInputsPlugin, Log, TEXT("Shutdown Module")); }

# Input Device Header File

/Plugins/PsudoInputs/Source/PsudoInputs/Private/PsudoInputsInputDevice.h

#pragma once //#include "PsudoInputsPluginPrivatePCH.h"   #include "IInputDevice.h"   #define MAX_NUM_PSUDO_INPUT_CONTROLLERS 4 // We dont realy have any input controllers, this is a sham! 😛 #define NUM_PSUDO_INPUT_BUTTONS 4 // I've only used the one button but w/evs   /** * Type definition for shared pointers to instances of FMessageEndpoint. */ // ToDo: Is this necessary? typedef TSharedPtr<class FPsudoInputsInputDevice> FPsudoInputsInputDevicePtr;   /** * Type definition for shared references to instances of FMessageEndpoint. */ // ToDo: Is this necessary? typedef TSharedRef<class FPsudoInputsInputDevice> FPsudoInputsInputDeviceRef;   /** * Interface class for my psudo device */ class FPsudoInputsInputDevice : public IInputDevice { public: FPsudoInputsInputDevice(const TSharedRef< FGenericApplicationMessageHandler >& MessageHandler);   // 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;   virtual ~FPsudoInputsInputDevice(); private: // ToDo: Is this necessary? bool Active;   /** Delay before sending a repeat message after a button was first pressed */ float InitialButtonRepeatDelay; // How long a button is held for before you send a 2nd event   /** Delay before sending a repeat message after a button has been pressed for a while */ float ButtonRepeatDelay; // How long a button is held for before you send a 3rd/4th/e.t.c event   EControllerButtons::Type PsudoInputButtonMapping[NUM_PSUDO_INPUT_BUTTONS]; double NextRepeatTime[NUM_PSUDO_INPUT_BUTTONS];   TSharedRef< FGenericApplicationMessageHandler > MessageHandler; };

# Input Device Cpp File

/Plugins/PsudoInputs/Source/PsudoInputs/Private/PsudoInputsInputDevice.cpp

#include "PsudoInputsPrivatePCH.h"   #include "GenericPlatformMath.h"   #include "PsudoInputsInputDevice.h"   #include "Slate.h"   #include "WindowsApplication.h" #include "WindowsWindow.h" #include "WindowsCursor.h" #include "GenericApplicationMessageHandler.h" #include "IInputDeviceModule.h" #include "IInputDevice.h"   DEFINE_LOG_CATEGORY_STATIC(LogPsudoInputDevice, Log, All);   byte pollState[MAX_NUM_PSUDO_INPUT_CONTROLLERS];   FPsudoInputsInputDevice::FPsudoInputsInputDevice(const TSharedRef< FGenericApplicationMessageHandler >& InMessageHandler) : Active(true), MessageHandler(InMessageHandler) { UE_LOG(LogPsudoInputDevice, Log, TEXT("Starting PsudoInputsInputDevice"));   InitialButtonRepeatDelay = 0.2f; ButtonRepeatDelay = 0.1f;   PsudoInputButtonMapping[0] = EControllerButtons::FaceButtonTop; // PSUDO_BUTTON_ONE NextRepeatTime[0] = -1.0; // Set to !pressed PsudoInputButtonMapping[1] = EControllerButtons::FaceButtonBottom; // PSUDO_BUTTON_TWO NextRepeatTime[1] = -1.0; // Set to !pressed PsudoInputButtonMapping[2] = EControllerButtons::FaceButtonLeft; // PSUDO_BUTTON_THREE NextRepeatTime[2] = -1.0; // Set to !pressed PsudoInputButtonMapping[3] = EControllerButtons::FaceButtonRight; // PSUDO_BUTTON_FOUR NextRepeatTime[3] = -1.0; // Set to !pressed }   // This method runs once every game tick, use it if you need a regular polling event void FPsudoInputsInputDevice::Tick(float DeltaTime){ }   // This method runs every time the game wants to check for controller updates! void FPsudoInputsInputDevice::SendControllerEvents() { // Commented this out as it will spam the log! //UE_LOG(LogPsudoInputDevice, Log, TEXT("Sending Controller Events"));   // Here is where we check the state of our input device proberbly by calling a method in your third party library... // - I dont have a real device (xbox controller, wiimote, e.t.c.) in this tutorial 😢 so im gona fake it!!! const double CurrentTime = FPlatformTime::Seconds();   // I could make library to read from a fancy set of matrix serebellum jacks and iterate over each of those 'controllers'.. but ill save that for the next tutorial int controllerIndex = 0; // Apparantly I was lazy so there is only one controller!   // This is how you fire regular boring controller events... the ones like the green xbox 'A' key (EControllerButtons::FaceButtonBottom)   int jumpButtonIndex = 0;   // This will make the third person tutorial man jump for player one!   // IF BUTTON IS CURRENTLY PRESSED DOWN BY USER // - If button pressed has not fired since the user pressed the button if (NextRepeatTime[jumpButtonIndex] == -1.0) { MessageHandler->OnControllerButtonPressed(PsudoInputButtonMapping[jumpButtonIndex], controllerIndex, false);   NextRepeatTime[jumpButtonIndex] = CurrentTime + InitialButtonRepeatDelay; } // - Else If (button pressed has fired > 0 times) && (time since last event > repeat event time) else if (NextRepeatTime[jumpButtonIndex] <= CurrentTime) { MessageHandler->OnControllerButtonPressed(PsudoInputButtonMapping[jumpButtonIndex], controllerIndex, true);   NextRepeatTime[jumpButtonIndex] = CurrentTime + ButtonRepeatDelay; } // ELSE (BUTTON IS CURRENTLY NOT PRESSED BY USER) if (NextRepeatTime[jumpButtonIndex] != -1.0) { // You would normally test whether the controll is pressed or released, on release you call: (see XInputInterface.cpp for a nice example) //MessageHandler->OnControllerButtonReleased(PsudoInputButtonMapping[jumpButtonIndex], controllerIndex, false); //NextRepeatTime[jumpButtonIndex] = -1.0; }     InitialButtonRepeatDelay = 0.2f; ButtonRepeatDelay = 0.1f;   // This is how you fire your fancypantz new controller events... the ones you added because you couldn't find an existing EControllerButton that matched your needs! FSlateApplication::Get().OnControllerAnalog(IPsudoInputsPlugin::Psudo_WeighingScales, controllerIndex, 75); // This will spam 75(kg) to my fancy new output type!   }   // This method is called every time someone changes the message handler (other hacky plugins might change it to customise it.. we haven't cos we're cool like that) void FPsudoInputsInputDevice::SetMessageHandler(const TSharedRef< FGenericApplicationMessageHandler >& InMessageHandler) { UE_LOG(LogPsudoInputDevice, Log, TEXT("Setting Message Handler")); MessageHandler = InMessageHandler; }   // Exec handler to allow console commands to be passed through for debugging bool FPsudoInputsInputDevice::Exec(UWorld* InWorld, const TCHAR* Cmd, FOutputDevice& Ar){ UE_LOG(LogPsudoInputDevice, Log, TEXT("Execute Console Command: %s"), Cmd);   // Put your fancy custom console command code here... I could have used this to let you fire psudo controller events but im lazy..   return true; }   // IForceFeedbackSystem pass through functions // - I *believe* this is a handel for the game to communicate back to your third party library (i.e. game tells joystick to increase force feedback/vibrate/turn on/off a light) void FPsudoInputsInputDevice::SetChannelValue(int32 ControllerId, FForceFeedbackChannelType ChannelType, float Value){ UE_LOG(LogPsudoInputDevice, Log, TEXT("Set Force Feedback %f"), Value); } void FPsudoInputsInputDevice::SetChannelValues(int32 ControllerId, const FForceFeedbackValues &values){ UE_LOG(LogPsudoInputDevice, Log, TEXT("Set Force Feedback Values")); }   // This is where you nicely clean up your plugin when its told to shut down! // - USE THIS PLEASE!!! no one likes a memory leak >_< FPsudoInputsInputDevice::~FPsudoInputsInputDevice() { UE_LOG(LogPsudoInputDevice, Log, TEXT("Shutdown Complete")); }

Retrieved from "https://wiki.unrealengine.com/index.php?title=Creating_Input_Devices&oldid=6624"