PC Gamepad

From XNAWiki
Jump to: navigation, search

The XNA GamePad class does not support standard PC game pads because it is based on XINPUT, an API specifically designed for Xbox 360 controllers. To read data from standard PC game pads, DirectInput needs to be used. DirectInput ships with Windows XP SP2 and later. It also hasn't changed since the DirectX SDK Summer 2004 Update (source), so you do not have to include the DirectX runtime or check for the availability of DirectInput on a target system. If XNA run on it, it has DirectInput.

Using DirectInput in .NET

In order to access DirectInput from C#, a managed wrapper is required. There are two popular wrappers that can be used for this purpose:

Managed DirectX 1.1

Discontinued in favor of XNA Game Studio, MDX 1.1 is kept in "maintenance mode" and can still be used to access parts of DirectX that XNA doesn't support. However, it's quite dated and has the following drawbacks:

  • It requires you to include an additional bootstrapper in your game's installer to deploy MDX 1.1 because the Managed DirectX DLLs are not included in the XNA redistributable.
  • MDX 1.1 targets .NET 1.1 and therefore will require you to include the Microsoft .NET 1.1 Framework in your game's installer (.NET 1.1 no longer ships with Windows 7, for example) - even if your XNA game is based on XNA 4.0 and .NET 4.0.
  • Due to some issues in .NET 1.1 that were only fixed in .NET 2.0 and later, you'll trigger the LoaderLock MDA (which can be turned off) and your game could, although with a very low probability, lock up whenever it launches.

SlimDX

SlimDX is a community project started by Promit of gamedev.net fame. It wraps all of the functionality offered by DirectX, including Direct3D 10.0 and Direct3D 11.0 as well as DirectInput. There are builds for .NET 2.0 (XNA 3.1) and .NET 4.0 (XNA 4.0) available. SlimDX consists of a single assembly that can be included along with the game's executable, requiring no further installation steps.

  • SlimDX depends on the Visual C++ runtime. Current builds (2010-06) require the Visual C++ 10.0 (2010) runtime, which is included with the XNA 4.0 redistributable, so at the moment, this is a non-issue.
  • The SlimDX DLL weights in at 3.3 MiB. Theoretically (and with the some knowledge of C++), a stripped-down build of SlimDX could be created that only includes the classes relevant for DirectInput.

PC GamePad classes for XNA

Soopah's PC Gamepad Support for XNA Framework

Soopah.Xna.Input.dll is a very compact wrapper based on MDX 1.1. It provides access to all DirectInput devices and all buttons, sticks and pads these devices provides (theoretically, a DirectInput controller can have a much larger number of these than an XINPUT aka. Xbox 360 controller)

The source code can be obtained via https://xnadirectinput.svn.sourceforge.net/svnroot/xnadirectinput

Example:

var gamePad = DirectInputGamePad.GamePads[0]; // static member
var thumbSticks = gamePad.Thumbsticks; // state is queried here
 
if(thumbSticks.Left.X > 0.5f)
{
    RunRight();
}

Nuclex.Input

Nuclex.Input is a DirectInput wrapper based on SlimDX that maps up to 4 DirectInput controllers into XNA's GamePadState structure. It follows the XNA/XINPUT design by always providing 4 controllers and returning a neutral state if the queried controller isn't connected.

The source code is part of the Nuclex Framework that can be found on CodePlex: http://nuclexframework.codeplex.com/releases

Example:

var input = new InputManager(); // in Game constructor
 
input.Update() // state is queried here
 
var xnaState = input.GetGamePad(PlayerIndex.One).GetState(); // First XNA GamePad
var diState = input.GetGamePad(ExtendedPlayerIndex.Five).GetState(); // First DirectInput GamePad
 
if(diState.ThumbSticks.Left.X > 0.5f)
{
    RunRight();
}

MDX-based DirectInput GamePad class

This class maps the DirectInput.JoystickState to a Microsoft.Xna.Framework.Input.GamePadState

#if !XBOX360
 
using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;
using DI = Microsoft.DirectX.DirectInput;
 
