Recently I was playing Binding Of Isaac with Xbox one controller and this game is basically random.org with better graphics. At the start of the game you will reset a lot to get good starting item otherwise to complete game will require a lot of time. When playing with controller game doesn’t allow to map one of the keys for reset, but it is still possible to reset by pressing key on keyboard. So I decided to create small application that will allow to reset from controller, just press and hold map button for one or more second and game will reset. All it does is send ‘F’ key to active application, this is the key I have mapped for reset in game settings.

 

Getting gamepad input

To get gamepad input we can use SharpDX  just install SharpDx.DirectInput NuGet package:

Install-Package SharpDX.DirectInput

 

Connecting to joystick:

// Initialize DirectInput
var directInput = new DirectInput();

// Find a Joystick Guid
var joystickGuid = Guid.Empty;

foreach (var deviceInstance in directInput.GetDevices(DeviceType.Gamepad,
            DeviceEnumerationFlags.AllDevices))
    joystickGuid = deviceInstance.InstanceGuid;

// If Gamepad not found, look for a Joystick
if (joystickGuid == Guid.Empty)
    foreach (var deviceInstance in directInput.GetDevices(DeviceType.Joystick,
            DeviceEnumerationFlags.AllDevices))
        joystickGuid = deviceInstance.InstanceGuid;

// If Joystick not found, throws an error
if (joystickGuid == Guid.Empty)
{
    Debug.WriteLine("No Gamepad found.");
    Environment.Exit(1);
}

// Instantiate the joystick
var joystick = new Joystick(directInput, joystickGuid);

Debug.WriteLine("Found Gamepad with GUID: {0}", joystickGuid);

// Set BufferSize in order to use buffered data.
joystick.Properties.BufferSize = 128;

// Acquire the joystick
joystick.Acquire();

 

Getting input:

while (true)
{
    joystick.Poll();
    var inputs = joystick.GetBufferedData();
}

 

Detecting gamepad button hold

Just by looping chunks of input is hard to detect when button was pressed and for how long, better approach to use Reactive Extensions.

When button is pressed and released it creates two inputs one for DOWN and one for UP, all we need to do is buffer it and filter button presses that were longer than one second.

 

// Poll events from joystick every 1 millisecond
           var timer = Observable.Interval(TimeSpan.FromMilliseconds(1));

           timer
// Get all joystick input
.SelectMany(_ => { joystick.Poll(); return joystick.GetBufferedData(); })
// Filter only menu key button (xbox one controller)
.Where(a => a.Offset == JoystickOffset.Buttons6)
// Input will contain UP and Down button events
.Buffer(2)
// If button was pressed longer than a second
.Where(t => (TimeSpan.FromMilliseconds(t.Last().Timestamp) - TimeSpan.FromMilliseconds(t.First().Timestamp)).TotalSeconds >= 1)
// Press and hold F key for 1.5s
.Subscribe(t =>
{
    SendKeyDown(KeyCode.KEY_F);
    System.Threading.Thread.Sleep(1500);
    SendKeyUp(KeyCode.KEY_F);
});

 

Sending keyboard keys to active application

 

[DllImport("user32.dll", SetLastError = true)]
private static extern uint SendInput(uint numberOfInputs, INPUT[] inputs, int sizeOfInputStructure);

private static void SendKeyDown(KeyCode keyCode)
{
    var input = new KEYBDINPUT
    {
        Vk = (ushort)keyCode
    };
  
    SendKeyboardInput(input);
}

private static void SendKeyUp(KeyCode keyCode)
{
    var input = new KEYBDINPUT
    {
        Vk = (ushort)keyCode,
        Flags = 2
    };
    SendKeyboardInput(input);
}

private static void SendKeyboardInput(KEYBDINPUT keybInput)
{
    INPUT input = new INPUT
    {
        Type = 1
    };
    input.Data.Keyboard = keybInput;

    if (SendInput(1, new[] { input }, Marshal.SizeOf(typeof(INPUT))) == 0)
    {
        throw new Exception();
    }
}

 

 

Source code

Comments


Comments are closed