Skip to content

JoeLumbley/XInput-CS

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

XInput C# 🎮

Welcome to XInput C#, your go-to solution for integrating Xbox controller support into your applications! This feature-rich application showcases the seamless integration of controllers, complete with vibration effects and real-time controller state monitoring.

001

With a clean and well-commented codebase, this project serves as an invaluable resource for developers looking to harness the power of XInput in their Windows applications. Whether you're a seasoned developer or just getting started, the XInput app provides a solid foundation for building immersive gaming experiences and beyond.


Code Walkthrough

Using Directives

using System.Diagnostics;
using System.Runtime.InteropServices;
  • using System.Diagnostics;: This directive allows us to use classes for debugging, such as Debug.Print, which helps in logging messages during development.

  • using System.Runtime.InteropServices;: This directive is crucial for working with unmanaged code and allows the use of attributes like DllImport, which is essential for calling functions from external libraries like the XInput DLL.

Index


Namespace Declaration

namespace XInput_CS
{
  • namespace XInput_CS: This defines a namespace called XInput_CS. Namespaces are used to organize code and avoid naming conflicts.

Index


Struct Declaration

public struct XboxControllers
{
  • public struct XboxControllers: This declares a public structure named XboxControllers. Structures are used to group related variables together. In this case, it represents the state and functionality of Xbox controllers.

Index


Importing XInput Function

[DllImport("XInput1_4.dll")]
private static extern int XInputGetState(int dwUserIndex, 
                                         ref XINPUT_STATE pState);
  • [DllImport("XInput1_4.dll")]: This attribute indicates that we are importing a function from the XInput1_4.dll library, which is used for Xbox controller interaction.

  • private static extern int XInputGetState(...): This defines the external function XInputGetState, which retrieves the state of a specified Xbox controller. It takes the user index (controller number) and a reference to an XINPUT_STATE structure to fill with the controller's current state.

Index


XINPUT_STATE Structure

[StructLayout(LayoutKind.Explicit)]
public struct XINPUT_STATE
{
    [FieldOffset(0)]
    public uint dwPacketNumber; // Unsigned integer range 0 through 4,294,967,295.
    [FieldOffset(4)]
    public XINPUT_GAMEPAD Gamepad;
}
  • [StructLayout(LayoutKind.Explicit)]: This attribute allows precise control over the memory layout of the structure. Each field can be placed at a specific offset.

  • public uint dwPacketNumber;: This field holds the packet number, which helps track the state changes of the controller. It is an unsigned integer with a large range.

  • public XINPUT_GAMEPAD Gamepad;: This field contains an instance of the XINPUT_GAMEPAD structure, which holds detailed information about the gamepad's state.

Index


XINPUT_GAMEPAD Structure

[StructLayout(LayoutKind.Sequential)]
public struct XINPUT_GAMEPAD
{
    public ushort wButtons; // Unsigned integer range 0 through 65,535.
    public byte bLeftTrigger; // Unsigned integer range 0 through 255.
    public byte bRightTrigger;
    public short sThumbLX; // Signed integer range -32,768 through 32,767.
    public short sThumbLY;
    public short sThumbRX;
    public short sThumbRY;
}
  • [StructLayout(LayoutKind.Sequential)]: This attribute indicates that the fields of the structure are laid out in the order they are defined.

  • public ushort wButtons;: This field stores the state of the buttons as an unsigned short, where each button corresponds to a unique bit value.

  • public byte bLeftTrigger; and public byte bRightTrigger;: These fields represent the values of the left and right triggers, respectively, as unsigned bytes.

  • public short sThumbLX;, public short sThumbLY;, public short sThumbRX;, public short sThumbRY;: These fields represent the positions of the left and right thumbsticks on the X and Y axes, using signed short integers.

Index


State Variable

private XINPUT_STATE State;
  • private XINPUT_STATE State;: This variable holds the current state of the Xbox controller, which is filled by the XInputGetState function.

Index


Enum for Button Mapping

enum Button
{
    DPadUp = 1,
    DPadDown = 2,
    DPadLeft = 4,
    DPadRight = 8,
    Start = 16,
    Back = 32,
    LeftStick = 64,
    RightStick = 128,
    LeftBumper = 256,
    RightBumper = 512,
    A = 4096,
    B = 8192,
    X = 16384,
    Y = 32768,
}
  • enum Button: This enumeration defines constants for each button on the Xbox controller. Each button is assigned a unique bit value, making it easy to check the state using bitwise operations.

Bitwise Operations

Index


Neutral Zone Constants

private const short NeutralStart = -16384; // -16,384 = -32,768 / 2
private const short NeutralEnd = 16384; // 16,383.5 = 32,767 / 2
  • private const short NeutralStart and private const short NeutralEnd: These constants define the range for the thumbstick's neutral zone. The thumbstick must move beyond these points to register as active input, which helps prevent unintentional actions.

The Neutral Zone

Index


Trigger Threshold Constant

private const byte TriggerThreshold = 64; // 64 = 256 / 4
  • private const byte TriggerThreshold: This constant sets the minimum value for the triggers to be considered pressed. It ensures that small, unintentional movements do not register as inputs.

The Trigger Threshold

Index


Controller State Arrays

public bool[] Connected;
private DateTime ConnectionStart;
public ushort[] Buttons;
  • public bool[] Connected;: This array keeps track of whether up to four controllers are connected.

  • private DateTime ConnectionStart;: This variable records the time when the connection check starts.

