Setting up UI with multiplayer.

Hello all, new user to the Input System, have watched as much as I can on the subject, and also read up on the docs, but I must be missing something as I am trying to get the UI to work with my game.

I’m the lead programmer for my final university group project, so it is kinda important that I can get this done fast. Apologies for any massive images on here, the “thumbnail” process was incredibly tiny it seemed.

So for reference, I don’t want to use the built in “Player Input” or “Player Input Manager” systems, as they aren’t flexible enough, and have actually presented a few problems, so I’ve gone on and made my own script to handle player input. Very basically, I have one Input Asset, with a Player and UI action map (seen in pictures below).

For debugging purposes, I just have two spheres, both having the same “Input” script I wrote in there.
I’ll comment along the way what I’m doing too.

[SerializeField] private InputActionAsset _input; //Grab the Input Asset.
    [SerializeField] public int gamePadId; //Simple ID to get the two controllers I have plugged in
    [SerializeField] private Canvas canvas; //Canvas object to disable and enable the canvas on "start" click.

    private InputActionMap _actions; //Custom Action Map -> mainly for player movement
    private InputActionMap _UI; //Custom Action map -> for the UI
    private InputActionMap finalInputAction; //A tried and failed attempt to combine the action maps.

    private Vector2 dir; //My direction variable
    private bool gamePaused; //Pause variable

 
    private InputUser newIP; //New InputUser made to the world view so I can reference it in lower methods.

    void Awake()
    {

        InputActionAsset ac = Instantiate(_input); //Instantiate the Input Asset

       //Make a new InputActionMap for each Action Map.
        _actions = new InputActionMap();
        _UI = new InputActionMap();
        finalInputAction = new InputActionMap();

        _actions = ac.FindActionMap("Player"); //Grab the "Player" Map from the Input Asset
        _UI = ac.FindActionMap("UI"); //Grab the "UI" Map from the Input Asset

        //This was a tried and failed method to combine the Player and UI inputs.
        foreach (InputAction iA in _actions.actions)
        {
            finalInputAction.AddAction(iA.name, iA.type);
            Debug.Log(iA.name);
        }
        foreach (InputAction iA in _UI.actions)
        {
            finalInputAction.AddAction(iA.name, iA.type);
            Debug.Log(iA.name);
        }

       //Create and assign a new Input user with the Gamepad being the ID set in the field
        newIP = InputUser.PerformPairingWithDevice(Gamepad.all[gamePadId]);
        //This one works for player movement, but it will override UI and any other action maps.
        //newIP.AssociateActionsWithUser(_actions);
        newIP.AssociateActionsWithUser(finalInputAction); //Failed attempt to combine Player and UI maps.
      
        //Assigning the Input Actions so I can set them to a custom function
        InputAction movement = _actions.FindAction("Movement");
        InputAction pause = _actions.FindAction("Pause Menu");

       //Now just assigning the input actions lambda expressions and functions.
        movement.performed += ctx => dir = ctx.ReadValue<Vector2>();
        movement.canceled += ctx => dir = Vector2.zero;
        pause.performed += PauseMenu;

        canvas.enabled = false;

    }

    public void OnEnable()
    {
       //Enable all Action maps on startup
        _actions.Enable();
        _UI.Enable();
        finalInputAction.Enable();
    }

    public void OnDisable()
    {
       //Disable all Action maps on shutdown
        _actions.Disable();
        _UI.Disable();
        finalInputAction.Disable();
    }

    private void PauseMenu(InputAction.CallbackContext context)
    {
       //Simple pause script, sets timescale to zero, turns the gamePaused to true or false, and enables the canvas.
        if (!gamePaused)
        {

            Time.timeScale = 0f;
            gamePaused = true;
            Debug.Log("Paused");
            canvas.enabled = true;
            newIP.AssociateActionsWithUser(_UI);
        }
        else
        {
            Time.timeScale = 1f;
            gamePaused = false;
            Debug.Log("UnPaused");
            canvas.enabled = false;
            newIP.AssociateActionsWithUser(_actions);
        }
     //The AssociateActions seems to override any actions when I apply the new one, so I tried that, but it also just threw a bunch of errors.
    }

    private void OnMoveChange(InputAction.CallbackContext context)
    {
        dir = context.ReadValue<Vector2>();
    }

    void Update()
    {
        transform.position += new Vector3(dir.x, transform.position.y, dir.y) * 4.0f * Time.deltaTime;
    }

