Custom Input Devices
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/Game...
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.
The following is based on a private plugin (Integrating Nintendo Wii Controllers/Sensors into UE4), and an Oculus plugin shipped with the engine (Engine/Plugins/Runtime/OculusInput).
The actor is a useful way of referencing the InputDevice to run arbitrary methods. For some reason if the two actor classes are deleted this all fails to compile (Possibly related to *.generated.h files). If someone figures out why I would love to know (mspe044).
Example Code
The code will show how to create a plugin for an input device named PseudoController which will simulate controller#1 pressing and holding the bottom face button (jump) and moving the right analog stick wildly. It will also create a new custom Gamepad Input called "Pseudo 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 FPseudoControllerInputDevice::SendControllerEvents() you can then pass on any events/polled controller states using the MessageHandler in a generic way.
uPlugin Ddefinition
/Plugins/PseudoController/PseudoController.uplugin
{
"FileVersion" : 0,
"FriendlyName" : "Pseudo Controller Plugin",
"Version" : 0,
"VersionName" : "0.2",
"CreatedBy" : "mspe044@gmail.com",
"EngineVersion" : 1579795,
"Description" : "I wish I was a real input! :'(",
"Category" : "Tutorial",
"Modules" :
[
{
"Name" : "PseudoController",
"Type" : "Runtime",
"LoadingPhase" : "PreDefault",
"WhitelistPlatforms" : [ "Win64", "Win32" ]
}
]
}
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/PseudoController/Source/PseudoController/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/PseudoController/Source/PseudoController/PseudoController.Build.cs
namespace UnrealBuildTool.Rules
{
using System.IO; // ToDo: Replace with standard mechenism
public class PseudoController : ModuleRules
{
public PseudoController(TargetInfo Target)
{
PCHUsage = PCHUsageMode.NoSharedPCHs;
// ... add public include paths required here ...
PublicIncludePaths.AddRange( new string[] {
"PseudoController/Public",
"PseudoController/Classes",
});
// ... add other private include paths required here ...
PrivateIncludePaths.AddRange( new string[] {
"PseudoController/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 ../PseudoController/
string ModulePath = Path.GetDirectoryName(RulesCompiler.GetModuleFilename(this.GetType().Name));
// This will give you a relative path to ../PseudoController/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 ../PseudoController/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 ../PseudoController/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/PseudoController/Source/PseudoController/Public/IPseudoControllerPlugin.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 IPseudoControllerPlugin : 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 IPseudoControllerPlugin& Get() {
return FModuleManager::LoadModuleChecked< IPseudoControllerPlugin >("PseudoController");
}
/**
* 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( "PseudoController" );
}
};
PCH File
/Plugins/PseudoController/Source/PseudoController/Private/PseudoControllerPrivatePCH
// 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 "IPseudoControllerPlugin.h"
Plugin Header File
/Plugins/PseudoController/Source/PseudoController/Private/PseudoControllerPlugin.h
#pragma once
#include "PseudoControllerPrivatePCH.h"
class FPseudoControllerPlugin : public IPseudoControllerPlugin
{
public:
/** IPseudoControllerInterface implementation */
virtual TSharedPtr< class IInputDevice > CreateInputDevice(const TSharedRef< FGenericApplicationMessageHandler >& InMessageHandler);
//virtual void StartupModule() OVERRIDE; // This is not required as IInputDeviceModule handels it!
virtual void ShutdownModule() OVERRIDE;
TSharedPtr< class FPseudoControllerInputDevice > PseudoInputDevice;
};
Plugin Cpp File
/Plugins/PseudoController/Source/PseudoController/Private/PseudoControllerPlugin.cpp
#include "PseudoControllerPrivatePCH.h"
#include "Internationalization.h" // LOCTEXT
#include "InputCoreTypes.h"
#include "PseudoControllerPlugin.h"
#include "Engine.h" // Are these both necessary?
#include "EngineUserInterfaceClasses.h" // Are these both necessary?
#include "IPseudoControllerPlugin.h"
#include "PseudoController.generated.inl"
IMPLEMENT_MODULE(FPseudoControllerPlugin, PseudoController)
DEFINE_LOG_CATEGORY_STATIC(PseudoControllerPlugin, Log, All);
#define LOCTEXT_NAMESPACE "InputKeys"
// This function is called by *Application.cpp after startup to instantiate the modules InputDevice
TSharedPtr< class IInputDevice > FPseudoControllerPlugin::CreateInputDevice(const TSharedRef< FGenericApplicationMessageHandler >& InMessageHandler)
{
UE_LOG(PseudoControllerPlugin, Log, TEXT("Create Input Device"));
FPseudoControllerPlugin::PseudoInput = MakeShareable(new FPseudoControllerInputDevice(InMessageHandler));
return PseudoInput;
}
#undef LOCTEXT_NAMESPACE
// This function may be called during shutdown to clean up the module.
void FPseudoControllerPlugin::ShutdownModule()
{
FPseudoControllerPlugin::PseudoInput->~FPseudoControllerInputDevice();
UE_LOG(PseudoControllerPlugin, Log, TEXT("Shutdown Module"));
}
Input Device Header File
/Plugins/PseudoController/Source/PseudoController/Private/PseudoControllerInputDevice.h
#pragma once
#include "PseudoControllersInputState.h"
#include "IInputDevice.h"
#define MAX_NUM_PSEUDO_INPUT_CONTROLLERS 4 // We dont realy have any input controllers, this is a sham! :P
#define NUM_PSEUDO_INPUT_BUTTONS 6 // 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 FPseudoControllerInputDevicePtr;
/**
* Type definition for shared references to instances of FMessageEndpoint.
*/
// ToDo: Is this necessary?
typedef TSharedRef FPseudoControllerInputDeviceRef;
/**
* Interface class for WiiInput devices (wii devices)
*/
class FPseudoControllerInputDevice : public IInputDevice
{
public:
FPseudoControllerInputDevice(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 ~FPseudoControllerInputDevice();
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 PseudoInputButtonMapping[NUM_PSEUDO_INPUT_BUTTONS];
/** Last frame's button states, so we only send events on edges */
bool PreviousButtonStates[NUM_PSEUDO_INPUT_BUTTONS];
/** Next time a repeat event should be generated for each button */
double NextRepeatTime[NUM_PSEUDO_INPUT_BUTTONS];
TSharedRef< FGenericApplicationMessageHandler > MessageHandler;
FPseudoControllerSensorState PseudoControllerStates[MAX_NUM_PSEUDO_INPUT_CONTROLLERS];
};
Input Device Cpp File
/Plugins/PseudoController/Source/PseudoController/Private/PseudoControllerInputDevice.cpp
#include "PseudoControllerPrivatePCH.h"
#include "GenericPlatformMath.h"
#include "PseudoControllerInputDevice.h"
#include "SlateBasics.h"
#include "WindowsApplication.h"
#include "WindowsWindow.h"
#include "WindowsCursor.h"
#include "GenericApplicationMessageHandler.h"
#include "IInputDeviceModule.h"
#include "IInputDevice.h"
DEFINE_LOG_CATEGORY_STATIC(LogPseudoControllerDevice, Log, All);
const FKey FPseudoControllerKey::Pseudo_WeighingSensor1("Pseudo_WeighingSensor1");
const FPseudoControllerKeyNames::Type FPseudoControllerKeyNames::Pseudo_WeighingSensor1("Pseudo_WeighingSensor1");
FPseudoControllerInputDevice::FPseudoControllerInputDevice(const TSharedRef< FGenericApplicationMessageHandler >& InMessageHandler) : Active(true), MessageHandler(InMessageHandler) {
UE_LOG(LogPseudoControllerDevice, Log, TEXT("Starting PseudoControllerInputDevice"));
// Initialize button repeat delays
InitialButtonRepeatDelay = 0.2f;
ButtonRepeatDelay = 0.1f;
// Register the FKeys (Gamepad key for controllers, Mouse for mice, FloatAxis for non binary values e.t.c.)
EKeys::AddKey(FKeyDetails(FPseudoControllerKey::Pseudo_WeighingSensor1, LOCTEXT("Pseudo_WeighingSensor1", "Pseudo Weighing Sensor #1"), FKeyDetails::GamepadKey | FKeyDetails::FloatAxis));
// Setting all buttons on my pseudo controllers to 'not pressed'
// (You should check the inital state of your controllers)
PreviousButtonStates[NUM_PSEUDO_INPUT_BUTTONS] = { 0 };
//NextRepeatTime[NUM_PSEUDO_INPUT_BUTTONS] = { 0.0 };
// Initialize mapping of controller button mask to unreal button mask
// - mapping 0-3 to the 'FaceButtons'
// --- 'X','Square','Circle','Triangle' for playstation
// --- 'A','X','B','Y' on xbox
PseudoInputButtonMapping[0] = EControllerButtons::FaceButtonTop; // PSEUDO_BUTTON_ZERO
PseudoInputButtonMapping[1] = EControllerButtons::FaceButtonBottom; // PSEUDO_BUTTON_ONE
PseudoInputButtonMapping[2] = EControllerButtons::FaceButtonLeft; // PSEUDO_BUTTON_TWO
PseudoInputButtonMapping[3] = EControllerButtons::FaceButtonRight; // PSEUDO_BUTTON_THREE
// - mapping 4-5 to the left joystick (traditionally used for movement controls)
PseudoInputButtonMapping[4] = EControllerButtons::LeftAnalogX; // PSEUDO_BUTTON_FOUR
PseudoInputButtonMapping[5] = EControllerButtons::LeftAnalogY; // PSEUDO_BUTTON_FIVE
}
void FPseudoControllerInputDevice::Tick(float DeltaTime) {
// This will spam the log heavily, comment it out for real plugins :)
UE_LOG(LogPseudoControllerDevice, Log, TEXT("Tick %f"), DeltaTime);
}
void FPseudoControllerInputDevice::SendControllerEvents() {
// Here is where we check the state of our input device proberbly by calling a method in your third party libary...
// - I dont have a real device (xbox controller, wiimote, e.t.c.) in this tutorial :'( so we will just pretend!!!
// I could make libary to read from a fancy set of 'matrix' cerebellum 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 firing events today!
const double CurrentTime = FPlatformTime::Seconds();
const float CurrentTimeFloat = FPlatformTime::ToSeconds(FPlatformTime::Cycles()); // Works with FMath functions
// This weard statement simulates user holding down the button 1/3 of the time
if (FMath::Fmod(CurrentTimeFloat, 1.5f) < .5f) {
// If the button was pressed this tick
if (!PreviousButtonStates[1]) {
// Fire button pressed event
MessageHandler->OnControllerButtonPressed(PseudoInputButtonMapping[1], controllerIndex, false);
// this button was pressed - set the button's NextRepeatTime to the InitialButtonRepeatDelay
NextRepeatTime[1] = CurrentTime + InitialButtonRepeatDelay;
PreviousButtonStates[1] = true;
// If the buttons has been held long enough to fire a nth event
} else if (NextRepeatTime[1] OnControllerButtonPressed(PseudoInputButtonMapping[1], controllerIndex, true);
// set the button's NextRepeatTime to the ButtonRepeatDelay
NextRepeatTime[1] = CurrentTime + ButtonRepeatDelay;
}
// If the button was released this tick
}
else if (PreviousButtonStates[1]) {
UE_LOG(LogPseudoControllerDevice, Log, TEXT("Release"));
// Fire button released event
MessageHandler->OnControllerButtonReleased(PseudoInputButtonMapping[1], controllerIndex, false);
PreviousButtonStates[1] = false;
}
// This simulates the user moving the stick left and right repeatedly
float xMove = FMath::Sin(CurrentTimeFloat * .223f * 2 * PI);
// Fire analog input event
MessageHandler->OnControllerAnalog(PseudoInputButtonMapping[4], controllerIndex, xMove);
// This simulates the user moving the stick up and down repeatedly
float yMove = FMath::Cos(CurrentTimeFloat * .278f * 2 * PI);
// Fire analog input event
MessageHandler->OnControllerAnalog(PseudoInputButtonMapping[5], controllerIndex, yMove);
// This will spam the log heavily, comment it out for real plugins :)
UE_LOG(LogPseudoControllerDevice, Log, TEXT("Sending Controller Events jump%d, x%f, y%f"), PreviousButtonStates[1], xMove, yMove);
// 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!
// We 'read' the value 75 from the weight sensor
const unsigned short int sensorValue = 75;
FPseudoControllerSensorState Sensor1 = PseudoControllerStates[controllerIndex].WeightAxes[(int32)EPseudoControllerAxes::WeightSensor1];
if(sensorValue != Sensor1.State)
{
Sensor1.State = sensorValue;
FSlateApplication::Get().OnControllerAnalog(Sensor1.Axis, controllerIndex, Sensor1.GetValue()); // This will spam the calculated weight to my fancy new output type!
}
}
void FPseudoControllerInputDevice::SetMessageHandler(const TSharedRef< FGenericApplicationMessageHandler >& InMessageHandler) {
UE_LOG(LogPseudoControllerDevice, Log, TEXT("Set Message Handler"));
MessageHandler = InMessageHandler;
}
bool FPseudoControllerInputDevice::Exec(UWorld* InWorld, const TCHAR* Cmd, FOutputDevice& Ar) {
UE_LOG(LogPseudoControllerDevice, Log, TEXT("Execute Console Command: %s"), Cmd);
// Put your fancy custom console command code here...
// ToDo: use this to let you fire pseudo controller events
return true;
}
// IForceFeedbackSystem pass through functions
// - I *believe* this is a handel for the game to communicate back to your third party libary (i.e. game tells joystick to increase force feedback/vibrate/turn on/off a light)
void FPseudoControllerInputDevice::SetChannelValue(int32 ControllerId, FForceFeedbackChannelType ChannelType, float Value) {
UE_LOG(LogPseudoControllerDevice, Log, TEXT("Set Force Feedback %f"), Value);
}
void FPseudoControllerInputDevice::SetChannelValues(int32 ControllerId, const FForceFeedbackValues &values) {
// This will spam the log heavily, comment it out for real plugins :)
UE_LOG(LogPseudoControllerDevice, 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 memory leaks >_< FPseudoControllerInputDevice::~FPseudoControllerInputDevice() {
UE_LOG(LogPseudoControllerDevice, Log, TEXT("Closing PseudoControllerInputDevice"));
}
Input State Header File
This is where you define custom struts to rember FKeys / FNames / Values for custom input types (Pseudo_WeighingSensor1) /Plugins/PseudoController/Source/PseudoController/Private/PseudoControllerInputState.h
#pragma once
#include "InputCoreTypes.h"
/**
* Digital weight sensors on the Wii Balance Board
*/
enum class EPseudoControllerAxes
{
WeightSensor1 = 0,
/** Total number of weight axes */
TotalAxisCount = 1
};
struct FPseudoControllerKey
{
static const FKey Pseudo_WeighingSensor1;
};
struct FPseudoControllerKeyNames
{
typedef FName Type;
static const FName Pseudo_WeighingSensor1;
};
/**
* Capacitive Axis State
*/
struct FPseudoControllerSensorState
{
/** The axis that this sensor state maps to */
FName Axis;
/** The zero value (no additional weight) */
unsigned short int Zero;
/** What is the current sensor reading, from 0.f to 1.f */
float Scale;
/** What is the current sensor reading, from 0.f to 1.f */
unsigned short int State;
FPseudoControllerSensorState()
: Axis(NAME_None)
, Zero(900)
, Scale(100.f)
, State(0)
{
}
float GetValue() {
if(State < Zero) {
return 0.f;
}
return (float)(State - Zero) * Scale;
}
};
/**
* Input state for an pseudo controller
*/
struct FPseudoControllerState
{
/** Weight axes */
FPseudoControllerSensorState WeightAxes[(int32)EWiiBalanceAxes::TotalAxisCount];
/** Default constructor */
FPseudoControllerState()
{
WeightAxes[(int32)EPseudoControllerAxes::WeightSensor1].Axis = FPseudoControllerKeyNames::Pseudo_WeighingSensor1;
}
};
Input Actor Cpp File
/Plugins/PseudoController/Source/PseudoController/Classes/PseudoActor.cpp
// Copyright 1998-2014 Epic Games, Inc. All Rights Reserved.
#include "PseudoControllerPrivatePCH.h"
#include "Engine.h"
#include "PseudoActorObject.h"
#include "PseudoControllerPlugin.h"
#include "PseudoControllerInputDevice.h"
DEFINE_LOG_CATEGORY_STATIC(LogPseudoActor, Log, All);
// Copyright 1998-2014 Epic Games, Inc. All Rights Reserved.
APseudoActorObject::APseudoActorObject(const class FObjectInitializer& PCIP) : Super(PCIP) {
UE_LOG(LogPseudoActor, Log, TEXT("Construct"));
}
void APseudoActorObject::FunkyMethod() {
UE_LOG(LogPseudoActor, Log, TEXT("FunkyMethod()"));
}
Input Actor Header File
/Plugins/PseudoController/Source/PseudoController/Classes/PseudoActor.h
#pragma once
#include "GameFramework/Actor.h"
#include "PseudoActorObject.generated.h"
UCLASS(MinimalAPI, hidecategories = (Input))
class APseudoActorObject : public AActor
{
GENERATED_UCLASS_BODY()
UFUNCTION(BlueprintCallable, Category = "Development")
virtual void FunkyMethod();
};
()