Monday, June 30, 2014

Unity3d - Input Mapper (Games,Joystick,Keyboards,Gamepads,Mouse)

 Unity Input Manager pain is lasting for years. On Unity site you can find request on the forum for InputManager programmatic access dating from 2004, and some new from 2014. Futile.


Its good they are planning to :). Now see the date 2009 of feedback, then see your computer date. Calculate difference in years. Ultimate Game Development with state of the art InputManager system :D.

Problems:

Input handling is beast overall taken. There difference in how diffrent OSes handle input. Even same controller is attached meaning identical hardware and communication protocol different OSes would handle differently cos of at least byte order. Then difference come from different drivers used in different OS so for example OSX recognize XBOX360 Wireless controller POV as 4 buttons and WIN as 2 Axes. In same OS you can have multiply default handling ways like Winmm and DInput,XInput in Windows. Even in same OS but different version and same handling way could have differences(ex. Winmm doesn't recognize sliders/triggers in WIN7 and have no problem in WIN8). On top all that come problems with Unity mono threading when add/remove device notification and input are async interrupt based.



1) Editor and Ingame input controller mapping.
   Unity Input Class works only in Play mode so you are left to blindly mapping inside Input Manager tree view. Changing mapping later need game restart.

2) Handling input in code based on states/tag abstracting real input.
Some sort of abstraction is done thru InputManager well known "Horizontal" and "Vertical", but that abstraction is still bonded to axes and buttons and not to actions/states of the game(one of those might be AnimateStates of Mecanima).

3) Saving and restoring user preferences
PlayerPref might do the trick if filesize is not bigger then 1 megabyte. You can use .asset or also load InputManager.asset during runtime and save but might involve extra efforts when WebPlayer or Droid is target.
File type that can be readable by humans and machines, and easy exchangeable( players can exchange their game settings) between them might  be XML.

4) Recognize positive and negative part of the axis.
Unity recognize Axis as whole part and gives value 1 <--- (-1) ---> 1.  It is expected to gives value from 1<---- (0) ----> -1, so  turning left/right on wheel input controller or joystick push/pull  forward/backward can be achieved, trigger  shooting rapid fire... .

5) OS independent driver and easy expansion with drivers supporting other devices and special properties
Unity internal input handler might not recognize the controller or it will identify layout in different system differently and would not offer support of device extra features like force feedback, IR pointer, accelerators, gyros ...of modern input controllers. So instead of use pluggin-paid OS dependent drivers, seem much better OS dependent HID interfaces with OS independent pluggable drivers.

6) Handling input axis and buttons as digital or analog
In Unity thru Input class you can handle axes only as analog and buttons as analog/digital. Its handy to have axes as digital events UP, DOWN , HOLD...

7) Create combination of inputs that would trigger action/state
Unity doesn't offer out of the box combining input actions like 2 keys in row, button press and axis move, ....like in fighting game scenario 2 times joystick left + fire (Mustafa kick Cadillacs and Dinosaurs)

8) Handling inputs by events
Unity is planned as single threaded getting out of the way of  multi-threading headache. Complex input handling require input events as Update function got overcrowded. This involve multi-threading and need extra attention towards synchronization with Unity main thread.


9) Plug and play instead of plug and pray.
Attach or remove controllers while game is running and continue playing. Somekind of add/remove notification can be done with tracking of number of joystickNames thru Input.GetJoystickNames() but when 2 or more joysticks are of same type that way having same names distinction isn't possible. Also order of names in list is might change after reattaching same device for ex. previous Joy1 become Joy4  (hack *). I noticed that some controllers(XBOX wireless under OSX) are hard bind to Joystick# no matter position in GetJoystickNames list.




10) Profiles - Layouts
Show user friendly appearance of mapped input like Left Stick X, Bumper, Forward, Triangle, Square,....instead of Joystick1Button15, Joystick3Axis12... Switch profiles by assigning different controller to different player.




*Entries for every Joystick#+Buttons# (Joystick1Button1,...Joystick2Button2... inside InputManager multiplied by factor for variation with keys and settings.




Input Mapper system try to address above issues.







1) Ingame input controller mapping.
Input Mapper allows you easily to map game controller input to custom states.





