JDPeckham

It's my biggest gripe about XNA and directx, there's no unified input...  

I don't have a 360 controller hooked to my pc, not even sure if that is possible... is it Anyhow, since i took the whole unified input idea from Lynn T Harrison's book, i'll share it here in hopes that people can email me suggestions and feedback. The enum translations aren't finished yet and the InputManager is a singleton (i'm on a singleton kick lately don't know why), and obviously the gamepad implementation isn't finished.  Hopefully you can sortof see where i was headed with the 360 implementation...

 

wel here it is, i guess email me at jhered at yahoo com if you have suggestions.

 




Re: XNA Framework Unified Input

JDPeckham

 

#region USINGS

using System;

using System.Collections.Generic;

using Microsoft.Xna.Framework;

using Microsoft.Xna.Framework.Design;

using Microsoft.Xna.Framework.Input;

using System.Diagnostics;

#endregion

namespace Gbg.Input

{

public struct ButtonActionMapping

{

public ButtonActionEventHandler Action;

}

public struct AxisActionMapping

{

public AxisActionEventHandler Action;

}

public enum ControlAxis

{

X,

Y,

Z

}

public delegate void ButtonActionEventHandler();

 

public enum ActionButton

{

LeftMouse= 0,

RightMouse = 1,

MiddleMouse = 2,

a = (int)Keys.A,

b = (int)Keys.B,

c = (int)Keys.C,

d = (Int32)Keys.D,

e = (Int32)Keys.E,

f = (Int32)Keys.F,

g = (Int32)Keys.G,

h = (Int32)Keys.H,

i = (Int32) Keys.I,

//and so on

}

public delegate void AxisActionEventHandler(int amount);

 

 

public sealed class InputManager : GameComponent

{

private ButtonActionMapping[] mouseDoubleClickActions = new ButtonActionMapping[3];

private Dictionary<ControlAxis, AxisActionMapping> axisActionMapLookup = new Dictionary<ControlAxis, AxisActionMapping>();

private Dictionary<ActionButton, ButtonActionMapping> buttonDownActionmapLookup = new Dictionary<ActionButton, ButtonActionMapping>();

private Dictionary<ActionButton, ButtonActionMapping> buttonUpActionmapLookup = new Dictionary<ActionButton, ButtonActionMapping>();

public void AddButtonDownAction(ActionButton button, ButtonActionMapping mapping)

{

if (!buttonDownActionmapLookup.ContainsKey(button))

{

buttonDownActionmapLookup.Add(button, mapping);

}

}

public void AddButtonUpAction(ActionButton button, ButtonActionMapping mapping)

{

if (!buttonUpActionmapLookup.ContainsKey(button))

{

buttonUpActionmapLookup.Add(button, mapping);

}

}

public void AddAxisAction(ControlAxis axis, AxisActionMapping mapping)

{

if (!axisActionMapLookup.ContainsKey(axis))

{

axisActionMapLookup.Add(axis, mapping);

}

}

public void SetDoubleClickAction(ActionButton button, ButtonActionMapping mapping)

{

if ((int)button < 0 || (int)button > 2) throw new ArgumentException("Only mouse buttons can be mapped");

mouseDoubleClickActions[(int)button] = mapping;

}

public void ClearButtonDownActions()

{ buttonDownActionmapLookup.Clear(); }

public void ClearButtonUpActions()

{ buttonUpActionmapLookup.Clear(); }

public void ClearAxisActions()

{ axisActionMapLookup.Clear(); }

public void ClearDoubleClickActions()

{

mouseDoubleClickActions[0].Action = null;

mouseDoubleClickActions[1].Action = null;

mouseDoubleClickActions[2].Action = null;

}

public void InitializeComponent()

{ }

private static InputManager _instance;

private static object syncRoot = new object();

private InputManager(Microsoft.Xna.Framework.Game game) : base (game)

{

InitKeyBoard();

InitMouse();

InitPads();

}

 

 

 

public static InputManager GetInstance(Microsoft.Xna.Framework.Game game)

{

lock (syncRoot)

{

if (_instance == null) _instance = new InputManager(game);

return _instance;

}

}

public override void Update(GameTime gameTime)

{

UpdateKeyBoardInput();

UpdateMouseInput(gameTime);

UpdateJoyPadInput(gameTime);

base.Update(gameTime);

}

#region Keyboard

/// <summary>

/// Keys held down last frame

/// </summary>

Keys[] keysHeldLastFrame;

// Stored key-press lists to compare against.

/// <summary>

/// Keys newly pressed this frame

/// </summary>

List<Keys> keysPressedThisFrameList = new List<Keys>(5);

/// <summary>

/// Keys newly released this frame

/// </summary>

List<Keys> keysReleasedThisFrameList = new List<Keys>(5);

private void InitKeyBoard()

{

keysHeldLastFrame = Keyboard.GetState().GetPressedKeys();

}

void UpdateKeyBoardInput()

{

// Clear our pressed and released lists.

keysPressedThisFrameList.Clear();

keysReleasedThisFrameList.Clear();

// Interpret pressed key data between arrays to

// figure out just-pressed and just-released keys.

KeyboardState currentState = Keyboard.GetState();

Keys[] currentKeys = currentState.GetPressedKeys();

// First loop, looking for keys just pressed.

for (int currentKey = 0; currentKey < currentKeys.Length; currentKey++)

{

bool found = false;

for (int previousKey = 0; previousKey < keysHeldLastFrame.Length; previousKey++)

{

if (currentKeys[currentKey] == keysHeldLastFrame[previousKey])

{

// The key was pressed both this frame and last; ignore.

found = true;

break;

}

}

if (!found)

{

// The key was pressed this frame, but not last frame; it was just pressed.

keysPressedThisFrameList.Add(currentKeys[currentKey]);

}

}

// Second loop, looking for keys just released.

for (int previousKey = 0; previousKey < keysHeldLastFrame.Length; previousKey++)

{

bool found = false;

for (int currentKey = 0; currentKey < currentKeys.Length; currentKey++)

{

if (keysHeldLastFrame[previousKey] == currentKeys[currentKey])

{

// The key was pressed both this frame and last; ignore.

found = true;

break;

}

}

if (!found)

{

// The key was pressed last frame, but not this frame; it was just released.

keysReleasedThisFrameList.Add(keysHeldLastFrame[previousKey]);

}

}

// Set the held state to the current state.

keysHeldLastFrame = currentKeys;

//fire events

foreach (Keys key in keysPressedThisFrameList)

{

if (buttonDownActionmapLookup.ContainsKey((ActionButton)key))

{

ButtonActionMapping mapping = buttonDownActionmapLookup[(ActionButton)key];

mapping.Action();

}

}

foreach (Keys key in keysReleasedThisFrameList)

{

if (buttonUpActionmapLookup.ContainsKey((ActionButton)key))

{

ButtonActionMapping mapping = buttonUpActionmapLookup[(ActionButton)key];

mapping.Action();

}

}

}

#endregion

#region GamePad

//gamepad helpers

int pad1Xmoved, pad1Ymoved, pad1Zmoved;

/*

int pad2Xmoved, pad2Ymoved, pad2Zmoved;

int pad3Xmoved, pad3Ymoved, pad3Zmoved;

int pad4Xmoved, pad4Ymoved, pad4Zmoved;

* */

GamePadState oldPadState1;

/*

GamePadState oldPadState2;

GamePadState oldPadState3;

GamePadState oldPadState4;

* */

float gamePadScalingFactor = 0.025f;

private void InitPads()

{

oldPadState1 = GamePad.GetState(PlayerIndex.One);

/*

oldPadState2 = GamePad.GetState(PlayerIndex.Two);

oldPadState3 = GamePad.GetState(PlayerIndex.Three);

oldPadState4 = GamePad.GetState(PlayerIndex.Four);

* */

}

private void UpdateJoyPadInput(GameTime gameTime)

{

//TODO: get joypad axis input and translate to amount of xyz moved this frame so we can fire actions

//TODO: Get joypad button input and record in list so we can fire joybutton input actions

//TODO: I don't have an xbox 360 so i can't test this stuff but here goes..

int ms = gameTime.ElapsedGameTime.Milliseconds;

GamePadState player1 = GamePad.GetState(PlayerIndex.One);

GamePadState player2= GamePad.GetState(PlayerIndex.Two);

GamePadState player3= GamePad.GetState(PlayerIndex.Three);

GamePadState player4 = GamePad.GetState(PlayerIndex.Four);

pad1Xmoved = (int)((float)player1.ThumbSticks.Right.X * gamePadScalingFactor * (float)ms);

pad1Ymoved = (int)((float)player1.ThumbSticks.Right.Y * gamePadScalingFactor *(float)ms);

int upValue = (int)((float)(player1.DPad.Up == ButtonState.Pressed -1 : 0) * gamePadScalingFactor * (float)ms);

int downValue = (int)((float)(player1.DPad.Down == ButtonState.Pressed 1 : 0) * gamePadScalingFactor * (float)ms);

pad1Zmoved = downValue + upValue;

//TODO: other players, put these into objects and a dictionary<PlayerIndex> for those objects

}

#endregion

#region Mouse

//mouse helpers

MouseState oldMouseState;

double lastClick1, lastClick2, lastClick3;

double _dblClickSensitivity = 250;

public double DoubleClickSensitivity

{

get { return _dblClickSensitivity; }

set

{

_dblClickSensitivity = value;

}

}

private void InitMouse()

{

oldMouseState = Mouse.GetState();

}

private void UpdateMouseInput(GameTime gameTime)

{

int mouseXmoved, mouseYmoved, mouseZmoved;

//TODO: get mouse axis and translate to xyz this frame for axis action maps

//TODO: Get mouse buttons fired and record for mouse button input

MouseState mouseState = Mouse.GetState();

mouseXmoved = mouseState.X - oldMouseState.X;

mouseYmoved = mouseState.Y - oldMouseState.Y;

mouseZmoved = mouseState.ScrollWheelValue - oldMouseState.ScrollWheelValue;

if (mouseXmoved != 0)

{

if(axisActionMapLookup.ContainsKey(ControlAxis.X))

{

axisActionMapLookup[ControlAxis.X].Action(mouseXmoved);

}

}

if(mouseYmoved != 0)

{

if(axisActionMapLookup.ContainsKey(ControlAxis.Y))

{

axisActionMapLookup[ControlAxis.Y].Action(mouseYmoved);

}

}

if(mouseZmoved != 0)

{

if(axisActionMapLookup.ContainsKey(ControlAxis.Z))

{

axisActionMapLookup[ControlAxis.Z].Action(mouseZmoved);

}

}

//get the mouse button pressed and released this frame flags

if (oldMouseState.LeftButton == ButtonState.Released && mouseState.LeftButton == ButtonState.Pressed)

{

if (buttonDownActionmapLookup.ContainsKey(ActionButton.LeftMouse))

{

ButtonActionMapping mapping = buttonDownActionmapLookup[ActionButton.LeftMouse];

mapping.Action();

}

}

if (oldMouseState.LeftButton == ButtonState.Pressed && mouseState.LeftButton == ButtonState.Released)

{

if (buttonUpActionmapLookup.ContainsKey(ActionButton.LeftMouse))

{

ButtonActionMapping mapping = buttonUpActionmapLookup[ActionButton.LeftMouse];

mapping.Action();

}

if ((gameTime.TotalGameTime.TotalMilliseconds - lastClick1) < DoubleClickSensitivity && lastClick1 != 0)

{

if (mouseDoubleClickActions[(int)ActionButton.LeftMouse].Action != null)

{

mouseDoubleClickActions[(int)ActionButton.LeftMouse].Action();

}

lastClick1 = 0;

}

else

{

lastClick1 = gameTime.TotalGameTime.TotalMilliseconds;

}

}

if (oldMouseState.RightButton == ButtonState.Released && mouseState.RightButton == ButtonState.Pressed)

{

if (buttonDownActionmapLookup.ContainsKey(ActionButton.RightMouse))

{

ButtonActionMapping mapping = buttonDownActionmapLookup[ActionButton.RightMouse];

mapping.Action();

}

}

if (oldMouseState.RightButton == ButtonState.Pressed && mouseState.RightButton == ButtonState.Released)

{

if (buttonUpActionmapLookup.ContainsKey(ActionButton.RightMouse))

{

ButtonActionMapping mapping = buttonUpActionmapLookup[ActionButton.RightMouse];

mapping.Action();

}

if ((gameTime.TotalGameTime.TotalMilliseconds - lastClick2) < DoubleClickSensitivity && lastClick2 != 0)

{

if (mouseDoubleClickActions[(int)ActionButton.RightMouse].Action != null)

{

mouseDoubleClickActions[(int)ActionButton.RightMouse].Action();

}

lastClick2 = 0;

}

else

{

lastClick2 = gameTime.TotalGameTime.TotalMilliseconds;

}

}

if (oldMouseState.MiddleButton == ButtonState.Released && mouseState.MiddleButton == ButtonState.Pressed)

{

if (buttonDownActionmapLookup.ContainsKey(ActionButton.MiddleMouse))

{

ButtonActionMapping mapping = buttonDownActionmapLookup[ActionButton.MiddleMouse];

mapping.Action();

}

}

if (oldMouseState.MiddleButton == ButtonState.Pressed && mouseState.MiddleButton == ButtonState.Released)

{

if (buttonUpActionmapLookup.ContainsKey(ActionButton.MiddleMouse))

{

ButtonActionMapping mapping = buttonUpActionmapLookup[ActionButton.MiddleMouse];

mapping.Action();

}

if ((gameTime.TotalGameTime.TotalMilliseconds - lastClick3) < DoubleClickSensitivity && lastClick3 != 0)

{

if (mouseDoubleClickActions[(int)ActionButton.MiddleMouse].Action != null)

{

mouseDoubleClickActions[(int)ActionButton.MiddleMouse].Action();

}

lastClick3 = 0;

}

else

{

lastClick3 = gameTime.TotalGameTime.TotalMilliseconds;

}

}

oldMouseState = mouseState;

}

#endregion

}

 

}