  • public ushort[] Buttons;: This array stores the state of the controller buttons for each connected controller.

Index


Additional State Arrays

public bool[] LeftThumbstickXaxisNeutral;
public bool[] LeftThumbstickYaxisNeutral;
public bool[] RightThumbstickXaxisNeutral;
public bool[] RightThumbstickYaxisNeutral;
  • These arrays track whether the thumbsticks are in a neutral position. If the thumbstick is moved outside of the neutral zone, the corresponding array will be set to false.

Index


Initialization Method

public void Initialize()
{
    // Initialize the Connected array to indicate whether controllers are connected.
    Connected = new bool[4];

    // Record the current date and time when initialization starts.
    ConnectionStart = DateTime.Now;

    // Initialize the Buttons array to store the state of controller buttons.
    Buttons = new ushort[4];

    // Initialize arrays to check if thumbstick axes are in the neutral position.
    LeftThumbstickXaxisNeutral = new bool[4];
    LeftThumbstickYaxisNeutral = new bool[4];
    RightThumbstickXaxisNeutral = new bool[4];
    RightThumbstickYaxisNeutral = new bool[4];

    // Initialize array to check if the D-Pad is in the neutral position.
    DPadNeutral = new bool[4];

    // Initialize array to check if letter buttons are in the neutral position.
    LetterButtonsNeutral = new bool[4];

    // Set all thumbstick axes, triggers, D-Pad, letter buttons, start/back buttons,
    // bumpers, and stick buttons to neutral for all controllers (indices 0 to 3).
    for (int i = 0; i < 4; i++)
    {
        LeftThumbstickXaxisNeutral[i] = true;
        LeftThumbstickYaxisNeutral[i] = true;
        RightThumbstickXaxisNeutral[i] = true;
        RightThumbstickYaxisNeutral[i] = true;

        DPadNeutral[i] = true;

        LetterButtonsNeutral[i] = true;
    }

    // Initialize arrays for thumbstick directional states.
    RightThumbstickLeft = new bool[4];
    RightThumbstickRight = new bool[4];
    RightThumbstickDown = new bool[4];
    RightThumbstickUp = new bool[4];
    LeftThumbstickLeft = new bool[4];
    LeftThumbstickRight = new bool[4];
    LeftThumbstickDown = new bool[4];
    LeftThumbstickUp = new bool[4];

    // Initialize arrays for trigger states.
    LeftTrigger = new bool[4];
    RightTrigger = new bool[4];

    // Initialize arrays for letter button states (A, B, X, Y).
    A = new bool[4];
    B = new bool[4];
    X = new bool[4];
    Y = new bool[4];

    // Initialize arrays for bumper button states.
    LeftBumper = new bool[4];
    RightBumper = new bool[4];

    // Initialize arrays for D-Pad directional states.
    DPadUp = new bool[4];
    DPadDown = new bool[4];
    DPadLeft = new bool[4];
    DPadRight = new bool[4];

    // Initialize arrays for start and back button states.
    Start = new bool[4];
    Back = new bool[4];

    // Initialize arrays for stick button states.
    LeftStick = new bool[4];
    RightStick = new bool[4];

    TimeToVibe = 1000; // ms

    LeftVibrateStart = new DateTime[4];
    RightVibrateStart = new DateTime[4];

    for (int ControllerNumber = 0; ControllerNumber < 4; ControllerNumber++)
    {
        LeftVibrateStart[ControllerNumber] = DateTime.Now;
        RightVibrateStart[ControllerNumber] = DateTime.Now;
    }

    IsLeftVibrating = new bool[4];
    IsRightVibrating = new bool[4];

    // Call the TestInitialization method to verify the initial state of the controllers.
    TestInitialization();
}
  • public void Initialize(): This method initializes all the arrays and variables related to the controller states. It sets everything to a neutral position and records the start time for connection checks.

  • for (int i = 0; i < 4; i++): This loop initializes the neutral states for each controller.

  • TestInitialization();: This method is called at the end of the initialization to verify that all controllers are set up correctly.

Index


Update Method

public void Update()
{
    TimeSpan ElapsedTime = DateTime.Now - ConnectionStart;

    // Every second check for connected controllers.
    if (ElapsedTime.TotalSeconds >= 1)
    {
        for (int controllerNumber = 0; controllerNumber <= 3; controllerNumber++) // Up to 4 controllers
        {
            Connected[controllerNumber] = IsConnected(controllerNumber);
        }

        ConnectionStart = DateTime.Now;
    }

    for (int controllerNumber = 0; controllerNumber <= 3; controllerNumber++)
    {
        if (Connected[controllerNumber])
        {
            UpdateState(controllerNumber);
        }
    }

    UpdateVibrateTimers();
}
  • public void Update(): This method is called regularly to update the state of the controllers. It checks if the controllers are connected and updates their states accordingly.

  • if (ElapsedTime.TotalSeconds >= 1): This condition checks if at least one second has passed since the last connection check.

  • UpdateState(controllerNumber);: This method is called for each connected controller to update its state.

Index


Update State Method

private void UpdateState(int controllerNumber)
{
    try
    {
        XInputGetState(controllerNumber, ref State);
        UpdateButtons(controllerNumber);
        UpdateThumbsticks(controllerNumber);
        UpdateTriggers(controllerNumber);
    }
    catch (Exception ex)
    {
        Debug.Print($"Error getting XInput state: {controllerNumber} | {ex.Message}");
    }
}
  • private void UpdateState(int controllerNumber): This method retrieves the current state of the specified controller and updates its buttons, thumbsticks, and triggers.

  • XInputGetState(controllerNumber, ref State);: This line calls the imported function to get the current state of the controller.

  • catch (Exception ex): This block handles any exceptions that may occur while trying to get the controller state, logging the error message.

Index


Update Buttons Method

private void UpdateButtons(int controllerNumber)
{
    UpdateDPadButtons(controllerNumber);
    UpdateLetterButtons(controllerNumber);
    UpdateBumperButtons(controllerNumber);
    UpdateStickButtons(controllerNumber);
    UpdateStartBackButtons(controllerNumber);
    UpdateDPadNeutral(controllerNumber);
    UpdateLetterButtonsNeutral(controllerNumber);

    Buttons[controllerNumber] = State.Gamepad.wButtons;
}
  • private void UpdateButtons(int controllerNumber): This method updates the state of all buttons for the specified controller.

  • Each Update... method checks the state of a specific set of buttons, such as D-Pad buttons, letter buttons, and bumpers.

  • Buttons[controllerNumber] = State.Gamepad.wButtons;: This line stores the current button state in the Buttons array.

Index


Update Thumbsticks Method

private void UpdateThumbsticks(int controllerNumber)
{
    UpdateLeftThumbstick(controllerNumber);
    UpdateRightThumbstickPosition(controllerNumber);
}
  • private void UpdateThumbsticks(int controllerNumber): This method updates the state of the thumbsticks for the specified controller.

  • It calls methods to update both the left and right thumbsticks.

Index


Update Triggers Method

private void UpdateTriggers(int controllerNumber)
{
    UpdateLeftTriggerPosition(controllerNumber);
    UpdateRightTriggerPosition(controllerNumber);
}
  • private void UpdateTriggers(int controllerNumber): This method updates the state of the triggers for the specified controller.

