Search Unity

How to use multiple controllers in local multiplayer game?

Discussion in 'Input System' started by Dragondean, Jan 22, 2019.

  1. Dragondean

    Dragondean

    Joined:
    Jun 14, 2017
    Posts:
    2
    I know there have been multiple posts on this before but hear me out...

    I have recently gotten this system. And I am making a local multiplayer game. So I need to incorporate multiple controllers into this game. All the threads I have seen on this have said that yes you can do it but I still have no idea how. I was wondering if I could look at the code to see how to do this. Also, another post said that I need to get the device but every time I try to return the name of the device it logs only 'System.String[]'. Can anyone help? Thanks!
     
    Luminefarious and AgentFox101 like this.
  2. Rene-Damm

    Rene-Damm

    Joined:
    Sep 15, 2012
    Posts:
    1,779
    We're just about to wrap up work on a new set of components that will make this significantly easier. As soon as the functionality is generally usable (should be within a week or so), there will be a video showing how to quickly set up local multiplayer using input actions. Following that, we will put some work into the documentation in this area.

    If you want to dive straight in instead and not wait, here's a couple pointers about the general setup. The components will obsolete having to deal with pretty much any of this stuff, though. All of their functionality is implemented on top of this, though.
    • InputUser is there to handle user-device pairings.
    • To pair a user to a device, call InputUser.PerformPairingWithDevice().
    • To give a user actions, call InputUser.AssociateActionsWithUser().
    • Note that each user will have to have a separate set of actions. Use the generated MakePrivateCopyOfActions() or Instantiate(...) to duplicate your actions for each player.
    • To listen for player joins, use InputUser.listenForUnpairedDeviceActivity and InputUser.onUnpairedDeviceUsed.
    • To activate control schemes, use InputUser.ActivateControlScheme(). To automatically pair missing devices, call AndPairRemainingDevices() on the result.
    Requires coming up with some script logic on top of all of this. The components will do handle all that stuff for you.
     
    AgentFox101 and Lurking-Ninja like this.
  3. GilbertoBitt

    GilbertoBitt

    Joined:
    May 27, 2013
    Posts:
    111
    o_O, that what is missing form me the InputUser.ActivateControlScheme()!! i wil create some extensions to make this process more easly for me.
     
  4. Rene-Damm

    Rene-Damm

    Joined:
    Sep 15, 2012
    Posts:
    1,779
    Sorry to leave you hanging there in the other thread.

    For lack of proper documentation, one thing that might be useful is looking at the tests for InputUser. They go through all the various uses. Also, this here is the new component that wraps InputUser. Could also be useful to look at.
     
    GilbertoBitt likes this.
  5. GilbertoBitt

    GilbertoBitt

    Joined:
    May 27, 2013
    Posts:
    111
    don't worry i know that sometimes we have so much to do and we can't answer or chat, and also sometime there are similar thread all over forum about the same thing, that why i always keep navigating on threads about the same things!.

    i notice about the tests that's where i got to find ways to user the InputUser! thank's man! i will check others tests and now thank's to u i can start making my amazing game.
     
    Rene-Damm likes this.
  6. Djayp

    Djayp

    Joined:
    Feb 16, 2015
    Posts:
    114
    Hello ! I just tried PlayerInput/PlayerInputManager.

    PlayerInput.OnActionTriggered(InputAction.CallbackContext context) isn't called (so it doesn't send any message). Thanks for the code anyway, it's pretty helpful.
     
  7. Djayp

    Djayp

    Joined:
    Feb 16, 2015
    Posts:
    114
    I found the problem. PlayerInput only activates the first Action Map.
     
  8. Rene-Damm

    Rene-Damm

    Joined:
    Sep 15, 2012
    Posts:
    1,779
    Thanks for giving it a go :)

    The PlayerInput stuff definitely still needs work and there's several pieces that are unfinished as is.

    For managing multiple action maps, I was thinking of adding a dropdown to the UI that allows selecting the action map to enable by default (including "None" as an option to enable nothing by default). And then to have several methods that control what's active and that can be invoked by sending messages, for example.

    Code (CSharp):
    1. SendMessage("SwitchActions", "Driving");
     
    Djayp likes this.
  9. Djayp

    Djayp

    Joined:
    Feb 16, 2015
    Posts:
    114
    An enum flag ?

    Just found another problem. Pairing an XBox One controller also pairs the keyboard and the mouse. I'd put a checkbox because this is what we want in solo games.
     
    GilbertoBitt likes this.
  10. Rene-Damm

    Rene-Damm

    Joined:
    Sep 15, 2012
    Posts:
    1,779
    Just direct selection by name. It'd read out the available actions and you'd select one or "None".

    This won't be necessary. One thing that is on the list is automatic control scheme switching. The backend already supports the functionality, just needs to be wired up in PlayerInput. With this enabled, when in single player, the player can freely switch between all control schemes that devices are available for.

    But you'd still have the bindings only in one control scheme. Just that when the player is currently playing with, say, the "Keyboard&Mouse" control scheme and the system notices that there's user interaction on the gamepad, it automatically switches the player to the other control scheme. And it'll be fully observable so you can e.g. update UI hints and such to reflect the currently used device(s).
     
  11. Djayp

    Djayp

    Joined:
    Feb 16, 2015
    Posts:
    114
    I just don't understand how to assign one scheme only because the last scheme take them both when a player join for the moment.

    ex :
    I have a M&K (first) and a Gamepad (last) scheme, default to none. When I press the gamepad start, I have no device left. Keyboard then gamepad goes fine.
     
    Last edited: Jan 26, 2019
    GilbertoBitt likes this.
  12. GilbertoBitt

    GilbertoBitt

    Joined:
    May 27, 2013
    Posts:
    111
    i have the same issue! but only when Join behavior is set to user action to "Join Player When Join Action Is Trigger" on the others mode of Join Behavior it work like a charm! it only attach the control scheme i'm using when using the Join action selected!
     
  13. Djayp

    Djayp

    Joined:
    Feb 16, 2015
    Posts:
    114
    I tried to replace :
    Code (CSharp):
    1. foreach (var controlScheme in m_Actions.controlSchemes)
    2. {
    3. if (TryToActivateControlScheme(controlScheme))
    4.                break;
    5. }
    with
    Code (CSharp):
    1. InputControlScheme? controlScheme = InputControlScheme.FindControlSchemeForControl(s_InitPairWithDevices[0], m_Actions.controlSchemes);
    2. if (controlScheme.HasValue)
    3.                TryToActivateControlScheme(controlScheme.Value);
    I now have the good controlScheme assigned, but I can't join with a second player :'(
     
  14. Djayp

    Djayp

    Joined:
    Feb 16, 2015
    Posts:
    114
    And found it again ^^

    The above lines (PlayerInput.cs, line 726) work with "Use reference" set to false.
     
    GilbertoBitt likes this.
  15. Djayp

    Djayp

    Joined:
    Feb 16, 2015
    Posts:
    114
    Thanks for the update @Rene-Damm !

    I saw a copy/paste mistake in
    Replaced :
    m_SelectedDefaultControlScheme = selected;

    With :
    m_SelectedDefaultActionMap = selected;

    Following your conventions, i added a SchemeFromControl functionality :
    Added :
    [SerializeField] internal bool m_SchemeFromControl;
    Added :
    Code (CSharp):
    1. else if (m_SchemeFromControl)
    2. {
    3.     InputControlScheme? controlScheme = InputControlScheme.FindControlSchemeForControl(s_InitPairWithDevices[0], m_Actions.controlSchemes);
    4.     if (controlScheme.HasValue)
    5.         TryToActivateControlScheme(controlScheme.Value);
    6. }
    Added :
    [NonSerialized] private readonly GUIContent m_SchemeFromControlText = EditorGUIUtility.TrTextContent("Get Control Scheme From Control");
    Added :
    [NonSerialized] private bool m_SchemeFromControl;
    Replaced :
    Code (CSharp):
    1. // Default control scheme picker.
    2. var selected = EditorGUILayout.Popup(m_DefaultControlSchemeText, m_SelectedDefaultControlScheme,
    3. m_ControlSchemeOptions);
    4. if (selected != m_SelectedDefaultControlScheme)
    5. {
    6.     var defaultControlSchemeProperty = serializedObject.FindProperty("m_DefaultControlScheme");
    7.     if (selected == 0)
    8.     {
    9.         defaultControlSchemeProperty.stringValue = null;
    10.     }
    11.     else
    12.     {
    13.         defaultControlSchemeProperty.stringValue =
    14.             m_ControlSchemeOptions[selected].text;
    15.     }
    16.     m_SelectedDefaultControlScheme = selected;
    17. }
    With :
    Code (CSharp):
    1. bool toggled = EditorGUILayout.Toggle(m_SchemeFromControlText, m_SchemeFromControl);
    2. if (toggled != m_SchemeFromControl)
    3. {
    4.     SerializedProperty schemeFromControlProperty = serializedObject.FindProperty("m_SchemeFromControl");
    5.     schemeFromControlProperty.boolValue = m_SchemeFromControl = toggled;
    6. }
    7. if (!toggled)
    8. {
    9.     // Default control scheme picker.
    10.     var selected = EditorGUILayout.Popup(m_DefaultControlSchemeText, m_SelectedDefaultControlScheme,
    11.     m_ControlSchemeOptions);
    12.     if (selected != m_SelectedDefaultControlScheme)
    13.     {
    14.         var defaultControlSchemeProperty = serializedObject.FindProperty("m_DefaultControlScheme");
    15.         if (selected == 0)
    16.         {
    17.             defaultControlSchemeProperty.stringValue = null;
    18.         }
    19.         else
    20.         {
    21.             defaultControlSchemeProperty.stringValue =
    22.                 m_ControlSchemeOptions[selected].text;
    23.         }
    24.         m_SelectedDefaultControlScheme = selected;
    25.     }
    26. }

    It works fine except I have to disconnect a device to get messages from inputs. Do you have a clue ?

    EDIT : I don't have messages from the device used to join. Using Enter key to join for the Keyboard/Mouse control scheme, I have messages from the mouse. Disconnect any device (even a Gamepad) make it works as intended.

    EDIT2 : To make it works with Use Reference set to true, I added the following :
    Added :
    m_Actions = Instantiate(m_Actions);
     
    Last edited: Jan 30, 2019
  16. Rene-Damm

    Rene-Damm

    Joined:
    Sep 15, 2012
    Posts:
    1,779
    Good catch. Thanks for the fix. Pushed.

    Will take a look. Technically, this behavior (finding a control scheme based on a control) is taken are of by PlayerInputManager but when using PlayerInput by itself, probably makes sense to have it be able to infer control schemes from just the devices it is given.

    That is curious. Not seeing that. Will have a dig.

    Just to make sure, this is with 2018.3, right?

    Hmm, that shouldn't be necessary. PlayerInput already duplicates actions as needed. What was the problem you were seeing?
     
  17. Djayp

    Djayp

    Joined:
    Feb 16, 2015
    Posts:
    114
    I'm working on 2018.3.2f1 on InputSystem develop branch.

    With original scripts from dev branch, when I "join player when action is triggered" :

    - with Use reference set to true :
    > pressing start from Keyboard adds the Keyboard and the first control scheme (Gamepad) instead of Keyboard/Mouse control scheme so I can't join with Gamepad and Mouse is unbounded
    > pressing start from the Gamepad only adds the Gamepad but now I can't join with the Keyboard

    - with Use reference set to false :
    > pressing start from Keyboard adds the Keyboard and the first control scheme (Gamepad) instead of Keyboard/Mouse control scheme so I can't join with Gamepad and Mouse is unbounded
    > pressing start from the Gamepad adds The Gamepad and I can join with Keyboard (every control scheme is ok).

    In every case, I don't have any message from the device used to join before some device has been disconnected or connected.

    What I did with SchemeFromControl binds schemes as intended. Instantiate the clone before InitializeActions solved the issue with Use reference. I still don't understand why the first paired device doesn't send any message.
     
    Last edited: Jan 30, 2019
  18. Djayp

    Djayp

    Joined:
    Feb 16, 2015
    Posts:
    114
    I've been able to do so, but since the asset is in PlayerInput, I would have changed all the Instantiate/Initialize logic and didn't want to go so far.
     
    GilbertoBitt likes this.
  19. GilbertoBitt

    GilbertoBitt

    Joined:
    May 27, 2013
    Posts:
    111
    it's funny that this is exactly my problem right now!
     
    Djayp likes this.
  20. Djayp

    Djayp

    Joined:
    Feb 16, 2015
    Posts:
    114
    So... I tried everything from scratch.

    I just did what I said here to get the schemes right. The real problem with Use reference is it unbinds Gamepad's Start when using K/M Start to join. Maybe I could deal with it without
    m_Actions = Instantiate(m_Actions);
    before InitializeActions.
     
    GilbertoBitt likes this.
  21. Djayp

    Djayp

    Joined:
    Feb 16, 2015
    Posts:
    114
    About this "Use reference" problem, it seems like since Player 1 and the Input Reference both use the original asset, instanciating Player 1 hides the other schemes. I just removed this check in InitializeActions so it always Instantiate :

    Code (CSharp):
    1.             for (var i = 0; i < s_AllActivePlayersCount; ++i)
    2.                 if (s_AllActivePlayers[i].m_Actions == m_Actions && s_AllActivePlayers[i] != this)
    3.                 {
    4.                     m_Actions = Instantiate(m_Actions);
    5.                     break;
    6.                 }
    replaced by
    m_Actions = Instantiate(m_Actions);


    I still have to disconnect or connect a controller to get the messages from already paired devices.
     
    Last edited: Jan 31, 2019
  22. GilbertoBitt

    GilbertoBitt

    Joined:
    May 27, 2013
    Posts:
    111
    when i get home i will try to after joining set player to ActivateControlScheme i know is some activate stuff becouse we keep needing disconnect and reconnect the controller and when we connect the controller the function activate something to make the binging to device/listen to device. maybe enableit again?
     
  23. Rene-Damm

    Rene-Damm

    Joined:
    Sep 15, 2012
    Posts:
    1,779
    There was indeed a problem with PlayerInput grabbing devices and control schemes in combinations that didn't make sense. I pushed a change that hopefully fixes this.

    What would happen is that you'd join on a keyboard, it'd pair the keyboard, and then it try the gamepad scheme and call AndPairMissingDevices(). And that method would just go and happily pair a gamepad and think all is good. This shouldn't happen anymore.

    Overall, I think both InputUser and PlayerInput need some additional refinement over what pairings they go for.

    Device loss and regain needs more work and there's chunks of implementation in PlayerInput missing ATM to handle it properly. InputUser is equipped to detect loss of device and recover from it, but it depends on some logic for device recreation that has seen little testing and will likely need refinement.

    When this stuff is finished, PlayerInput should be able to cope with device disconnect and reconnects (e.g. on battery loss) out of the box and also allow the game to help out in the process (e.g. bringing up a "Press A to Continue" box that then gives the player the device that A was pressed on).
     
    Djayp and GilbertoBitt like this.
  24. GilbertoBitt

    GilbertoBitt

    Joined:
    May 27, 2013
    Posts:
    111
    the last commit really fixes the problem of initial pairing now no matter what device we press first it pair the correct one and only the one used. but the problem about the Xbox controller(gamepad) needing to be disconnect and reconnect to work still happening, since you fixed the first problem we probably will continue trying to figure it out how to fix the other one.
    the thanks @Rene-Damm !


    EDIT1:
    also, the problem when use reference is enabled I'm unable to join more than one Player!
    no matter the order M/K or XboxController, the first to enter is the only one that will enter.

    EDIT2:
    right now my workaroun is to use "InputSystem.RemoveDevice(device);" and then after use
    "InputSystem.AddDevice(device);" to make it work, trying to understand what happen when players join! and by the way this don't happen on keyboard/Mouse just on xbox controller(Gamepad) i think this will happen to any other controller as well or is something only on XInputControllerWindow, maybe latter i will try reset the state block of Input to default after joining to see if it make it work.
     
    Last edited: Feb 1, 2019
  25. Djayp

    Djayp

    Joined:
    Feb 16, 2015
    Posts:
    114
    Got it !

    (Updated since 0.2.6) ̶I̶n̶ ̶J̶o̶i̶n̶P̶l̶a̶y̶e̶r̶F̶r̶o̶m̶A̶c̶t̶i̶o̶n̶I̶f̶N̶o̶t̶A̶l̶r̶e̶a̶d̶y̶J̶o̶i̶n̶e̶d̶ ̶I̶ ̶h̶a̶d̶ ̶t̶o̶ ̶D̶i̶s̶a̶b̶l̶e̶/̶E̶n̶a̶b̶l̶e̶ ̶j̶o̶i̶n̶i̶n̶g̶.̶

    It doesn't fix the "use reference" problem, so I still have to Instantiate an asset for the first player. To sum up :
     
    Last edited: Apr 10, 2019
    GilbertoBitt likes this.
  26. GilbertoBitt

    GilbertoBitt

    Joined:
    May 27, 2013
    Posts:
    111
    it work like a charm now!
     
    Djayp likes this.
  27. Djayp

    Djayp

    Joined:
    Feb 16, 2015
    Posts:
    114
    GilbertoBitt likes this.
  28. GilbertoBitt

    GilbertoBitt

    Joined:
    May 27, 2013
    Posts:
    111
    i'm still trying to make it work on UI!
     
  29. Djayp

    Djayp

    Joined:
    Feb 16, 2015
    Posts:
    114
    Hello ! An update to say it works much better now (preview 0.2.6) !

    We don't need to Disable/Enable joining, but we still need to Instantiate m_Actions for every player. Updated here. (Don't forget to set a default action map or enabling it manually)
     
  30. reinfeldx

    reinfeldx

    Joined:
    Nov 23, 2013
    Posts:
    164
    Has this how-to video been released? If so, where can I find it?
     
  31. Rene-Damm

    Rene-Damm

    Joined:
    Sep 15, 2012
    Posts:
    1,779
    Unfortunately not yet. The crappy video I recorded some time ago is still the only video available at this point. PlayerInput still has a couple outstanding issues and multiplayer UI is still to be worked on.
     
  32. DrDoofenshmirtz

    DrDoofenshmirtz

    Joined:
    Aug 23, 2018
    Posts:
    4
    Has anything new happened since april?
    It would be really cool to be able to support multiple controllers.
     
  33. Deleted User

    Deleted User

    Guest

    From what I can tell, the only difference is that PlayerInput is now included in the default Input System package, but I don't know if it was there from the start.
     
  34. Djayp

    Djayp

    Joined:
    Feb 16, 2015
    Posts:
    114
    I'm not working on multiple controllers ATM but it seemed to be good enough during the first tests using the 0.2.9. Also, I didn't try with UnityEvents but logs seems to say it's fixed.
     
  35. scourgey

    scourgey

    Joined:
    Jan 5, 2014
    Posts:
    20
    Rene-Damm, I was following your video I can see there's an option for Continuous,
    upload_2019-8-29_23-14-41.png
    However in the latest Player input system I downloaded this option no longer appears, and when I press on the keyboard it doesn't continously provide values. How could I replicate the behaviour in the video

    upload_2019-8-29_23-15-13.png

    Many Thanks!
     
  36. Rene-Damm

    Rene-Damm

    Joined:
    Sep 15, 2012
    Posts:
    1,779
    Easiest way is to poll the action in your Update/FixedUpdate method.

    Code (CSharp):
    1. void Update()
    2. {
    3.     var move = moveAction.ReadValue<float>();
    4.     //...
    5. }
    6.  
     
  37. scourgey

    scourgey

    Joined:
    Jan 5, 2014
    Posts:
    20
    Thanks that worked, used:
    Code (CSharp):
    1. PlayerInput.actions["Move"].ReadValue<Vector2>();