Re: XNA Framework Unified Input

Peter D.

You can purchase a 360 controller for windows.

http://www.microsoft.com/hardware/gaming/ProductDetails.aspx pid=090









Re: XNA Framework Unified Input

Jon Watte

I don't have a 360 controller hooked to my pc, not even sure if that is possible


Yes, you can plug a 360 controller into a PC. You can buy one like that, or you can buy a cable adapter if you already have the controller. Soon, Microsoft will also release a wireless adapter for Windows, to use the Xbox wireless adapter.

By the way: your code is WAY too long to work in the forum. If you're going to post that much, please post it on a web page, and post a link here. Or post only the pertinent parts, cutting out the long boring bits and replacing them with "...".





Re: XNA Framework Unified Input

waruwaru

Peter D. wrote:
You can purchase a 360 controller for windows.

http://www.microsoft.com/hardware/gaming/ProductDetails.aspx pid=090

The wired 360 controller will work just the same. Usually a little cheaper than the Windows specific one.

http://www.xbox.com/en-US/hardware/x/xbox360controller/

The gaming receiver let you connect your 360 accessories to windows.

http://www.xbox.com/en-US/hardware/x/xbox360wirelessgamingreceiver/






Re: XNA Framework Unified Input

JDPeckham

i got rid of the extra post and cut off some of the enum...