  • It calls methods to check the positions of both the left and right triggers.

Index


Update D-Pad Buttons Method

private readonly void UpdateDPadButtons(int CID)
{
    DPadUp[CID] = (State.Gamepad.wButtons & (ushort)Button.DPadUp) != 0;
    DPadDown[CID] = (State.Gamepad.wButtons & (ushort)Button.DPadDown) != 0;
    DPadLeft[CID] = (State.Gamepad.wButtons & (ushort)Button.DPadLeft) != 0;
    DPadRight[CID] = (State.Gamepad.wButtons & (ushort)Button.DPadRight) != 0;
}
  • private readonly void UpdateDPadButtons(int CID): This method updates the state of the D-Pad buttons for the specified controller.

  • Each line uses a bitwise AND operation to check if a specific button is pressed, updating the corresponding boolean array.

Index


Update Letter Buttons Method

private readonly void UpdateLetterButtons(int CID)
{
    A[CID] = (State.Gamepad.wButtons & (ushort)Button.A) != 0;
    B[CID] = (State.Gamepad.wButtons & (ushort)Button.B) != 0;
    X[CID] = (State.Gamepad.wButtons & (ushort)Button.X) != 0;
    Y[CID] = (State.Gamepad.wButtons & (ushort)Button.Y) != 0;
}
  • private readonly void UpdateLetterButtons(int CID): Similar to the D-Pad buttons, this method checks the state of the letter buttons (A, B, X, Y) for the specified controller.

Index


Update Trigger Positions Methods

private void UpdateLeftTriggerPosition(int controllerNumber)
{
    if (State.Gamepad.bLeftTrigger > TriggerThreshold)
    {
        LeftTrigger[controllerNumber] = true;
    }
    else
    {
        LeftTrigger[controllerNumber] = false;
    }
}

private void UpdateRightTriggerPosition(int controllerNumber)
{
    if (State.Gamepad.bRightTrigger > TriggerThreshold)
    {
        RightTrigger[controllerNumber] = true;
    }
    else
    {
        RightTrigger[controllerNumber] = false;
    }
}
  • private void UpdateLeftTriggerPosition(int controllerNumber) and private void UpdateRightTriggerPosition(int controllerNumber): These methods check if the left or right trigger is pressed based on the defined threshold and update the corresponding boolean array.

Index


Update Thumbstick Methods

private void UpdateLeftThumbstick(int ControllerNumber)
{
    UpdateLeftThumbstickXaxis(ControllerNumber);
    UpdateLeftThumbstickYaxis(ControllerNumber);
}

private void UpdateRightThumbstickPosition(int controllerNumber)
{
    UpdateRightThumbstickXaxis(controllerNumber);
    UpdateRightThumbstickYaxis(controllerNumber);
}
  • private void UpdateLeftThumbstick(int ControllerNumber): This method updates both the X and Y axes of the left thumbstick.

  • private void UpdateRightThumbstickPosition(int controllerNumber): This method updates both the X and Y axes of the right thumbstick.

Index


Update Thumbstick Axis Methods

Update Left Thumbstick Y-Axis

private readonly void UpdateLeftThumbstickYaxis(int ControllerNumber)
{
    if (State.Gamepad.sThumbLY <= NeutralStart)
    {
        LeftThumbstickUp[ControllerNumber] = false;

        LeftThumbstickYaxisNeutral[ControllerNumber] = false;

        LeftThumbstickDown[ControllerNumber] = true;
    }
    else if (State.Gamepad.sThumbLY >= NeutralEnd)
    {
        LeftThumbstickDown[ControllerNumber] = false;

        LeftThumbstickYaxisNeutral[ControllerNumber] = false;

        LeftThumbstickUp[ControllerNumber] = true;
    }
    else
    {
        LeftThumbstickUp[ControllerNumber] = false;

        LeftThumbstickDown[ControllerNumber] = false;

        LeftThumbstickYaxisNeutral[ControllerNumber] = true;

    }

}
  • if (State.Gamepad.sThumbLY <= NeutralStart): This condition checks if the left thumbstick's Y-axis position is less than or equal to the NeutralStart value, indicating that the thumbstick is pushed down.

  • LeftThumbstickUp[ControllerNumber] = false;: If the thumbstick is down, we set the LeftThumbstickUp state to false.

  • LeftThumbstickYaxisNeutral[ControllerNumber] = false;: This indicates that the thumbstick is not in the neutral position.

  • LeftThumbstickDown[ControllerNumber] = true;: We set the LeftThumbstickDown state to true, indicating that the thumbstick is pressed down.

Index


Update Left Thumbstick X-Axis

private readonly void UpdateLeftThumbstickXaxis(int ControllerNumber)
{
    if (State.Gamepad.sThumbLX <= NeutralStart)
    {
        LeftThumbstickRight[ControllerNumber] = false;
        LeftThumbstickXaxisNeutral[ControllerNumber] = false;
        LeftThumbstickLeft[ControllerNumber] = true;
    }
    else if (State.Gamepad.sThumbLX >= NeutralEnd)
    {
        LeftThumbstickLeft[ControllerNumber] = false;
        LeftThumbstickXaxisNeutral[ControllerNumber] = false;
        LeftThumbstickRight[ControllerNumber] = true;
    }
    else
    {
        LeftThumbstickLeft[ControllerNumber] = false;
        LeftThumbstickRight[ControllerNumber] = false;
        LeftThumbstickXaxisNeutral[ControllerNumber] = true;
    }
}
  • private readonly void UpdateLeftThumbstickXaxis(int ControllerNumber): This method updates the X-axis position of the left thumbstick.

  • The logic is similar to the Y-axis update:

