Search Unity

Local multiplayer with new input system

Discussion in 'Input System' started by dziemo, Dec 2, 2019.

  1. dziemo

    dziemo

    Joined:
    Aug 13, 2016
    Posts:
    7
    Hello :)
    I can't get my head around handling local players. I want to have at least 2 scenes:
    1st scene with player selection
    2nd scene with actual gameplay
    How do I handle joining players with Player Input Manager manually?
    How do I migrate players between scenes with new input system?
     
  2. Forberg

    Forberg

    Joined:
    Oct 27, 2018
    Posts:
    25
    Get a reference to the PlayerInputManager and call
    Code (CSharp):
    1. playerInputManager.JoinPlayer(....)
    If you have set the PlayerInputManager component to "SendMessages" in the Editor it will call a function on all components on the same game object:
    Code (CSharp):
    1.  public void OnPlayerJoined(PlayerInput playerInput) { }
    The received PlayerInput instance is a MonoBehaviour on a newly spawned game object. So you can do
    Code (CSharp):
    1. DontDestroyOnLoad(playerInput.gameObject);
    Hope that helps!
     
    TubbyStubby and albrechtjess like this.
  3. dziemo

    dziemo

    Joined:
    Aug 13, 2016
    Posts:
    7
    Thanks for your response! Sorry for not answering but unfortunately I can't test it right now :-( I also hope it helps :)
     
  4. dziemo

    dziemo

    Joined:
    Aug 13, 2016
    Posts:
    7
    Don't want to leave this without response so I found some time to test this.
    If I call
    playerInputManager.JoinPlayer(....)

    then it still needs a prefab set in PlayerInputManager
    I will just Instantiate a prefab with PlayerInput and Notification Behaviour set to BroadcastMessage so I can just swap it's children objects and change PlayerInput's mapping to suit situation (lobby, gameplay).
     
  5. DeathBySafety

    DeathBySafety

    Joined:
    May 9, 2015
    Posts:
    1
    Thanks! I'm gonna use this solution as well :)
     
  6. Ploxo

    Ploxo

    Joined:
    Mar 8, 2019
    Posts:
    1
    I'm trying to do a similiar thing, what are you supposed to write in the parentheses when you get a reference to the PlayerInputManager, i don't really understand how you make it work, where are you supposed to write all the code? On a script for the spawner, or a script for the PlayerInputManager?
     
  7. albrechtjess

    albrechtjess

    Joined:
    Oct 11, 2017
    Posts:
    11
    I have been looking for this answer everywhere, thank you so much <3 I think this will help me get started on what I'm trying to do.
     
  8. albrechtjess

    albrechtjess

    Joined:
    Oct 11, 2017
    Posts:
    11
    You'd in theory write that in some sort of game manager object script and use that to assign devices and controls to players. I found the info for that call here https://docs.unity3d.com/Packages/c...m_String_UnityEngine_InputSystem_InputDevice_

    I'd recommend not doing it manually though as it looks like you'll need to select the device you want to pair it and other things when you can have the new input manager help you with that and use the sendMessages to help do things with individual devices though it depends on what you're trying to do I suppose.
     
  9. Holonet

    Holonet

    Joined:
    Aug 14, 2017
    Posts:
    84
    To expand on this (hope I'm not derailing, I think this is on topic), I'm confused about the Prefab that it expects, mentioned above as well. I've seen a tutorial or 2 on it, and sure, it seems that you plug in the game object, and when players join, it can spawn another of those game objects...but what if you don't want your players to have the same game object? How do you have the API manage this when not all the players are the same thing?
     
  10. alextrudeau97

    alextrudeau97

    Joined:
    Oct 6, 2019
    Posts:
    1
    Yeah I really don't like that the prefab field is mandatory with the "Join on Action" option. I'm not sure what the "best practice" of working with the PlayerInputManager is but my way of working with it was i created a "Player UI controller" prefab and assigned it to PlayerInputManager (set to Join on Action Triggered).
    The players join the game with an action from my game's main menu and the UI controller instance created by PlayerInputManager is assigned to a UI element on the screen, and the players select their character.
    I then cached the player selections and on every subsequent scene load my PlayerInputManager was set to Join Manually, and I instantiated player choices at the position of a "player spawn" game object. Join Manually doesn't require a prefab because it expects you to instantiate from a script.
     
    GrayedFox and albrechtjess like this.
  11. Holonet

    Holonet

    Joined:
    Aug 14, 2017
    Posts:
    84
    Thanks, that was part of what I've been wrapping my head around. In my case, I'm joining manually, but it took me a day to figure out how, but it's buried in the docs under the join method. Apparently, for joining "manually," a player is considered to have joined after a PlayerInput object has been created. For me, I had already painfully created a UI where the players could move their cursors over their player, etc... and select, so I wanted to utilize that and then join manually. I'm still fighting with it, but I have "some" idea of what I'm doing, maybe this info will help others as well. :cool:
     
    GrayedFox and albrechtjess like this.
  12. ProceduralCatMaker

    ProceduralCatMaker

    Joined:
    Mar 9, 2020
    Posts:
    108
    Wonder if you could share your solution. I also have a similar issue of spawning two different Players, each with its Inputs mapped. Helpeful to save time to many people, as usually characters are different. Thanks!
     
    Last edited: May 7, 2020
  13. Holonet

    Holonet

    Joined:
    Aug 14, 2017
    Posts:
    84
    Sorry, didn't see this post... Well, I'll post what I have for one of my selection screens. It "works" insofar as I had multiple controllers, and each would control their respective icon on the selection screen, but I hadn't gotten to the point of actually persisting that controller relationship to the actual gameplay. Looking at some of the samples with the new input system, I'm actually thinking of scrapping that and trying to do it "right" with the PlayerInput and the gamepad mouse cursor (gamepad mouse cursor sample scene in the input manager, you can download that)

    But I hadn't gotten to a "solution" or anything just yet, and I was referring to just beginning to understand the new system. That said, I'm fine posting one of the scripts here if it does pose any interest to anyone. Please note, I don't take full credit for this, the basic cursor behavior and idea of overriding the PointerInputModule is something I got from a post on here somewhere years ago, which I have no idea as to the location of :p.

    Basically, what I'm doing is that, plus putting the actual game object that the player will use behind/child of a UI/canvas image. It works, but I want to incorporate PlayerInput, now that it's there, because I never really felt comfortable wrapping my head around the notion of keeping the player # associated with the game object, and it just gets awfully confusing. Anyhoo, script:

    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections.Generic;
    3. using UnityEngine.EventSystems;
    4. using UnityEngine.InputSystem;
    5. using UnityEngine.SceneManagement;
    6.  
    7. // Attach this script on your canvas's EventSystem game object
    8. // It glitches the StandaloneInputModule if both are active at the same time
    9.  
    10. public class CarSelectionScript : PointerInputModule
    11. {
    12.  
    13.     // Use this for initialization
    14.     void Start()
    15.     {
    16.  
    17.         // Disable the car controls so the cars don't move while trying to select them! (Re-enable afterwards)
    18.         foreach (var car in GameObject.FindGameObjectsWithTag("Car"))
    19.         {
    20.             var physicsCarObject = car.GetComponent<Rigidbody>();
    21.             physicsCarObject.constraints = RigidbodyConstraints.FreezeAll;
    22.         }
    23.  
    24.  
    25.         for (int i = 0; i < PersistentManagerScript.Instance.cursorsActive.Count; i++)
    26.         {
    27.             // Set cursors all to false initially in case selection screen is revisted w/ different # of players
    28.             PersistentManagerScript.Instance.cursorsActive[i] = false;
    29.         }
    30.            
    31.        
    32.        
    33.         for (int i = 0; i < PersistentManagerScript.Instance.numberOfPlayers; i++)
    34.         {
    35.             // However many players are selected, activate their cursors
    36.             // Associate with player by index (+1)
    37.             PersistentManagerScript.Instance.cursorsActive[i] = true;
    38.  
    39.            
    40.         }
    41.  
    42.  
    43.         // (PointerInputModule)
    44.         base.Start();
    45.  
    46.         if (cursorObjects == null || eventSystem == null)
    47.         {
    48.             Debug.LogError("Set the game objects in the cursor module.");
    49.             GameObject.Destroy(gameObject);
    50.         }
    51.  
    52.         // This is only necessary if players already selected cars, but came back to the screen to pick again.
    53.         // We need to destroy the player car game objects so they can be created afresh
    54.         var destroyMe = Resources.FindObjectsOfTypeAll<GameObject>();
    55.  
    56.         foreach (var item in destroyMe)
    57.         {
    58.             if (item.name.Contains("Player") && item.name.Contains("Car"))
    59.                 Destroy(item);
    60.         }
    61.  
    62.        
    63.  
    64.         // INPUT EVENTS
    65.         PersistentManagerScript.Instance.controls.Generic.start.performed += OnStartButton;
    66.        
    67.     }
    68.  
    69.     private void OnDestroy()
    70.     {
    71.         base.OnDestroy();
    72.         PersistentManagerScript.Instance.controls.Generic.start.performed -= OnStartButton;
    73.  
    74.         // Re-enable car control scripts
    75.         foreach (var car in GameObject.FindGameObjectsWithTag("Car"))
    76.         {
    77.             var physicsCarObject = car.GetComponent<Rigidbody>();
    78.             physicsCarObject.constraints = RigidbodyConstraints.None;
    79.  
    80.             // Re-enable the constraints we do want in game
    81.             physicsCarObject.constraints = RigidbodyConstraints.FreezeRotationX;
    82.             physicsCarObject.constraints = RigidbodyConstraints.FreezeRotationZ;
    83.             physicsCarObject.constraints = RigidbodyConstraints.FreezePositionY;
    84.         }
    85.     }
    86.  
    87.  
    88.  
    89.  
    90.     private void OnStartButton(InputAction.CallbackContext ctx)
    91.     {
    92.         // Check that all players have selected a car :)
    93.         for (var i = 0; i < PersistentManagerScript.Instance.numberOfPlayers; i++)
    94.         {
    95.             if (PersistentManagerScript.Instance.PlayerCarsAssigned[i] == false)
    96.                 return;
    97.         }
    98.  
    99.         SceneManager.LoadScene("CharacterSelection");
    100.     }
    101.  
    102.  
    103.  
    104.    
    105.  
    106.  
    107.     // The same event system used on the Canvas
    108.     [SerializeField] EventSystem eventSystem;
    109.  
    110.     // A list of cursor objects inside the canvas
    111.     // It moves only on X and Y axis
    112.     [SerializeField] List<GameObject> cursorObjects;
    113.  
    114.     private Vector2 auxVec2;
    115.     private PointerEventData pointer;
    116.  
    117.  
    118.     // Process is called once per tick
    119.     public override void Process()
    120.     {
    121.  
    122.  
    123.         // For each player                                  // Hopefully joysticks too!!
    124.         for (int i = 0; i < PersistentManagerScript.Instance.PlayerObjects.Count; i++)
    125.         {
    126.             // Get the player # according to the InputSystem API
    127.             var playerIndex = PersistentManagerScript.Instance.PlayerObjects[i].GetComponent<PlayerInput>().playerIndex;
    128.  
    129.             // Getting objects related to player (i+1)
    130.             GameObject cursorObject = cursorObjects[playerIndex];
    131.  
    132.             // Add step to make player selection cursor visible *******
    133.             if (!cursorObject.GetComponentInChildren<Renderer>().enabled)
    134.                 cursorObject.GetComponentInChildren<Renderer>().enabled = true;
    135.  
    136.  
    137.  
    138.             GetPointerData(playerIndex, out pointer, true);
    139.        
    140.  
    141.             // Converting the 3D-coords to Screen-coords
    142.             // This gets the position of the cursor OBJECT
    143.             Vector3 screenPos = Camera.main.WorldToScreenPoint(cursorObject.transform.position);
    144.  
    145.  
    146.             auxVec2.x = screenPos.x;
    147.             auxVec2.y = screenPos.y;
    148.  
    149.             // This sets the pointer itself (PointerEventData) to the position of the OBJECT we have above :)
    150.             pointer.position = auxVec2;
    151.             // Raycasting
    152.             eventSystem.RaycastAll(pointer, this.m_RaycastResultCache);
    153.             RaycastResult raycastResult = FindFirstRaycast(this.m_RaycastResultCache);
    154.             pointer.pointerCurrentRaycast = raycastResult;
    155.             this.ProcessMove(pointer);
    156.  
    157.  
    158.             pointer.clickCount = 0;
    159.  
    160.  
    161.         // Cursor click - adapt for detect input for player (i+1) only
    162.         // if (Input.GetButtonDown("Boost_P0" + (i+1).ToString()))
    163.  
    164.  
    165.         if (Gamepad.all[playerIndex].buttonSouth.isPressed)
    166.         {
    167.                 Debug.Log(Gamepad.all[playerIndex] + " buttonSouth pressed! " + playerIndex);
    168.  
    169.                 pointer.pressPosition = auxVec2;
    170.                 pointer.clickTime = Time.unscaledTime;
    171.                 pointer.pointerPressRaycast = raycastResult;
    172.  
    173.                 pointer.clickCount = 1;
    174.                 pointer.eligibleForClick = true;
    175.  
    176.                
    177.  
    178.                 // If the player pressed the confirm button over a car
    179.                 if (this.m_RaycastResultCache.Count > 0 && PersistentManagerScript.Instance.cursorsActive[i])
    180.                 {
    181.                     Debug.Log("Player " + (playerIndex + 1) + " selected a car!");
    182.                     pointer.selectedObject = raycastResult.gameObject;
    183.                     pointer.pointerPress = ExecuteEvents.ExecuteHierarchy(raycastResult.gameObject, pointer, ExecuteEvents.submitHandler);
    184.                     pointer.rawPointerPress = raycastResult.gameObject;
    185.  
    186.                     // DEBUGGING
    187.                     // Debug.Log("The game object selected is " + pointer.selectedObject.transform.GetChild(0).gameObject);
    188.                     // Debug.Log("Raycast count is " + m_RaycastResultCache.Count);
    189.  
    190.                     // Lock the player's cursor after selection
    191.                     if (pointer.selectedObject.transform.GetChild(0).gameObject.tag == "Car")
    192.                     {
    193.                         Debug.Log(pointer.selectedObject.transform.GetChild(0).gameObject);
    194.                         // PersistentManagerScript.PlayerCars[i] = pointer.selectedObject.transform.GetChild(0).gameObject;  
    195.                         PersistentManagerScript.Instance.PlayerCarsAssigned[playerIndex] = true;
    196.  
    197.                         // Actually create new Game OBJECT, not object within list
    198.                         var currentCar = Instantiate(pointer.selectedObject.transform.GetChild(0).gameObject);
    199.  
    200.                         // var currentCar = pointer.selectedObject.transform.GetChild(0).gameObject;
    201.  
    202.                         // Assign the selected car to the newly created GameObject
    203.                         currentCar.name = "Player" + (playerIndex + 1).ToString() + "Car";
    204.                        
    205.                         // Make invisible/inactive until we place it/spawn it in the arena
    206.                         currentCar.SetActive(false);
    207.                        
    208.                         DontDestroyOnLoad(currentCar);
    209.  
    210.                         // Add this car as a property to the PlayerObject in the dictionary
    211.                         if (PersistentManagerScript.Instance.PlayerObjects[playerIndex] != null)
    212.                         {
    213.                             var playerObject = PersistentManagerScript.Instance.PlayerObjects[playerIndex];
    214.                             // Move the PlayerInput, etc... object to the same spot so the child (the car) doesn't move/disappear from the menu
    215.                             playerObject.transform.position = currentCar.transform.position;
    216.  
    217.                             // Make the car the parent so it can be found by GameObject.Find later (camera tracking)
    218.                             playerObject.transform.parent = currentCar.transform;
    219.  
    220.                         }
    221.                         // Set taunt according to character here (Character as prefab--get taunt component?)
    222.  
    223.                         PersistentManagerScript.Instance.cursorsActive[playerIndex] = false;  //  Freeze cursor
    224.                     }
    225.                 }
    226.                 else
    227.                 {
    228.                     pointer.selectedObject = null;
    229.                     pointer.pointerPress = null;
    230.                     pointer.rawPointerPress = null;
    231.                 }
    232.             } // End of "if the confirm (south) button was pressed"
    233.  
    234.  
    235.             // Add cancel button condition to deselect car and free up pointer motion
    236.             if (Gamepad.all[playerIndex].buttonWest.isPressed)
    237.             {
    238.                 PersistentManagerScript.Instance.PlayerCarsAssigned[playerIndex] = false;  //  Unassign previously selected car
    239.                 PersistentManagerScript.Instance.cursorsActive[playerIndex] = true;   //  Free up the player's cursor again
    240.  
    241.                 // This garbage is necessary because the simple GameObject.Find method does not return inactive objects!
    242.                 var destroyMe = Resources.FindObjectsOfTypeAll<GameObject>();
    243.  
    244.                 foreach(var item in destroyMe)
    245.                 {
    246.                     if (item.name == "Player" + (playerIndex + 1).ToString() + "Car")
    247.                         Destroy(item);
    248.                 }
    249.             }
    250.  
    251.  
    252.  
    253.             else
    254.             {
    255.                 pointer.clickCount = 0;
    256.                 pointer.eligibleForClick = false;
    257.                 pointer.pointerPress = null;
    258.                 pointer.rawPointerPress = null;
    259.             }
    260.         } // for( int i; i < cursorObjects.Count; i++ )
    261.  
    262.     }
    263. }
    264.  
    Inside the override for "Process()" and the for loop inside there is the crux of the cursor objects.
     
    ProceduralCatMaker likes this.
  14. ProceduralCatMaker

    ProceduralCatMaker

    Joined:
    Mar 9, 2020
    Posts:
    108
    Thanks, I am also following in the forum the Player Input component thread https://forum.unity.com/threads/player-input-component.855130/ that also has some suggestions on "doing it right".
    I will study also your code. Thanks for contributing.
     
  15. deeprest

    deeprest

    Joined:
    Jun 8, 2016
    Posts:
    17
    I didn't want to use SendMessage(), UnityEvents, or have PlayerInputManager spawn prefabs.
    There is a way "roll your own" using tips from Rene-Damm in the other local multiplayer thread.
    Hope someone else finds it useful. There's a link to the source there, too.
    NOTE: I was not able to test with multiple gamepads (I only have one), and did not include device connectivity callbacks. I'm sure PlayerInput provides a lot of useful stuff that's missing from my code... but for a simple game it worked for me.
     
    coatline and NotaNaN like this.
  16. Holonet

    Holonet

    Joined:
    Aug 14, 2017
    Posts:
    84
    NotaNaN and ProceduralCatMaker like this.
  17. BenjBot42

    BenjBot42

    Joined:
    Nov 10, 2020
    Posts:
    2
    Hi, I know this forum is rather old, and there seems to be solutions to these problems, but I still don't quite understand how this all works. My situation is very similar to dziemo's initial problem. I want to have a character select screen that proceeds into gameplay on another scene.
    The main issue, however, is that I can't figure out how to save separate players' choices. Basically, I have it so that the player will choose a character in a scene, and it stores that chosen character as an int using a "playerPref", which in this case, I named "selectedCharacter". Once the gameplay scene loads, the player's car script references the selectedCharacter, and activates one of many child objects and deactivates the rest (those child objects are just the different character models). Fortunately, this works for one player, but I can't seem to differentiate between multiple players. I'm really stuck, and I haven't worked with the new input system very much, so any help would be greatly, greatly appreciated. I'm right on the verge of understanding how this works, but I can't seem to get it working for my split-screen situation. Can someone help me?
     
  18. Holonet

    Holonet

    Joined:
    Aug 14, 2017
    Posts:
    84
    I'm not sure, especially with no code to look at, but it sounds like you're just storing an arbitrary relationship between the player # and your game objects. It doesn't sound like you're doing anything to handle the controllers.

    In the post just before yours, I linked to a demo project that does exactly this. Also, recently, in this thread:
    https://forum.unity.com/threads/local-multiplayer-keyboard-tirannie.1489306/#post-9300236
    I posted the relevant part of the code that persists the relationship to the controllers between scenes.
     
  19. BenjBot42

    BenjBot42

    Joined:
    Nov 10, 2020
    Posts:
    2
    Okay, thank you! I think this is enough info to kick start me in the right direction. I appreciate your help!
     
    Holonet likes this.