2) Handling input in code based on states/tag abstracting real input.
InputMapper API looks very similar to Unity API's, but they offer abstraction on 2 levels. First you not program with real inputs like KeyCode.Q or Joystick1Button99, but with states which also allows player to map different input to same action state.
 


//atOnce (default=false)
//Function returns true when combination pressed in sequence  If set to true function return true 
//when all keys/buttons in combination are pressed.Modifier like behaviour (LeftCtrl(-)+B)
bool bCombo = (InputManager.GetInput((int)States.Jump,InputPlayer.Player.Player0,atOnce
));


You are handling input of State.Jump, no matter what Player have mapped to that input, no matter is KeyCode.Q or Joystick1Button99 or something else.

3) Saving and restoring user preferences
Saving would export your settings to .xml or .bin  file and States.cs will be generated containing enum of your mapped states.

public enum States:int{
         Wave=1397315813,
         MyCustomState=-1624475888,

Now you can forget about:
//  static int idleState = Animator.StringToHash("Base Layer.Idle"); 
//  static int locoState = Animator.StringToHash("Base Layer.Locomotion");  
as we are thought in Unity tutorials, but you can use States.[some state].


4) Recognize positive and negative part of the axis.
System recognize Forward,Backward,Left,Right of POV axis and Positive/Negative of normal axes.
This is useful when you are using your joy stick as button.


 

  InputCode.Joystick2AxisPositive.SINGLE
     
  InputCode.Joystick2AxisNegative.LONG



Intent is states not to be addressed as string as in above example given just for clarification. Use Enums!

5) OS independent driver and easy expansion with drivers supporting other devices and special properties
I understood Unity couldn't support all game devices but some system that allows simple binding of specific driver might be good idea. Why? All AAA Games provide list of top controllers used and tested with their game and they would work out of the box. The owner of rest controllers might relay on manufacturer or OS.

 (Yeah they have pluggins.....).  
So instead building pluggins for all OSes, you use InputMapper system built-in mini HID interface systems for (Win,Web,Droid and OSX)  and OS independent driver. (Yes you can make C driver cross compile to anything...but....discussion need lot of effort).

If specific driver is found for your device it would be handled by it:

 
//supporting devices with custom drivers
   InputManager.AddDriver(new XInputDriver());

If there is no specific driver for device default driver (WinMMDriver for Win and OSXDriver for OSX) would try to handle device.

You can use UnityDriver as default driver having default Unity handling functionality, and still handle specific devices with specific drivers.

 InputManager.hidInterface.defaultDriver=new UnityDriver());//

Swap your InputManger.asset with InputManager.asset from github source code.


Warring:Unity doesn't make distinction between triggers/axis/poitOfView and doesn't make controller distinction as multiply instances of same type have same name and can hard code index of devices no matter position in GetJoystickNames list.

Specific driver implementation guidelines:

Your implementation of custom device driver had 2 entry points that need implementation:
 
 (1)public IJoystickDevice ResolveDevice(IHIDDeviceInfo info)...

 (2)public void Update(IJoystickDevice joystick)...

ResolveDevice where HIDInterface provide device info from the OS, you can check VID and PID to decide to handle and init device properties and structures, or not returning null, and Update function to query device by use of provided Read/Write methods methods and fill the JoystickDevice structures, so can be accessed by InputManager. Scare about handling few bytes??? :)

Originally OS implementation of ResolveDevice point is by using Report HID Page from device description, but that restrict using devices that aren't planned to work with that OS, like Wiimote,Xbox Gamepad...
In Input Mapper you need simply to extend JoystickDevice Class, which already contains some basic functionality supporting axes and buttons, by adding specialized properties of device you are planning to support. 

Update point is synchronization point with Unity. System use high level synch and async ReadXXX methods and return buffered byte array. What each of bytes means is easy to decipher as there are many web sites about any devices. Developer task is to fill JoystickDevice  structure with values processed from byte array returned.
Write methods are used for setup the device...like light leds, start FFD, enable disable....



Devices used during testing: XBox360W controller, ThrustMaster Wheel FFD, Wiimote + Nunchuk.
One classic Gamepad controller, one Wheel and complex controller.