    • If the thumbstick's X position is less than or equal to NeutralStart, it is moved left.
    • If it exceeds NeutralEnd, it is moved right.
    • Otherwise, it is in the neutral position.

Index


Update Right Thumbstick Position

private void UpdateRightThumbstickPosition(int controllerNumber)
{
    UpdateRightThumbstickXaxis(controllerNumber);
    UpdateRightThumbstickYaxis(controllerNumber);
}
  • private void UpdateRightThumbstickPosition(int controllerNumber): This method updates the position of the right thumbstick by calling the respective methods for the X and Y axes.

Index


Update Right Thumbstick Y-Axis

private readonly void UpdateRightThumbstickYaxis(int controllerNumber)
{
    if (State.Gamepad.sThumbRY <= NeutralStart)
    {
        RightThumbstickDown[controllerNumber] = false;
        RightThumbstickYaxisNeutral[controllerNumber] = false;
        RightThumbstickUp[controllerNumber] = true;
    }
    else if (State.Gamepad.sThumbRY >= NeutralEnd)
    {
        RightThumbstickUp[controllerNumber] = false;
        RightThumbstickYaxisNeutral[controllerNumber] = false;
        RightThumbstickDown[controllerNumber] = true;
    }
    else
    {
        RightThumbstickUp[controllerNumber] = false;
        RightThumbstickDown[controllerNumber] = false;
        RightThumbstickYaxisNeutral[controllerNumber] = true;
    }
}
  • private readonly void UpdateRightThumbstickYaxis(int controllerNumber): This method checks the Y-axis position of the right thumbstick.

  • The logic follows the same pattern as the left thumbstick:

    • It determines if the thumbstick is pushed up, down, or in a neutral position.

Index


Update Right Thumbstick X-Axis

private readonly void UpdateRightThumbstickXaxis(int controllerNumber)
{
    if (State.Gamepad.sThumbRX <= NeutralStart)
    {
        RightThumbstickRight[controllerNumber] = false;
        RightThumbstickXaxisNeutral[controllerNumber] = false;
        RightThumbstickLeft[controllerNumber] = true;
    }
    else if (State.Gamepad.sThumbRX >= NeutralEnd)
    {
        RightThumbstickLeft[controllerNumber] = false;
        RightThumbstickXaxisNeutral[controllerNumber] = false;
        RightThumbstickRight[controllerNumber] = true;
    }
    else
    {
        RightThumbstickLeft[controllerNumber] = false;
        RightThumbstickRight[controllerNumber] = false;
        RightThumbstickXaxisNeutral[controllerNumber] = true;
    }
}
  • private readonly void UpdateRightThumbstickXaxis(int controllerNumber): This method updates the X-axis position of the right thumbstick.

  • The logic mirrors that of the left thumbstick's X-axis, determining if the thumbstick is moved left, right, or in a neutral position.

Index


Update Trigger Position Methods

Update Left Trigger Position

private void UpdateLeftTriggerPosition(int controllerNumber)
{
    if (State.Gamepad.bLeftTrigger > TriggerThreshold)
    {
        LeftTrigger[controllerNumber] = true;
    }
    else
    {
        LeftTrigger[controllerNumber] = false;
    }
}
  • private void UpdateLeftTriggerPosition(int controllerNumber): This method checks if the left trigger is pressed beyond the defined threshold.

  • If it is, the corresponding boolean for the left trigger is set to true; otherwise, it is set to false.

Index


Update Right Trigger Position

private void UpdateRightTriggerPosition(int controllerNumber)
{
    if (State.Gamepad.bRightTrigger > TriggerThreshold)
    {
        RightTrigger[controllerNumber] = true;
    }
    else
    {
        RightTrigger[controllerNumber] = false;
    }
}
  • private void UpdateRightTriggerPosition(int controllerNumber): This method performs the same check for the right trigger, updating its state accordingly.

Index


Update Neutral States Methods

Update D-Pad Neutral State

private void UpdateDPadNeutral(int controllerNumber)
{
    if (DPadDown[controllerNumber] ||
        DPadLeft[controllerNumber] ||
        DPadRight[controllerNumber] ||
        DPadUp[controllerNumber])
    {
        DPadNeutral[controllerNumber] = false;
    }
    else
    {
        DPadNeutral[controllerNumber] = true;
    }
}
  • private void UpdateDPadNeutral(int controllerNumber): This method checks if any D-Pad button is pressed.

  • If any button is pressed, the D-Pad is marked as not neutral; otherwise, it is set to neutral.

Index


Update Letter Buttons Neutral State

private void UpdateLetterButtonsNeutral(int controllerNumber)
{
    if (A[controllerNumber] ||
        B[controllerNumber] ||
        X[controllerNumber] ||
        Y[controllerNumber])
    {
        LetterButtonsNeutral[controllerNumber] = false;
    }
    else
    {
        LetterButtonsNeutral[controllerNumber] = true;
    }
}
  • private void UpdateLetterButtonsNeutral(int controllerNumber): This method checks if any letter buttons (A, B, X, Y) are pressed.

  • Similar to the D-Pad check, it updates the neutral state based on whether any buttons are active.

Index


Checking Connection Status

public bool IsConnected(int controllerNumber)
{
    try
    {
        return XInputGetState(controllerNumber, ref State) == 0;
    }
    catch (Exception ex)
    {
        Debug.Print($"Error getting XInput state: {controllerNumber} | {ex.Message}");
        return false;
    }
}
  • public bool IsConnected(int controllerNumber): This method checks if a specific controller is connected.

