2 players, 1 keyboard. Custom PlayerInputManager to handle sharing a device with multiple players

PlayerInputManager doesn’t support setups with 2 players on a single keyboard. I tried to setup my own class to do this that would replace PlayerInputManager. I was unable to get it to work. It allows only one player to join. Afterwards, it stops listening on that device. Any idea why?

Here’s a scene showing the issue TestShareDevice.zip (10.7 KB)

Here’s the class most likely causing the trouble:

using System.Collections.Generic;
using UnityEngine;
using UnityEngine.InputSystem;

/// <summary>
/// <see cref="PlayerInputManager"/> doesn't support multiple players on a single device. This class replaces <see cref="PlayerInputManager"/> specializing in multiple players on a single device.
/// </summary>
public class TestShareDevice : MonoBehaviour
{
    [Header("BROKEN - this attempts:\n" +
            "A demonstration of multiple players on a single\n" +
            "keyboard device. Use either ctrl left, ctrl right,\n" +
            "spacebar, or numpad enter to jump a cube.\n")]

    [Tooltip("Button to press for a player to join.")]
    [SerializeField]
    private InputActionReference ActionToJoin;

    [SerializeField]
    private GameObject PlayerPrefab;

    /// <summary> What control schemes have already joined. They're not allowed to join again. </summary>
    private List<string> schemesJoined = new List<string>();

    private void OnEnable()
    {
        ActionToJoin.action.performed += OnAction_performed;
        ActionToJoin.action.Enable();
    }

    private void OnDisable()
    {
        ActionToJoin.action.Disable();
        ActionToJoin.action.performed -= OnAction_performed;
    }

    /// <summary> Similiar to the <see cref="PlayerInputManager"/> function that raises OnPlayerJoined </summary>
    private void OnAction_performed(InputAction.CallbackContext ctx)
    {
        // Get control that triggered from the context.
        var control = ctx.control;

        // "binding.group" contains the name of the scheme. ref https://discussions.unity.com/t/any-way-to-get-the-control-scheme-for-an-input-through-inputaction-callbackcontext/811484/5
        var scheme = ctx.action.GetBindingForControl(control).Value.groups;

        // player already joined
        if (schemesJoined.Contains(scheme))
        {
            Debug.Log(scheme + " already added " + Time.frameCount);
            return;
        }

        // join player
        else
        {
            Debug.Log(scheme + " being added " + Time.frameCount);
            schemesJoined.Add(scheme);
            var playerInput = PlayerInput.Instantiate(PlayerPrefab, controlScheme: scheme, pairWithDevice: control.device);
            playerInput.gameObject.name = "Player" + playerInput.playerIndex + playerInput.currentControlScheme;
        }
    }
}

After some struggle, I have multiple players able to use the same keyboard working in a UnityEngine.InputSystem aligned in a way to support rebinding and players dynamically joining and dropping.

Since this thread has been so quiet I imagine few know how to make this work. Bad news is I got laid off earlier this year and am hopeful for extra ramen. So I decided to put it on the asset store in hopes I’ll get 1 purchase. If someone buys it, feel free to share its contents in this thread. It’s submitted at https://u3d.as/3pzb . Teaser is at https://youtu.be/543NxuraolA . Unity estimates it’ll take a month for the initial validation. So hopefully you’re not viewing this during https://jam.CrazyGames.com which will be in progress in 1 week.

Hi @ShawnFeatherly,
Yes the current design of control schemes has a granularity level where bindings may be associated with a scheme bound to device types (a.k.a. device families), and currently do not support defining a “playable set of controls” to a scheme to be assigned to a PlayerInput instance. It looks like you ran into this limitation, as I suspect the first PlayerInput instance would be assigned the keyboard which would put the second player in a situation where it cannot be a assigned a unique device.

Since sharing a device typically implies a low number of concurrent players, maybe a feasible workaround is having a fixed set of bindings assigned to each player? Of course other creators could have a look at your extension as well. Happy to see you decided to fill the feature gap here with your own solution and make it possible for others to benefit from it! I will also take a note for the team to consider this potential feature gap in the future since I think you brought up a valid scenario for a classic shared device local multiplayer game.

Thanks @unity_hakan for explaining a bit why the code in my original post wasn’t working. Greatly appreciated!

Do hope other creators can make use of my extension! It does retain the original PlayerInput component. I don’t entirely understand why its method gets PlayerInput to accept uniqueness to include control scheme instead of only device. Yet, I’ve tested it quite a bit. Was happy it works well for both rebinding and rejoining.

Sorry about this last spam to conclude the thread, 2 players, 1 keyboard package is now live on the asset store.