6) Handling input axis and buttons as digital or analog
Second abstraction is that you can use digital or analog output no matter your real input source is,  meaning for ex. Joystick Axis is analog source giving input from 0.0 to 1.0, but can be tracked as digital true/false, or mouse button which is digital would return float values 0.0 to 1.0 while hold .
     //DIGITAL
    if (InputManager.GetInputDown((int)States.Wave,Player,true))
        {
            animator.Play((int)States.Wave);
        }

        if (InputManager.GetInputDown((int)States.Jump))
        {
            animator.Play((int)States.Jump);
        }


        //ANALOG
        //
        float forward = Math.Abs(InputManager.GetInput((int)States.WalkForward,Player, 0.25f))
            - Math.Abs(InputManager.GetInput((int)States.WalkBackward,Player, 0.25f));

        

        animator.SetFloat(forwardHash, forward);
        //
        //
        float turn = Math.Abs(InputManager.GetInput((int)States.TurnRight,Player, 0.25f))
            - Math.Abs(InputManager.GetInput((int)States.TurnLeft,Player, 0.25f));
       
        animator.SetFloat(turnHash, turn);



        



7) Create combination of inputs that would trigger action/state
So you not need to open that nasty InputManager Tree component. Connect your test controller to your computer. Open InputMapper Editor in Window->Editor .
Map inputs to state by just clicking keys/mouse/buttons or moving joystick as SINGLE, DOUBLE and LONG and make combination of inputs(Combos). Primary and secondary. In bellow example I've map to Wave state combo of Mouse0 double clicks + Joystick1AxisYNegative (Long pull backward of the Joystick) + double click of letter Y.


You can set modifier format or clicks sensitivity. 



Select Player and then profile to start mapping states to inputs. When controller is attached, its profile would appear in profile list under "default". You can use "Clone default" to clone states from default to controller specific settings. Then you can change mappings. You can also clone Player to other players and add difference changes.

8) Handling inputs by events
As Update method get overcrowded, library offer modern input handling solution by use of Event based system.
  

     InputManager.addEventListener((int)States.Wave).UP += onUp;
        InputManager.addEventListener((int)States.Wave).DOWN += onDown;


    void onUp(object o, EventArgs args)
    {
        Debug.Log("Up");
    }

    void onDown(object o, EventArgs args)
    {
        Debug.Log("Down");
    }

or use editor:






Hardcode developers can manually map input to states and even mix with loaded settings. This is useful mainly for testing.
 



     InputManager.loadSettings(Path.Combine(Application.streamingAssetsPath,"InputSettings.xml"));


      InputManager.MapStateToInput ("WalkForward", InputCode.W.SINGLE);
      InputManager.MapStateToInput ("WalkForward", 1, Mouse1.SINGLE);//Secondary
    //InputManager.MapStateToInput ("WalkForward", InputCode.JoystickAxisPositive.SINGLE);


      InputManager.MapStateToInput ("WalkBackward", InputCode.S.SINGLE);
      InputManager.MapStateToInput ("WalkBackward", 1, InputCode.Mouse0.SINGLE);//Secondary
    //InputManager.MapStateToInput ("WalkBackward", InputCode.JoystickAxisNegative.SINGLE);

   //map full axis (useful for analog input)
   InputManager.MapStateToInput ("ManualFullAxisMap", InputCode.Joystick0AxisX);
    
   //adding input-states pairs manually
   InputManager.MapStateToInput("Click_W_x2_C_State", Input.W.DOUBLE,InputCode.C.SINGLE);


   InputManager.MapStateToInput("My State1",new InputCombination("Mouse1+Joystick12AxisXPositive(x2)+B"));

   
In InputCode all KeyCode stuff are supported plus additional for Joystick Axis mapping.





9) Plug and play instead of plug and pray.





10) Profiles:
Create profiles(layouts) for your device by just click button or move axis of connected device.


Profiles are stored in DeviceProfiles.asset.




User Interface:
Drag and drop InputComponent to empty GameObject.









User can choose what controller will assign to which player.
When user attach controller, its layout with predefined settings would be loaded . Instead seeing "Joystick4Button10" you will see "Y","A","B"....