Re: XNA Framework Unified Input

JDPeckham

Peter D. wrote:
You can purchase a 360 controller for windows.

http://www.microsoft.com/hardware/gaming/ProductDetails.aspx pid=090




and this one works with xna gamepad class






Re: XNA Framework Unified Input

waruwaru

JDPeckham wrote:

Peter D. wrote:
You can purchase a 360 controller for windows.

http://www.microsoft.com/hardware/gaming/ProductDetails.aspx pid=090




and this one works with xna gamepad class

Yes, it will work with the XNA gamepad class. Hardware wise, it is the exactly same as the XBox 360 Wired controller. You can download the driver from MS. Many people are using it to test their XNA games. I am still waiting for my remote gaming adapter thing to arrive...






Re: XNA Framework Unified Input

Orofiamma

JDPeckham I appreciate your work and I'd sure use it if you could post it on a website, as someone suggested.

I can understand who say "just buy a Microsoft gamepad", but there is a problem.

Someone, i.e. me, who just writes games as hobbyst (shouldn't be XNA for hobbyst ) would like to program something usable by his own gamepads (unlucky me, I have 4, and all of them not xna-compliant) and by his friends. Should I say to all my friends: if you want to play my little game buy a new gamepad

We can emulate almost everything on our pc, and we cannot emulate a xbox controller by using a non-xbox controller

I tried, as many suggest, to mix DirectInput and XNA... a failure, it even does not compile complaining about DirectX.





Re: XNA Framework Unified Input

SoopahMan

Few if anyone has a 360 Gamepad for the PC. Requiring the 360 controller is just silly.

However, I don't see why using the normal DirectInput classes (as long as you Reference them!) should slow you down any, which will get you the "old" way of getting gamepad input - PC gamepad input. I'm going to go ahead and give that a shot.

For those interested in doing the same this is an OK outline:
http://msdn.microsoft.com/coding4fun/gaming/arcade/article.aspx articleid=940908
(Updated with better tutorial)

You basically get the gamepads from DirectInput via a SystemGuid and then just mess with the axes. As a word of caution, the axes on a PC controller do not map exactly like you might expect - I recommend you write a simple app checking all of the axes for feedback, then map your controller's specific used axes, and proceed from there. For example, my controller is a PC gamepad yet looks a lot like a 360 pad, so it has a 4-way input and 2 analog sticks. These actually map to 6 different axes in DirectInput but the final 2 are counter-intuitive to what I would have expected.




Re: XNA Framework Unified Input

SoopahMan

More elaborate code in translating DirectInput here:
http://www.idi.ntnu.no/~fredrior/files/Joystick.NET/Joystick.cs

I've written a class called DirectInputGamepad to handle this issue and eliminate the namespace-collission issue with XNA. It will take a couple days before the project is ready on SourceForge but I'll post a link to it here once it's ready. Post here if you're interested in such a compatibility library.




Re: XNA Framework Unified Input

SoopahMan

Wrapper class posted:
http://forums.microsoft.com/MSDN/ShowPost.aspx PostID=1265609&SiteID=1&mode=1




Re: XNA Framework Unified Input

Kyle0654

I did something like this a while ago, except I took the GameController base class route with different derived classes, which you map however you liked (e.g. you could add a mapping from "Attack" to the A button on a controller). It worked pretty nicely, and you could switch out the controllers pretty nicely. I should probably update it sometime for more general use - though writing an input handler is always a nice exercise.




Re: XNA Framework Unified Input

SoopahMan

Cool move. If you have ideas for making mapping buttons simpler, the code posted is Open Source (BSD license so you can use it for whatever) and it's freely accessible from SubVersion.




Re: XNA Framework Unified Input

Alcedes78

I am not familiar with Harrison or this book, but I think the Adapter Design Pattern will address what you are asking about. In short, if there are a specific set of inputs that your game expects (let's say four directions, a button for action1, and a button for action2) then you would define an interface class suitable for retrieving that information. Then you would create a wrapper for each type of input device that implements this interface.

In the event that a new type of device is to be made available to your title then you would only need to make another class that implements the interface. Of course this only addresses the issue of making the input device usable to the application, but not the mapping of the buttons to the inputs that your game would expect. I believe that previous code example may have address that issue.

You could potentially make the adapter/wrapper classes dynamically loadable so that when adding a new input class you don't need to recompile the game; you would only need to add the DLL to the games directory.