  • It returns true if the XInputGetState call returns 0, indicating a successful connection. If an error occurs, it logs the error and returns false.

Index


Test Initialization Method

public void TestInitialization()
{
    Debug.Assert(Buttons != null, "Buttons should not be null.");

    for (int i = 0; i < 4; i++)
    {
        Debug.Assert(!Connected[i], $"Controller {i} should not be connected after initialization.");
        Debug.Assert(LeftThumbstickXaxisNeutral[i], $"Left Thumbstick X-axis for Controller {i} should be neutral.");
        Debug.Assert(LeftThumbstickYaxisNeutral[i], $"Left Thumbstick Y-axis for Controller {i} should be neutral.");
        Debug.Assert(RightThumbstickXaxisNeutral[i], $"Right Thumbstick X-axis for Controller {i} should be neutral.");
        Debug.Assert(RightThumbstickYaxisNeutral[i], $"Right Thumbstick Y-axis for Controller {i} should be neutral.");
        Debug.Assert(DPadNeutral[i], $"DPad for Controller {i} should be neutral.");
        Debug.Assert(LetterButtonsNeutral[i], $"Letter Buttons for Controller {i} should be neutral.");
        Debug.Assert(!RightThumbstickLeft[i], $"Right Thumbstick Left for Controller {i} should not be true.");
        Debug.Assert(!RightThumbstickRight[i], $"Right Thumbstick Right for Controller {i} should not be true.");
        Debug.Assert(!RightThumbstickDown[i], $"Right Thumbstick Down for Controller {i} should not be true.");
        Debug.Assert(!RightThumbstickUp[i], $"Right Thumbstick Up for Controller {i} should not be true.");
        Debug.Assert(!LeftThumbstickLeft[i], $"Left Thumbstick Left for Controller {i} should not be true.");
        Debug.Assert(!LeftThumbstickRight[i], $"Left Thumbstick Right for Controller {i} should not be true.");
        Debug.Assert(!LeftThumbstickDown[i], $"Left Thumbstick Down for Controller {i} should not be true.");
        Debug.Assert(!LeftThumbstickUp[i], $"Left Thumbstick Up for Controller {i} should not be true.");
        Debug.Assert(!LeftTrigger[i], $"Left Trigger for Controller {i} should not be true.");
        Debug.Assert(!RightTrigger[i], $"Right Trigger for Controller {i} should not be true.");
        Debug.Assert(!A[i], $"A for Controller {i} should not be true.");
        Debug.Assert(!B[i], $"B for Controller {i} should not be true.");
        Debug.Assert(!X[i], $"X for Controller {i} should not be true.");
        Debug.Assert(!Y[i], $"Y for Controller {i} should not be true.");
        Debug.Assert(!LeftBumper[i], $"Left Bumper for Controller {i} should not be true.");
        Debug.Assert(!RightBumper[i], $"Right Bumper for Controller {i} should not be true.");
        Debug.Assert(!DPadUp[i], $"D-Pad Up for Controller {i} should not be true.");
        Debug.Assert(!DPadDown[i], $"D-Pad Down for Controller {i} should not be true.");
        Debug.Assert(!DPadLeft[i], $"D-Pad Left for Controller {i} should not be true.");
        Debug.Assert(!DPadRight[i], $"D-Pad Right for Controller {i} should not be true.");
        Debug.Assert(!Start[i], $"Start Button for Controller {i} should not be true.");
        Debug.Assert(!Back[i], $"Back Button for Controller {i} should not be true.");
        Debug.Assert(!LeftStick[i], $"Left Stick for Controller {i} should not be true.");
        Debug.Assert(!RightStick[i], $"Right Stick for Controller {i} should not be true.");
        Debug.Assert(!IsLeftVibrating[i], $"Is Left Vibrating for Controller {i} should not be true.");
        Debug.Assert(!IsRightVibrating[i], $"Is Right Vibrating for Controller {i} should not be true.");
    }
}
  • public void TestInitialization(): This method verifies that the initialization of the controllers was successful.

  • Debug.Assert(...): These statements check various conditions, ensuring that the state of each controller is as expected after initialization. If any condition fails, it will throw an assertion error during debugging.

Index


Vibration Methods

Vibration Structure

[DllImport("XInput1_4.dll")]
private static extern int XInputSetState(int playerIndex, 
                                         ref XINPUT_VIBRATION vibration);

public struct XINPUT_VIBRATION
{
    public ushort wLeftMotorSpeed;
    public ushort wRightMotorSpeed;
}

private XINPUT_VIBRATION Vibration;
  • [DllImport("XInput1_4.dll")]: This imports the function to set the vibration state of the controller.

  • public struct XINPUT_VIBRATION: This structure holds the speed settings for the left and right motors of the controller.

  • private XINPUT_VIBRATION Vibration;: This variable will hold the current vibration settings.

Index


Vibrate Left Method

public void VibrateLeft(int cid, ushort speed)
{
    Vibration.wLeftMotorSpeed = speed;
    LeftVibrateStart[cid] = DateTime.Now;
    IsLeftVibrating[cid] = true;
}
  • public void VibrateLeft(int cid, ushort speed): This method sets the speed of the left motor for the specified controller.

  • The current time is recorded to track how long the motor has been vibrating, and the IsLeftVibrating flag is set to true.

Index


Vibrate Right Method

public void VibrateRight(int cid, ushort speed)
{
    Vibration.wRightMotorSpeed = speed;
    RightVibrateStart[cid] = DateTime.Now;
    IsRightVibrating[cid] = true;
}
  • public void VibrateRight(int cid, ushort speed): This method works similarly to VibrateLeft, but for the right motor.

Index


Send Vibration Motor Command Method

private void SendVibrationMotorCommand(int controllerID)
{
    try
    {
        if (XInputSetState(controllerID, ref Vibration) == 0)
        {
            // The motor speed was set. Success.
        }
        else
        {
            Debug.Print($"{controllerID} did not vibrate.  {Vibration.wLeftMotorSpeed} |  {Vibration.wRightMotorSpeed} ");
        }
    }
    catch (Exception ex)
    {
        Debug.Print($"Error sending vibration motor command: {controllerID} | {Vibration.wLeftMotorSpeed} |  {Vibration.wRightMotorSpeed} | {ex.Message}");
        return; // Exit the method.
    }
}
  • private void SendVibrationMotorCommand(int controllerID): This method sends the vibration command to the specified controller.

  • If the command is successful (returns 0), it indicates that the motor speed was set. If not, it logs an error message.

Index


Update Vibration Timers Method

private void UpdateVibrateTimers()
{
    UpdateLeftVibrateTimer();
    UpdateRightVibrateTimer();
}
  • private void UpdateVibrateTimers(): This method updates the timers for both the left and right vibration motors.

Index


Update Left Vibrate Timer

private void UpdateLeftVibrateTimer()
{
    for (int ControllerNumber = 0; ControllerNumber < 4; ControllerNumber++)
    {
        if (IsLeftVibrating[ControllerNumber])
        {
            TimeSpan ElapsedTime = DateTime.Now - LeftVibrateStart[ControllerNumber];

            if (ElapsedTime.TotalMilliseconds >= TimeToVibe)
            {
                IsLeftVibrating[ControllerNumber] = false;
                Vibration.wLeftMotorSpeed = 0;
            }

            SendVibrationMotorCommand(ControllerNumber);
        }
    }
}
  • private void UpdateLeftVibrateTimer(): This method checks if the left motor is vibrating and calculates how long it has been vibrating.

