How to use multiple controllers in local multiplayer game?

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!

2 Likes

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.

2 Likes

o.O, that what is missing form me the InputUser.ActivateControlScheme()!! i wil create some extensions to make this process more easly for me.

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.

1 Like

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.

1 Like

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.

I found the problem. PlayerInput only activates the first Action Map.

Thanks for giving it a go :slight_smile:

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.

SendMessage("SwitchActions", "Driving");
1 Like

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.

1 Like

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).

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.

1 Like

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!

I tried to replace :

foreach (var controlScheme in m_Actions.controlSchemes)
{
if (TryToActivateControlScheme(controlScheme))
               break;
}

with

InputControlScheme? controlScheme = InputControlScheme.FindControlSchemeForControl(s_InitPairWithDevices[0], m_Actions.controlSchemes);
if (controlScheme.HasValue)
               TryToActivateControlScheme(controlScheme.Value);

I now have the good controlScheme assigned, but I can’t join with a second player :cry:

And found it again ^^

The above lines (PlayerInput.cs, line 726) work with “Use reference” set to false.

1 Like

Thanks for the update @Rene-Damm !

I saw a copy/paste mistake in
PlayerInputEditor, OnInspectorGUI(), l.105

Replaced :
m_SelectedDefaultControlScheme = selected;
With :
m_SelectedDefaultActionMap = selected;

Following your conventions, i added a SchemeFromControl functionality :
PlayerInput.cs, l.523

Added :
[SerializeField] internal bool m_SchemeFromControl;

PlayerInput.cs, AssignUserAndDevices(), l.761

Added :

else if (m_SchemeFromControl)
{
    InputControlScheme? controlScheme = InputControlScheme.FindControlSchemeForControl(s_InitPairWithDevices[0], m_Actions.controlSchemes);
    if (controlScheme.HasValue)
        TryToActivateControlScheme(controlScheme.Value);
}

PlayerInputEditor.cs, l.521

Added :
[NonSerialized] private readonly GUIContent m_SchemeFromControlText = EditorGUIUtility.TrTextContent("Get Control Scheme From Control");

PlayerInputEditor, l.529

Added :
[NonSerialized] private bool m_SchemeFromControl;

PlayerInputEditor, OnInspectorGUI(), l.65

Replaced :

// Default control scheme picker.
var selected = EditorGUILayout.Popup(m_DefaultControlSchemeText, m_SelectedDefaultControlScheme,
m_ControlSchemeOptions);
if (selected != m_SelectedDefaultControlScheme)
{
    var defaultControlSchemeProperty = serializedObject.FindProperty("m_DefaultControlScheme");
    if (selected == 0)
    {
        defaultControlSchemeProperty.stringValue = null;
    }
    else
    {
        defaultControlSchemeProperty.stringValue =
            m_ControlSchemeOptions[selected].text;
    }
    m_SelectedDefaultControlScheme = selected;
}

With :

bool toggled = EditorGUILayout.Toggle(m_SchemeFromControlText, m_SchemeFromControl);
if (toggled != m_SchemeFromControl)
{
    SerializedProperty schemeFromControlProperty = serializedObject.FindProperty("m_SchemeFromControl");
    schemeFromControlProperty.boolValue = m_SchemeFromControl = toggled;
}
if (!toggled)
{
    // Default control scheme picker.
    var selected = EditorGUILayout.Popup(m_DefaultControlSchemeText, m_SelectedDefaultControlScheme,
    m_ControlSchemeOptions);
    if (selected != m_SelectedDefaultControlScheme)
    {
        var defaultControlSchemeProperty = serializedObject.FindProperty("m_DefaultControlScheme");
        if (selected == 0)
        {
            defaultControlSchemeProperty.stringValue = null;
        }
        else
        {
            defaultControlSchemeProperty.stringValue =
                m_ControlSchemeOptions[selected].text;
        }
        m_SelectedDefaultControlScheme = selected;
    }
}

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 :
PlayerInput.cs, OnEnable(), l.903

Added :
m_Actions = Instantiate(m_Actions);

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?

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.

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.

1 Like

it’s funny that this is exactly my problem right now!

1 Like

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.

1 Like