Thanks a lot to navarraDotNet too !
AJAX, XNA, WF, WCF, WPF, WTF does that mean?
Thanks a lot to navarraDotNet too !
[Warning: This article is obsolote. I´d suggest you to read the Refreshed version here.]
We continue with the MDX Custom Action Mapping series where we left it. This is, by now, the last part of the tutorial. Hope it helps you...
Bind up the physical object
What do we put in the file to identify the physical object? It´s name? It´s ID? It´s offset?
Any of them would work if we´d only need to recover info about a physical device, as it´s name, properties, etc. You can do that, looping through the collection Device.Objects, and searching by any of that terms. The problem is that we don´t only need that, we need to retrieve data from that object.
Steping back to the previous part, we could think of using Inmediate Mode, and storing in the file the name of the field in JoystickState struct we wanted to map the game action to, regardless of the physical device it´s attached to. That might be a solution, but once we have the JoystickState structure, how do we get the value of a field whose name is stored in a variable? One way to do that is to make an unsafe pointer access to the struct, if you know the address and size of the field you want to retrieve. This is, you know, so C++, that we´ll discard it by now.
There´s a simpler and better way...so don´t use inmediate mode. That´s why ActionMapping does so too, I guess.
Let´s have a look again at buffered mode. Specially to the BufferedData object. The significant difference in this way of retrieving data is the field Offset . This field does really have a meaning! It is the Physical Device Object´s offset, if Im not wrong, and it will serve us as a link between GameActions and the physical devices. So, what we want to store in the file is the PhysicalOffset, like this:
Map Action="Steering Action"
PhysicalDeviceGuid="1820-12820-2147-94579-3426-4575"
PhysicalObjectOffset="138452"
So there´s the link. We choose BufferedMode and go ahead !
Designing the Action class
It´s a good Idea to defina a class like this:
public class Action
{
public eGameAction mGameAction;
public Device mPhysicalDevice;
public int mPhysicalObjectOffset;
public int mLastDataReaded;
}
It will handle the mapping between a GameAction and a Physical Object, and store the last readed value. Then, to handle the whole Action Map, we could define structures like this (optional, make your own for your purposes):
1.- Collections.Generic.Dictionary<> > mActionMap;
This collection has a first dictionary, keyed by the DirectInput Device, and whose value is another dictionary, with all the Actions mapped for that device, keyed by the offset of the object.
2.- Collections.Generic.Dictionary
This is a plain list of the actions available in the application. Initialized for every eGameAction, regardless it´s mapped to a device or not.
Initializing and Storing the Action Map
The best way to store the action map is an XML file in the user´s ApplicationData special folder. This way, the config will be made for every machine the application is installed.
To save the settings, simplly loop for every device in mActionMap saving it´s Guid, and a list of actions, just as we´ve seen before.
To read the settings, just load the xml file, loop through it´s nodes, and do the following:
1.- Recover a GameAction based on it´s name: Just as we said earlier, use Enum.Parse ( typeof( eGameActions), name);
2.- Recover a device instance by it´s Guid: Just loop throught the Available Devices searching one with the same guid.
The DoSteps method
Once per frame, a DoSteps / OnFrameMove / whatever you like method should be called to update all the data.
foreach( Device dev in this.mActionMap.Keys)
{
dev.Poll()
BufferedDataCollection coll = dev.GetBufferedDate();
if( coll == null )
continue;
Collections.Generic.Dictionary<> deviceActions = this.mActionMap[dev];
foreach ( BufferedData bdata in coll)
{
if( deviceActions.ContainsKey ( bdata.offset) )
{
// Action is mapped. Save it´s value. Axis will report integer (usually 0..65535) and
// buttons will report integer (0 unpressed, 128 pressed)
deviceActions[ bdata.offset ].mLastDataReaded = bdata.data;
}
}
}
User configuration of the Action Map
DirectX Action Mapping has it´s own user interface to configure the mapping. It´s dark, misterious, ugly, unconfortable, strange, a little bit chaotic, and as we are no longer using standard Action Mapping we can no longer use it. So, make your own config dialog, with the appearance you want, and the behaviour you want.
Now, with your custom action mapping, making a new assignment is as easy as changing Action.PhysicalDevice and Action.PhysicalObjectOffset properties.
Listening to a device
Most of the games makes a controller configuration based on "Listen for any device´s object moving". If that happens, object is assigned to game´s action.
In the config dialog, there will be a list of available game actions. When user selects one and press the "Assign" button, the application should stay for a while listening for devices. To do so:
1.- Define a Timer object in your configuration dialog, which is started when the user presses the "Assign" button.
2.- The timer will fire up every 100 ms or so. In the Timer_tick event, do the actual "listening" process:
2.0.- Increment a Time Counter. If it reaches the amount of time for listening, get out.
2.1.- Loop through every device
2.2.- Make device.GetBufferedData ()
2.3.- Assign first retrieved data to selected GameAction
In this algorithm you should also apply a Thresold system, because analog devices are almost always reporting small changes. So keep track of the first values returned in BufferedData for every physical object and when newer values come, calculate the difference between actual and first value. If the difference is bigger than Thresold, make the assignment.
Finally, reading data from the application
Once in your application, you can simple access the mActions collection to get the data. For example:
1.- Reading the Steering game action:
int wheelPosition = this.mInput.mActions[ eGameActions.Steering ].mLastDataReaded;
2.- Reading a button
if ( this.mInput.mActions [ eGameActions.MoveForward ].mLastDataReaded != 0 )
this.WalkForward();
That´s it ! Hope all this stuff helped you !
Feel free to email any questions.
Best regards,
Iñaki Ayucar
[Warning: This article is obsolote. I´d suggest you to read the Refreshed version here.]
Taking the issue where we left it yesterday...
The next step is to make an important choice: Inmediate or Buffered mode? Take into account that DX Action Mapping works in Buffered Mode only. Why? To answer that, let´s describe both modes a little bit:
Inmediate Mode
Take a look at how data is reported under the Inmediate Mode: all you get is a struct of the type JoystickState, MouseState or KeyboardState, which has all the data you need under some default fields, defined by DirectX (like AxisX, AxixY, etc). This fields are always the same, no matter which device you are accesing to. It´s device´s builder (i.e. Logitech) who decides what physical objects are mapped to what DX default fields. An example:
- For a joystick, it´s quite trivial to map it´s objects to fields, because JoystickState was originally designed for that: joysticks (as it´s name states). So, the AxisX field will almost always be mapped to the X-Axis of the joystick.
- What happens for a driving wheel? Ah! that´s different. That´s something DirectInput was not originally designed for, and when this kind of devices came out, instead of adapting DInput for them, DX guys decided to use existing structs to handle new devices. So, there´s no default field in the JoystickState structure for the WheelAxis object. In this way, some device builders will map wheel axis to AxisX, while others will do to the Rx Axis, and so on...
Buffered Mode
Are things different in Buffered Mode? Quite a bit.
In buffered mode, you don´t get access to the whole structure of data. Instead of that, you call the GetBufferedData() method, which retrieves a collection of BufferedData objects, one for each changing object in the device. That means, if the device is absolutely stall, no data will be returned.
One tip: To set the buffered mode, you have to manually change the property:
Device.Properties.BufferSize = 16.
Making the relationship
What we need is a way to save and recover from a file something like this:
and recover it as: DeviceGuid = new Guid( attributeGuidInnerText );
3.- The third attribute.... aaaahhh. This is a little bit more tricky.
more on this later...
[Warning: This article is obsolote. I´d suggest you to read the Refreshed version here.]
In the last few months I realized that I didn´t have a decent controller configuration system and I started to look at DirectInput´s Action Mapping.
After a couple of weeks working around it, my frustration growed up and I decided to make my own full ActionMapping system (supporting mapping to several devices). Why?
1.- The documentation of Microsoft about AcionMapping sucks (no offense!)
2.- ManagedDirectX version of Action Mapping seems to be incomplete, just like if they wanted to finish asap, regardless what was the final result: undocumented methods, parameters, etc.
3.- Error reports coming out from methods is something just, impossible to understand without good specs. One example: The Acquire() method always throws "Argument Exception". Why? It can be due to a dozen of things.
4.- To push me even forwarder in my willing of comitting suicide, most of the actionMapping samples have been removed from the SDK. As there´s no human able to find old samples in Microsoft´s websites, I had to dive into the pile of old CDs that, thank to the great lord, I haven´t thrown away yet. Hey you gus, why just not putting there a special website with all the DX samples published ever?
5.- Of course, after finding the samples, they are all C++, so you have to bring all your c++ knowledge to life and start studying them.
6.- Even after all this process, I honestly have to say that I couldn´t determine the behaviour of some methods. They seem to be chaotic and I dont understand what happens sometimes. I loose the action map when set the dataFormat and vice-versa.
7.- Another and last (I hope) reason. What´s all that stuff about pre-defined genres, device types and so on... ??? Who decided that? Did you really thought that it was gonna be enough with a bunch of pre-definded genres and types? To be even worse, they are all mixed up in the Managed DirectInput namespaces, with no order. If you don´t know what Im talking about, I´ll say that, in action mapping, you have to assign a Genre (like "driving", "Fighting", "SpaceSimulator", ...) to your ActionMap. Each genre has it´s own Default Actions, and no more. This default actions are what you map your game actions to, and basing on this mapping and on the genre of your Actionmap, methods of DInput may even crash. Goddamn it! And, what happens if you need more actions than the pre-defined? Yes, I know there´s something called "AnyButton", "AnyAxis" and so on... but... I just don´t like it. i.e., now, with the new Logitech G25... it comes with a clutch pedal. There´s no DX Action for the clutch in the Driving Genre. It´s quite clear that DX was designed in U.S ;)
8.- No! it was not the last reason, more coming to my mind. How standard ActionMapping saves and recovers actionMap configurations is something misterious. Actually, it saves a per-application file in ApplicationData\Microsoft\DirectInput special folder with all the settings. DirectInput loads and saves it "behind the scene", and that´s something I don´t like at all. Why not just putting there a "SaveActionMapToFile(), LoadActionMapFromFile()" methods instead ?. I want to be able to decide where the file will be saved and how! In addition to that, while development, it´s behaviour its very unconfortable, because when app´s guid changed, DirectInput generates additional files. You can easily end up with hundreds of config files in that folder if that happens. Microsoft recommends to manually delete the contents of that folder periodically while development. Arrgglll....
9.- More and more reasons... The Acion Mapping configuration dialog. What can I say? It´s almost un-customizable. It´s behaviour is absolutely undocumented. I really think that DirectInput is the ugly brother of DirectX. It does not follow the quality standard present in other parts of the libs.
Anyway... No more time wasted in ActionMapping. I decided to make my own, custom, brighty, magnificent, easy Action Mapping System. This could seem an easy task, but again, just like always I try to make something new with DirectInput, it´s not.
The main task we want to make in our ActionMapping is to be able to save to disk and recover a controller configuration, which assign a InputDevice and an Object of that device to a GameAction defined by us.
As this tutorial is gonna take long, I will divide it into parts.
PART 1: Define game actions
Will put all of our actions in an enumeration.
enum eGameActions
{
MoveForward = 0,
MoveBackward = 1,
TurnLeft = 2,
TurnRight = 3,
Shoot = 4,
NumberOfActions = 5 // Not an action, just to know the number
}
If you don´t know, there´s a very useful class in the .Net Framework called System.Enum. This class has static methods to loop through members of an enumeration and more. Things like:
Enum.GetNames ( typeof (eGameActions) ) : Will return a string[] with: "MoveForward", "MoveBackward" and so on.
Enum.IsDefined( typeof(eGameActions), 6 ): Will return false because eGameActions doesnt have that member.
more coming tomorrow ...
Cheers !