namespace PCInput
{
    /// <summary>
    /// Encapsulates a DirectInput device for use with XNA
    /// </summary>
    public class PCGamepad
    {
        protected DI.Device device;
        protected DI.DeviceCaps caps;
        const float center = 32767.5f;
 
        /// <summary>
        /// Maps the first 12 pc gamepad buttons to the matching 
        /// bit positions of Microsoft.Xna.Framework.Input.Buttons
        /// </summary>
        public int[] ButtonMappings = { 14, 12, 13, 15, 8, 9, 23, 22, 4, 5, 7, 8 };
 
        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="gamepadInstanceGuid">The DirectInput device Guid</param>
        public PCGamepad(Guid gamepadInstanceGuid)
        {
            device = new DI.Device(gamepadInstanceGuid);
            device.SetDataFormat(DI.DeviceDataFormat.Joystick);
            device.Acquire();
            caps = device.Caps;
        }
 
        /// <summary>
        /// Converts the current state of this device to an XNA GamePadState
        /// </summary>
        /// <returns>The GamePadState for this DirectInput device</returns>
        public GamePadState GetState()
        {
            // Get the JoystickState state
            DI.JoystickState joyState = device.CurrentJoystickState;
 
            // Point Of View = DPad
            int pov = joyState.GetPointOfView()[0];
 
            byte[] btns = joyState.GetButtons();
 
            // Map the DirectInput buttons to XNA Buttons enum values
            Buttons padBtns = (Buttons)0;
            int btnCount = Math.Min(caps.NumberButtons, 12);
            for (int i = 0; i < btnCount; i++)
            {
                if (btns[i] != 0)
                    padBtns |= (Buttons)(1 << ButtonMappings[i]);
            }
 
            // Put it all together. 
            GamePadState gs = new GamePadState(
                new GamePadThumbSticks(
                    caps.NumberAxes <= 0 ? Vector2.Zero : 
                        new Vector2((joyState.X - center) / center, -(joyState.Y - center) / center),
                    caps.NumberAxes <= 2 ? Vector2.Zero :
                        new Vector2((joyState.Z - center) / center, -(joyState.Rz - center) / center)),
                new GamePadTriggers(
                    (0 != (int)(padBtns & Buttons.LeftTrigger)) ? 1.0f : 0.0f,
                    (0 != (int)(padBtns & Buttons.RightTrigger)) ? 1.0f : 0.0f),
                new GamePadButtons(padBtns),
                (pov < 0) ? new GamePadDPad() : new GamePadDPad(
                    (pov > 27000 || pov < 9000) ? ButtonState.Pressed : ButtonState.Released,
                    (9000 < pov && pov < 27000) ? ButtonState.Pressed : ButtonState.Released,
                    (18000 < pov) ? ButtonState.Pressed : ButtonState.Released,
                    (0 < pov && pov < 18000) ? ButtonState.Pressed : ButtonState.Released));
 
            return gs;
        }
    }
}
#endif

Your PC input manager might look something like this:

namespace PCInput
{
    public static class PCGamepadManager
    {
        public static List<PCGamepad> Pads = new List<PCGamepad>();
 
        static PCGamepadManager()
        {
	    RefreshPads();           
        }
 
        public static void RefreshPads()
        {
            DI.DeviceList joystickInstanceList = DI.Manager.GetDevices(
                DI.DeviceType.Joystick, DI.EnumDevicesFlags.AttachedOnly);
 
            Pads.Clear();
            foreach (DI.DeviceInstance deviceInstance in joystickInstanceList)
            {
                Pads.Add(new PCGamepad(deviceInstance.InstanceGuid));
            }
        }
    }
}

Now, you can treat your PC Gamepads much like the XBox 360 Gamepads:

GamePadState padState = PCGamepadManager.Pads[0].GetState();
 
if (padState.ThumbSticks.Left.X > 0.5f)
{
    RunRight();
}

Thanks to Soopah for his code at [1] for providing a great starting point. --Roonda 14:20, 17 September 2009 (UTC)