For reference, here are the errors:

Map index on trigger does not correspond to map index of trigger state
UnityEngine.InputSystem.LowLevel.<>c__DisplayClass7_0:<set_onUpdate>b__0(NativeInputUpdateType, NativeInputEventBuffer*)
UnityEngineInternal.Input.NativeInputSystem:NotifyUpdate(NativeInputUpdateType, IntPtr) (at C:/buildslave/unity/build/Modules/Input/Private/Input.cs:117)
Exception 'IndexOutOfRangeException' thrown from state change monitor 'InputActionState' on 'Stick:/XInputControllerWindows/leftStick'
UnityEngine.InputSystem.LowLevel.<>c__DisplayClass7_0:<set_onUpdate>b__0(NativeInputUpdateType, NativeInputEventBuffer*)
UnityEngineInternal.Input.NativeInputSystem:NotifyUpdate(NativeInputUpdateType, IntPtr) (at C:/buildslave/unity/build/Modules/Input/Private/Input.cs:117)
IndexOutOfRangeException: Index was outside the bounds of the array.
UnityEngine.InputSystem.InputActionState.ChangePhaseOfAction (UnityEngine.InputSystem.InputActionPhase newPhase, UnityEngine.InputSystem.InputActionState+TriggerState& trigger, UnityEngine.InputSystem.InputActionPhase phaseAfterPerformedOrCanceled) (at Library/PackageCache/com.unity.inputsystem@1.0.0-preview/InputSystem/Actions/InputActionState.cs:1530)
UnityEngine.InputSystem.InputActionState.ProcessDefaultInteraction (UnityEngine.InputSystem.InputActionState+TriggerState& trigger, System.Int32 actionIndex) (at Library/PackageCache/com.unity.inputsystem@1.0.0-preview/InputSystem/Actions/InputActionState.cs:1242)
UnityEngine.InputSystem.InputActionState.ProcessControlStateChange (System.Int32 mapIndex, System.Int32 controlIndex, System.Int32 bindingIndex, System.Double time, UnityEngine.InputSystem.LowLevel.InputEventPtr eventPtr) (at Library/PackageCache/com.unity.inputsystem@1.0.0-preview/InputSystem/Actions/InputActionState.cs:902)
UnityEngine.InputSystem.InputActionState.UnityEngine.InputSystem.LowLevel.IInputStateChangeMonitor.NotifyControlStateChanged (UnityEngine.InputSystem.InputControl control, System.Double time, UnityEngine.InputSystem.LowLevel.InputEventPtr eventPtr, System.Int64 mapControlAndBindingIndex) (at Library/PackageCache/com.unity.inputsystem@1.0.0-preview/InputSystem/Actions/InputActionState.cs:783)
UnityEngine.InputSystem.InputManager.FireStateChangeNotifications (System.Int32 deviceIndex, System.Double internalTime, UnityEngine.InputSystem.LowLevel.InputEvent* eventPtr) (at Library/PackageCache/com.unity.inputsystem@1.0.0-preview/InputSystem/InputManager.cs:2685)
UnityEngine.InputSystem.LowLevel.<>c__DisplayClass7_0:<set_onUpdate>b__0(NativeInputUpdateType, NativeInputEventBuffer*)
UnityEngineInternal.Input.NativeInputSystem:NotifyUpdate(NativeInputUpdateType, IntPtr) (at C:/buildslave/unity/build/Modules/Input/Private/Input.cs:117)
Should not get here
UnityEngine.InputSystem.LowLevel.<>c__DisplayClass7_0:<set_onUpdate>b__0(NativeInputUpdateType, NativeInputEventBuffer*)
UnityEngineInternal.Input.NativeInputSystem:NotifyUpdate(NativeInputUpdateType, IntPtr) (at C:/buildslave/unity/build/Modules/Input/Private/Input.cs:117)