Code is free if you contribute by solving bug or enhance something !!! Joking :) Who can stop you/us.

Feedback, Forks are welcome if you aren't too smart, too movie star or too busy making money.
Gmail me as winxalex.

Knowledge should be free. 

19.08.14 Thrustmaster wheel RGT FFD demo WIN+DROID



13.07.2014 Added WiimoteDevice and WiimoteDriver Skeleton
22.07.2014 Quite more stability and plug in/out supported added.
26.07.2014 Web Player joystick support (Chrome, FireFox)




01.10.2014 WiiDevice and WiiDriver 
05.10.2014 XInput driver pure C# (No DirectX xinput.dll wrappers)
13.10.2014 (OSXDriver default driver pure C#)
17.10.2014 (Thrustmaster Wheel FFD and XBOX360W working on OSX) 
CONCEPT PROVED!!!

18.10.14 Added much simplified sample scenes then previous hard core sample

Plans: 1)Make some kind of pluggin structure so you put ur .dll driver in same folder and get reflected in runtime
2) Adding accept time between 2 inputs

24.12.2014 HappyHolidays. In past 2 weeks I was working day/night to cure child diseases of InputMapper. Tons of bugs were smacked, many sections are heavily tested, optimized, synced or redone and new features like mapping axis as whole or as from any device easily were introduced.

10.01.2015 v1.2 Announced.  Profiles, multiplayer and big redesign of the system.

Issues:
InputMapper might crash Unity Win????
Generation of "States.cs" doesn't work in OSX???