  • If the elapsed time exceeds the set vibration time (TimeToVibe), it stops the vibration by setting the motor speed to zero.

Index


Update Right Vibrate Timer

private void UpdateRightVibrateTimer()
{
    for (int ControllerNumber = 0; ControllerNumber < 4; ControllerNumber++)
    {
        if (IsRightVibrating[ControllerNumber])
        {
            TimeSpan ElapsedTime = DateTime.Now - RightVibrateStart[ControllerNumber];

            if (ElapsedTime.TotalMilliseconds >= TimeToVibe)
            {
                IsRightVibrating[ControllerNumber] = false;
                Vibration.wRightMotorSpeed = 0;
            }

            SendVibrationMotorCommand(ControllerNumber);
        }
    }
}
  • private void UpdateRightVibrateTimer(): This method functions similarly to the left vibrate timer, checking the right motor's state and updating it accordingly.

Index


Overview of the Form1 Class

The Form1 class serves as the main user interface for the application. It handles controller input, updates the UI based on the state of connected controllers, and manages vibration functionality.

Key Components

  1. Controllers: An instance of the XboxControllers class, which manages the state and interactions with the Xbox controllers.
  2. Event Handlers: Methods that respond to user actions, such as button clicks and UI updates.
  3. UI Update Methods: Functions that refresh the UI to reflect the current state of the controllers.
  4. Initialization Methods: Functions that set up the application and its components.

Constructor

public Form1()
{
    InitializeComponent();
}
  • InitializeComponent(): This method is automatically generated by the Windows Forms designer and initializes the UI components defined in the form.

Form Load Event

private void Form1_Load(object sender, EventArgs e)
{
    InitializeApp();
    Controllers.Initialize();
}
  • Form1_Load: This event is triggered when the form loads. It calls InitializeApp() to set up the application and initializes the controllers.

Timer Tick Event

private void timer1_Tick(object sender, EventArgs e)
{
    Controllers.Update();
    UpdateLabels();
    UpdateRumbleGroupUI();
}
  • timer1_Tick: This method is called at regular intervals defined by the timer. It updates the state of the controllers, refreshes the UI labels, and manages the vibration group UI.

Button Click Events

Vibrate Left Button

private void ButtonVibrateLeft_Click(object sender, EventArgs e)
{
    if (Controllers.Connected[(int)NumControllerToVib.Value])
    {
        Controllers.VibrateLeft((int)NumControllerToVib.Value, (ushort)TrackBarSpeed.Value);
    }
}
  • Checks if the selected controller is connected before triggering the left vibration.

Vibrate Right Button

private void ButtonVibrateRight_Click(object sender, EventArgs e)
{
    if (Controllers.Connected[(int)NumControllerToVib.Value])
    {
        Controllers.VibrateRight((int)NumControllerToVib.Value, (ushort)TrackBarSpeed.Value);
    }
}
  • Similar to the left vibration, it checks connection status before triggering the right vibration.

TrackBar and NumericUpDown Events

TrackBar Scroll

private void TrackBarSpeed_Scroll(object sender, EventArgs e)
{
    UpdateSpeedLabel();
}
  • Updates the speed label whenever the vibration speed trackbar is adjusted.

NumericUpDown Value Change

private void NumericUpDownTimeToVib_ValueChanged(object sender, EventArgs e)
{
    Controllers.TimeToVibe = (int)NumericUpDownTimeToVib.Value;
}
  • Updates the time to vibrate based on user input in the numeric up-down control.

Updating Labels

Update Labels Method

private void UpdateLabels()
{
    for (int ControllerNumber = 0; ControllerNumber < 4; ControllerNumber++)
    {
        UpdateControllerStatusLabel(ControllerNumber);
        if (Controllers.Connected[ControllerNumber])
        {
            UpdateThumbstickLabels(ControllerNumber);
            UpdateTriggerLabels(ControllerNumber);
            UpdateDPadLabel(ControllerNumber);
            UpdateLetterButtonLabel(ControllerNumber);
            UpdateStartBackLabels(ControllerNumber);
            UpdateBumperLabels(ControllerNumber);
            UpdateStickLabels(ControllerNumber);
        }
    }
}
  • This method iterates through all controllers, updating their status and UI elements based on their current state.

Updating Specific Labels

Trigger Labels

private void UpdateTriggerLabels(int controllerNumber)
{
    UpdateLeftTriggerLabel(controllerNumber);
    UpdateRightTriggerLabel(controllerNumber);
}
  • Calls methods to update the labels for the left and right triggers.

Thumbstick Labels

private void UpdateThumbstickLabels(int controllerNumber)
{
    UpdateRightThumbstickLabels(controllerNumber);
    UpdateLeftThumbstickLabels(controllerNumber);
}
  • Updates the labels for both the left and right thumbsticks.

Clearing Labels

Each label clearing method checks if all controllers are in a neutral state for a specific control and clears the corresponding label if they are.

For example:

private void ClearRightTriggerLabel()
{
    bool NotActive = true; 
    for (int i = 0; i < 4; i++)
    {
        if (Controllers.Connected[i] && Controllers.RightTrigger[i])
        {
            NotActive = false;
            break;
        }
    }
    if (NotActive)
    {
        LabelRightTrigger.Text = string.Empty;
    }
}

D-Pad and Button Text Retrieval

Methods like GetDPadDirection and GetButtonText determine the current state of the D-Pad and button presses, respectively, returning the appropriate strings to display.

Initialization Methods

Initialize App

private void InitializeApp()
{
    Text = "XInput C# - Code with Joe";
    InitializeTimer1();
    ClearLabels();
    TrackBarSpeed.Value = 32767;
    UpdateSpeedLabel();
    InitializeToolTips();
}
  • Sets the form title, initializes the timer, clears labels, sets the default trackbar value, and initializes tooltips.

Initialize ToolTips

private void InitializeToolTips()
{
    // ToolTip setup for various controls
}
  • Configures tooltips for various UI elements to provide helpful information to the user.

Rumble Group UI Update

private void UpdateRumbleGroupUI()
{
    int NumberOfConnectedControllers = 0;
    int HighestConnectedControllerNumber = 0;

    for (int ControllerNumber = 0; ControllerNumber < 4; ControllerNumber++)
    {
        if (Controllers.Connected[ControllerNumber])
        {
            NumberOfConnectedControllers++;
            HighestConnectedControllerNumber = ControllerNumber;
        }
    }

    if (NumberOfConnectedControllers > 0)
    {
        // Enable controls based on connected controllers
    }
    else
    {
        NumControllerToVib.Maximum = 0;
        RumbleGroupBox.Enabled = false;
    }
}
  • Updates the UI elements related to vibration based on the number of connected controllers.

Index