Any insight or help at all would be greatly appreciated! I’ve already had nightmares trying to get the first stage (just basic player movement with separate controls) working.

Hey @JaronWilding , could you file a ticket with your setup through the Unity bug reporter so we can take a closer look? No matter the setup, even if it’s not right, it shouldn’t result in those exceptions. Especially not ones where it’s saying it shouldn’t get where it’s at :slight_smile:

Couple comments on the script.

  • Not sure what the intention/workaround with finalInputAction is. You’re already Instantiating the action asset and thus have a private copy and should be good to go.
  • Associating the actions with the user should be necessary only once. Then you should be able to just Enable and Disable maps as needed to switch context.
  • If the intention is to have one UI that can be operated by all players in parallel, you’ll need one EventSystem with one InputSystemUIInputAction module that references actions not belonging to any player.
  • If the intention is to have a separate UI for each player controlled by that player only, you will need one MultiplayerEventSystem for each player and one InputSystemUIInputAction module for each player which must reference the UI actions for just that player.
  • If the intention is to have UIs that can in part be controlled by multiple players and in part are private to players, we don’t have proper support for that scenario yet. Can probably be stitched together with a combination of the above two but probably will come out quite messy.
  • For the move action, rather than have a callback and store the value, you can alternatively just poll the value of the action directly in your Update method using ReadValue.

I’ve gone ahead and submitted it through the bug reporter, hopefully that helps out!

I’ve tried that, and I’m facing a really strange issue that I’ve attached as a small video. It seems that the one that is placed last, overwrites any actions placed, which is why I had the finalInputAction made, to try and combine both of them together to see if it would make any difference. But as the video will show you, it doesn’t.

4482t

If I try and disable\re-enable then if I forget to add the “pause” part back, I can’t actually unpause, and the UI actions won’t kick in, as they were overwritten by the Player actions. Also if I place the UI actions after the Player actions, if I try using the movement anyway, it’ll pair to both controllers, and actually dump a heap of actions in the Input Debugger, which I don’t know why they are doing so, they should be private and kept to that lone players input.

Currently, the intention is to be able to pause the game and use the UI for each player, I don’t mind if both players can access the UI, or if it’s separate (so if Player 1 pauses, Player 2 cannot unpause unless they have Player 1’s controller), although the latter is far more desirable, as this game is supposed to provoke and mess with each player, and to have both players be able to unpause would be adding an unfair element.

I’m also rather unsure of how to hook up the InputSystemUIInputAction to any of my actions that I associate with the players. How would I go about doing so?

That makes way more sense, I’ll do that instead then! I’m needing to try and optimize it as much as I can, as it can be a bit finicky already with some elements.

@Rene-Damm

You said:
“and one InputSystemUIInputAction module for each player which must reference the UI actions for just that player.

I have a situation where the player-controlled prefabs each have their own PlayerInput, MultiplayerEventSystem, and InputSystemUIInputModule. Their prefabs also contain their own Camera Screen Space Canvas, to represent each player’s individual HUD and some UI Selectables.

So far, so good, the problem I’ve just run into is that in any Selectable Navigation.Mode other than Explicit, players are able to navigate from a selectable on their own canvas to selectables on the other player’s canvas (using GamePad input, and the InputSystemUIInputModule “Move” method listening to Dpad.

I feel like somehow I’ve failed to do this part you said: “reference the UI actions for just that player.”, however I’ve tried both public linking them in the Prefab to their own parent/child structure, AND in script just finding the components in children to make the specific instance reference, but neither makes any difference in behavior.

I think I am having the same issue. The input from one player will bleed into the canvas of the other player. I have the same setup. Prefab with it’s own camera, canvas, MultiplayerEventSystem and InputSystemUIInputModule. The mouse / keyboard seems limited to one player, however, using the joystick on one screen I can move over to the other player’s screen and select objects there.

How do you reference UI actions for each player if there is only 1 asset / instance?

EDIT: I have made sure the player root is set correctly. Shouldn’t that prevent that? Since the player that is bound to the controller shouldn’t be selecting game objects that belong to the other player?