26 comments:

  1. Hi Alex, thank you very much for working on a better input manager. I have just downloaded your sample. I can open it on OSX with Unity 4.5.5. I have no WII controller connected and get a crash when I hit play in the Unity editor. Do you have any ideas?

    ReplyDelete
  2. Game.cs is bit more of the test entry and its bit overcrowded with different build targets diff drivers and handling code,...so I made couple of simple example scenes...
    https://github.com/winalex/Unity3d-InputMapper/tree/DEV_1/Assets/ExampleScenes

    ReplyDelete
  3. I downloaded your source code and I was not able to compile. A lot of errors are on the class Animator. Please specify your version of the Unity!

    ReplyDelete
  4. Downloaded and got:
    Assets/Scripts/ws/winx/examples/MyJoystickDevice.cs(15,29): error CS0115: `ws.winx.MyJoystickDevice.GetInput()' is marked as an override but no suitable method found to override
    Why would this be happening?

    ReplyDelete
  5. Please download latest github Dev branch. ws.winx.examples folder/namespace is removed

    ReplyDelete
  6. I've just downloaded and got this:
    Assets/Scripts/ws/winx/input/components/InputComponent.cs(43,18): error CS0246: The type or namespace name `FormerlySerializedAs' could not be found. Are you missing a using directive or an assembly reference?

    What should I do?

    ReplyDelete
  7. [FormerlySerializedAs ("onLoad"), UnityEngine.SerializeField] <--- this is very you have problem
    What Unity Version u have. Code is now updated to U5. Just replace with [UnityEngine.SerializeField] remove Formeer... Should solve ur problem.

    ReplyDelete
  8. Good day! Is your code actual to Unity5?I mean:does Unity solve this proplems or not?Thank you!

    ReplyDelete
  9. Above is my solution. Unity roadmap doesn't say that will/would tickle the issue in near future.

    ReplyDelete
  10. Hello winxalex

    I am currectly working on my bacheor thesis. Now I didn't know your Unity3D InputMapper before. But I found a way to get input data from a WiiMote (with MotionPlus and a Nunchuck) into a Unitygame on Andoid.
    Using the input from the buttons and the Nunchuck-Thumbstick is easy. But now I try to interpret the data I get from the gyro-sensor (often reffered to as accel). I'm using the Linux xwiimote driver and I get three int values ranging from 100 to -100 from the gyro-sensor. These values are called accel_abs (accelerometer_absolut) in the driver. There are also raw values but this seem to be only two ints. I dont really know how to work with its data because everything I try only seams to define two axis of movement.

    Now first I would be really gratefull if you cloud tell me what data you did get from the WiiMote to make rotation possible as seen in https://www.youtube.com/watch?v=tEwMcA2ZaMk
    What kind of data did you use to caculate the full rotation of the wiimote and how did you use/process it? Sadly I cloud not fine the apropriate part of the c# script in the GitHub Project...
    So please help me out if you can

    Kindest Regards, and thanks for your massive coding effort to write the InputMapper. I will try It as soon as I can

    ReplyDelete
  11. Massive coding effort doesn't have massive feedback. :) :(. I was stupid spending year or folks are damn. Or both. Admit that you didn't search at all in github :D. You wanted to popup by itself :).
    about driver modified by me https://github.com/winalex/Unity3d-InputMapper/blob/master/Assets/Scripts/ws/winx/drivers/WiiDriver.cs
    data then are filed in WiiMoteDevice and then you need to process. Raw value are counts -32k to 32k. How many counts goes by degree is depends of sensor.
    You are getting acceleration data and gyro data. You need to fuse them. https://github.com/winalex/Unity3d-InputMapper/blob/8ebdad5c54c05acc7d33f72173db098ab68a7fea/Assets/Scripts/ws/winx/input/components/TestInputManager.cs by something(lot of better methods u can find online). Hope it helps.
    https://github.com/winalex/Unity3d-InputMapper/blob/master/Assets/Scripts/ws/winx/utils/ComplementaryFilter.cs

    ReplyDelete
  12. So first - Thanks again for your instant response.

    To be honest, I did search GitHub, but after reading two .cs files that I didn't really understand I did hope that It would become more clear if I asked. Now first I will download your code and I will try to understand what the classes you wrote there really do ;-) Thanks for pointing out wich clases are mainly involved in processing the WiiMote Gyro and Accel data.

    Kindest Regards, Lucas

    PS:
    I'm sad to hear that your code hasn't really brought you the gloy it deserves. Did you try to bring to put it in the Unity Asset Store (maybe even for a few bucks - you deserve them well)? People are ways more likely to use som scripts from the asset store...

    ReplyDelete
  13. Hello! I´m working in a old game made in my old computer with Unity 4.3, There´s a possibility to use your code with that version?
    I really need to launch this game, I can´t update Unity, I tried once but my game crash a lot.
    I don´t find old version of your amazing wrapper in the github.
    Help!!!

    ReplyDelete
  14. I started dev, think in U4.6. Then AnimationAPI were bit different then U5 and I've custom InputEvent vs U5's UnityEngine.UnityEvents. Both aren't essential so with little effort you can remove them and adjust to 4.3 I hope.
    Just keyboard is wrapped other aren't. ;)

    ReplyDelete
  15. How are you even mean to install this? The Asset Store version is old, you can't clone this in the plugins folder since it's a full Unity project, and if I use the attached package (which is a year old), there are a bunch of syntax errors.

    ReplyDelete
  16. https://github.com/winalex/Unity3d-InputMapper need Unity 5.0 or greater. Although I would not update on Asset Store I'll help you with any issue you have.
    Asset store sucks and won't submit 100megs cos of one line change or photo and wait 2 weeks to be approved, until they have source version control like SVN<GIT and auto compiling like Jenkins...:)

    ReplyDelete
  17. This comment has been removed by the author.

    ReplyDelete
  18. This is amazing, thank you for sharing with the community!

    ReplyDelete
  19. Hi folks, I'm trying to use this plugin to control the forcefeedback of a Thrustmaster T500RS but the buttons of Rumble And Stop motor doesn't appear... It's like the plugin were not detecting the wheel. Any suggestions?

    Thanks in advance

    ReplyDelete
  20. This looks great. Is it possible to set controls or actions to only work with one specific controller? I have a flight stick and a steering wheel attached and I don't want them to drive with the flight stick or fly with the steering wheel. Can I either control the input based upon the controller, or can I poll the list of devices, find the number for the Steering wheel, then set it so that only input from that JoystickNumber will be accepted?

    ReplyDelete
  21. How to get the value (or define) when no button is selected?

    ReplyDelete
  22. Interesting. Nothing out of the box as I remember but you can do something with
    https://docs.unity3d.com/ScriptReference/Input-anyKey.html so if the condition is done you return some value.

    ReplyDelete