  1. XInput C# 🎮

    • Welcome and Overview
    • Project Features
  2. Code Walkthrough

  3. Overview of the Form1 Class

    • Key Components
    • Constructor
    • Form Load Event
    • Timer Tick Event
    • Button Click Events
    • TrackBar and NumericUpDown Events
    • Updating Labels
    • Clearing Labels
    • D-Pad and Button Text Retrieval
    • Initialization Methods
    • Rumble Group UI Update
  4. The Neutral Zone

    • Importance and Functionality
  5. The Trigger Threshold

    • Importance and Functionality
  6. Things to Watch Out for When Converting from VB to C#

    • Key Syntax Differences
  7. A Funny Thing Happened on the Way to Porting My App


The Neutral Zone

The neutral zone refers to a specific range of input values for a controller's thumbsticks or triggers where no significant action or movement is registered. This is particularly important in gaming to prevent unintentional inputs when the player is not actively manipulating the controls.

The neutral zone helps to filter out minor movements that may occur when the thumbsticks or triggers are at rest. This prevents accidental inputs and enhances gameplay precision.

For thumbsticks, the neutral zone is defined by a range of values (-16384 to 16384 for a signed 16-bit integer). Movements beyond this range are considered active inputs.

036

Reduces the likelihood of unintentional actions, leading to a smoother gaming experience. Enhances control sensitivity, allowing for more nuanced gameplay, especially in fast-paced or competitive environments. Understanding the neutral zone is crucial for both developers and players to ensure that controller inputs are accurate and intentional.

Index


The Trigger Threshold

The trigger threshold refers to the minimum amount of pressure or movement required on a controller's trigger (or analog input) before it registers as an active input. This concept is crucial for ensuring that the controller responds accurately to player actions without registering unintended inputs.

The trigger threshold helps filter out minor or unintentional movements. It ensures that only deliberate actions are registered, improving gameplay precision.

For example, in a typical game controller, the trigger may have a range of values from 0 to 255 (for an 8-bit input). A threshold might be set at 64, meaning the trigger must be pulled beyond this value to register as "pressed." Values below 64 would be considered inactive.

037

Reduces accidental inputs during gameplay, especially in fast-paced scenarios where slight movements could lead to unintended actions. Provides a more controlled and responsive gaming experience, allowing players to execute actions more precisely.

Commonly used in racing games (for acceleration and braking), shooting games (for aiming and firing), and other genres where trigger sensitivity is important. Understanding the trigger threshold is essential for both developers and players to ensure that controller inputs are intentional and accurately reflect the player's actions.

063

Index


Bitwise Operations

The Button enumeration defines constants for each button on the Xbox controller, each assigned a unique bit value. This design allows for efficient state checking using bitwise operations.

enum Button
{
    DPadUp = 1,                 // 0000 0001
    DPadDown = 2,               // 0000 0010
    DPadLeft = 4,               // 0000 0100
    DPadRight = 8,              // 0000 1000
    Start = 16,                 // 0001 0000
    Back = 32,                  // 0010 0000
    LeftStick = 64,             // 0100 0000
    RightStick = 128,           // 1000 0000
    LeftBumper = 256,      // 0001 0000 0000
    RightBumper = 512,     // 0010 0000 0000
    A = 4096,         // 0001 0000 0000 0000
    B = 8192,         // 0010 0000 0000 0000
    X = 16384,        // 0100 0000 0000 0000
    Y = 32768         // 1000 0000 0000 0000
}

Understanding Bitwise Values

Each button is represented by a power of two, which corresponds to a single bit in binary. This allows for the following:

  • Unique Identification: Each button can be uniquely identified without overlap.
  • Efficient State Management: Multiple buttons can be represented in a single integer value.

Checking Button States

Bitwise operations allow you to check if a specific button is pressed by using the bitwise AND operator (&). Here’s how it works:

  • Example: Checking if the A button is pressed.
  if ((State.Gamepad.wButtons & (ushort)Button.A) != 0)
  {

  // A button is pressed
  Debug.Print($"A button is pressed");

  }

// State.Gamepad.wButtons             Button.A                   Result
//         4096            And          4096           =          4096         Decimal
//  0001 0000 0000 0000     &    0001 0000 0000 0000   =   0001 0000 0000 0000 Binary
//     ^                            ^                          A is pressed
//         61440           And          4096           =          4096 
//  1111 0000 0000 0000     &    0001 0000 0000 0000   =   0001 0000 0000 0000
//     ^                            ^                          A is pressed
//         65535           And          4096           =          4096 
//  1111 1111 1111 1111     &    0001 0000 0000 0000   =   0000 0000 0000 0000
//     ^                            ^                          A is pressed

//         8192            And          4096           =            0
//  0010 0000 0000 0000     &    0001 0000 0000 0000   =   0000 0000 0000 0000
//     ^                            ^                        A is not pressed

//         57344           And          4096           =            0
//  1110 0000 0000 0000     &    0001 0000 0000 0000   =   0000 0000 0000 0000
//     ^                            ^                        A is not pressed
  • In this example:
    • The expression (State.Gamepad.wButtons & (ushort)Button.A) performs a bitwise AND between the current state and the A button's value.
    • If the result is not zero != 0 , it indicates that the A button is currently pressed.

Advantages of Bitwise Operations

  1. Performance: Checking multiple buttons in a single operation is faster than checking each button individually.
  2. Scalability: Easily extendable if more buttons are added; just assign new powers of two.
  3. Compactness: Reduces the need for multiple boolean variables to track each button's state.

Example: Checking Multiple Buttons

You can also check for multiple buttons at once. For instance, to see if either the A or B button is pressed:

  if ((State.Gamepad.wButtons & (((ushort)Button.A) | (ushort)Button.B)) != 0)
  {

  // Either A or B button is pressed
  Debug.Print($" Either A or B button is pressed");

  }
  • Here, Button.A | Button.B combines the states of both buttons using the bitwise OR operator (|), allowing you to check if either button is pressed in one operation.

004

Using bitwise operations with the Button enumeration provides a powerful and efficient way to manage Xbox controller inputs, making it easier to develop responsive and interactive applications.

Enum for Button Mapping

Index


Feel free to experiment with the code, modify it, and add new features as you learn more about programming! If you have any questions, please post on the Q & A Discussion Forum, don’t hesitate to ask.


Things to watch out for when converting from VB to C#

Hi GitHub community! I’m thrilled to share my recent journey of porting my VB app, "XInput," into its new C# counterpart, "XInput CS." This experience has been both challenging and rewarding, and I’d love to share some insights that might help others considering a similar transition.

Here are some key syntax differences.

1. Imports and Namespace Declarations

VB: The Imports statement is used to include namespaces in the file. This allows you to use the classes and methods defined in the System.Runtime.InteropServices namespace without needing to fully qualify them.

Imports System.Runtime.InteropServices

C#: The using directive is used to include namespaces in the file. This allows you to use the classes and methods defined in the System.Runtime.InteropServices namespace without needing to fully qualify them.

using System.Runtime.InteropServices;

022

2. Class Declaration

VB: Classes are declared using the Class keyword. The visibility modifier Public is capitalized.

Public Class Form1

C#: Classes are declared using the class keyword. The visibility modifier public is in lowercase.

public class Form1

3. Attributes

VB: Attributes are defined using angle brackets <> .

<DllImport("XInput1_4.dll")>

C#: Attributes are defined using square brackets [] .

[DllImport("XInput1_4.dll")]

4. Function Declaration

VB: Shared keyword is used for static methods and ByRef is used to pass parameters by reference.

Private Shared Function XInputGetState(dwUserIndex As Integer, ByRef pState As XINPUT_STATE) As Integer

C#: extern keyword is used to indicate external function, static keyword is used for static methods, ref to pass the parameter by reference and ends with a semicolon ; .

private static extern int XInputGetState(int dwUserIndex, ref XINPUT_STATE pState);

5. Structure Declaration

VB: Structure keyword is followed by the struct name and its members are defined within Structure and End Structure .

<StructLayout(LayoutKind.Explicit)>
Public Structure XINPUT_STATE
    <FieldOffset(0)>
    Public dwPacketNumber As UInteger
    <FieldOffset(4)>
    Public Gamepad As XINPUT_GAMEPAD
End Structure

C#: struct keyword is followed by the struct name and its members are defined within curly braces {} .

[StructLayout(LayoutKind.Explicit)]
public struct XINPUT_STATE
{
    [FieldOffset(0)]
    public uint dwPacketNumber;
    [FieldOffset(4)]
    public XINPUT_GAMEPAD Gamepad;
}

6. Field Declaration

VB: Fields are declared using the As keyword to specify the type. The FieldOffset attribute specifies the position of the field within the structure. Attributes are defined using angle brackets <> .

<FieldOffset(0)>
Public dwPacketNumber As UInteger

C#: Fields are declared with a semicolon ; at the end. The FieldOffset attribute specifies the position of the field within the structure. Attributes are defined using square brackets [] .

[FieldOffset(0)]
public uint dwPacketNumber;

7. Arrays Declaration

VB: Arrays are declared using parentheses () and using a range (0 To 3) to define the size.

Private ConButtons(0 To 3) As UShort

C#: Arrays are declared using square brackets [] and initialized with the new keyword.

private ushort[] ConButtons = new ushort[4];

8. Constants Declaration

VB: Constants are declared using the Const keyword and the As keyword to specify the type.

Private Const NeutralStart As Short = -16384

C#: Constants are declared using the const keyword and a semicolon ; at the end.

private const short NeutralStart = -16384;

9. Enum Declaration

VB: Enums are declared using the Enum keyword and End Enum to close the declaration.

Public Enum BATTERY_TYPE As Byte
    DISCONNECTED = 0
    WIRED = 1
End Enum

C#: Enums are declared using the enum keyword and curly braces {} to define the body.

public enum BATTERY_TYPE : byte
{
    DISCONNECTED = 0,
    WIRED = 1
}

10. Subroutine Declaration

VB: Subroutines are declared using the Sub keyword. The Handles keyword is used to specify the event handler.

Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load

C#: Subroutines (methods that do not return a value) are declared using the void keyword. The void keyword is used to indicate that a method does not return any value.

private void Form1_Load(object sender, EventArgs e)

11. If Statement with AndAlso

VB: Uses AndAlso for logical AND and = for comparisons.

If DPadUpPressed = True AndAlso DPadDownPressed = False Then

C#: Uses && for logical AND and ! for NOT.

if (DPadUpPressed && !DPadDownPressed)

12. Try-Catch Block

VB: Uses Try and End Try to define the block.

Try
    ' Code
Catch ex As Exception
    DisplayError(ex)
End Try

C#: Uses braces {} to define the block.

try
{
    // Code
}
catch (Exception ex)
{
    DisplayError(ex);
}

13. For Each Loop

VB: The For Each keyword is used for iteration.

For Each Con In ConButtons

C#: The foreach keyword is used to iterate through collections.

foreach (var con in ConButtons)

14. Return Statement

VB: The Return keyword is used to return a value, and = is used for comparison.

Return XInputGetState(controllerNumber, ControllerPosition) = 0

C#: The return keyword is used to return a value from a function, and == is used for comparison.

return XInputGetState(controllerNumber, ControllerPosition) == 0;

15. String Concatenation

VB: Strings are concatenated using the & operator.

LabelButtons.Text = "Controller " & ControllerNumber.ToString & " Button: Up"

C#: Strings are concatenated using the + operator.

LabelButtons.Text = "Controller " + ControllerNumber.ToString() + " Button: Up";

16. DateTime Handling

VB: Uses Now to get the current date and time.

Dim currentTime As DateTime = Now

C#: Uses DateTime.Now to get the current date and time.

DateTime currentTime = DateTime.Now;

These examples illustrate some of the common syntax differences you'll encounter when converting VB code to C#.

A Funny Thing Happened on the Way to Porting My App

So, I embarked on a journey to port my app, XInput , from VB to C# with the help of my AI assistant, Monica. Let me tell you, Monica is a game changer!

She zipped through converting the VB code to C# at lightning speed, as AI assistants do. But where she really shines is in her suggestions. Every time I asked for C# code, she’d nudge me with ideas like, “How about a function?” And I’d be like, “Oh yeah! That does look better. Maybe I should use more functions?”

Monica was really pushing me ahead, keeping my code clean and efficient. Thanks, Monica! I guess? 😄

In the midst of all this, I got a little carried away and redesigned the app’s interface. Now, I have to go back and redo the original app’s interface to match! Because, you know, I’m that type of guy. They need to look good side by side!

023