Search Unity

Switching between several player objects

Discussion in 'Connected Games' started by M0rtimer, Jun 15, 2015.

  1. M0rtimer

    M0rtimer

    Joined:
    Mar 19, 2014
    Posts:
    1
    I am having a bit of a problem with player objects. Am I right in thining that there can only be one player object (PO) per player?
    If so, I have trouble understanding how to set a player's PO. There are two functions on NetworkServer, AddPlayerForConnection and ReplacePlayerForConnection. Both of them insists on spawning a new gameobject. What if I want player to take control of an existing object? (for example: there are driveable cars in the scene, when the player gets in one, he now controls the car instead of his character).
    Also, is there any event, launched when a gameobject stops beeing PO? Because in that moment, player should not be able to control it anymore. However, isLocalPlayer will still return true on such object.
     
  2. Necromantic

    Necromantic

    Joined:
    Feb 11, 2013
    Posts:
    115
    There can be multiple Player objects per Client. ReplacePlayerForConnection does not mention any automatic spawning, though I have not tried it..
     
  3. seanr

    seanr

    Unity Technologies

    Joined:
    Sep 22, 2014
    Posts:
    669
    ReplacePlayerForConnection can take an already-spawned object.
    The old player object should have have isLocalPlayer set to false.

    The NetworkLobbyManage uses multiple player objects. Check the NetworkStarter example.
     
  4. trialnterrorgames

    trialnterrorgames

    Joined:
    Feb 20, 2014
    Posts:
    57
    This seems like a good place to ask my question (http://answers.unity3d.com/questions/987799/spawning-a-new-player-instance-unet.html#answer-987901)

    I'm trying to get a player to respawn by instantiating a new gameobject and assigning it to the player as the players new gameobject.

    What I'm doing now is:

    GameObject player = Instantiate<GameObject>(playerPrefab);
    NetworkServer.ReplacePlayerForConnection(NetworkManager.client.connection, player, NetworkManager.client.connection.playerControllers[0].playerControllerId);
    NetworkServer.Spawn(player);

    However, I'm not sure about two things:
    - Am I filling out the parameters for the
    ReplacePlayerForConnection function correctly?
    - Should I call Spawn before or after the ReplacePlayerForConnection function?
     
  5. seanr

    seanr

    Unity Technologies

    Joined:
    Sep 22, 2014
    Posts:
    669
    playerControllerId is per-connection. If there is one player for the client/connection then it would be zero.
     
  6. trialnterrorgames

    trialnterrorgames

    Joined:
    Feb 20, 2014
    Posts:
    57
    NetworkManager.client.connection.playerControllers[0].playerControllerId is indeed zero when I print it to the console, so that parameter seems correct.

    Looking at the NetworkManager everything seems to be OK, except that isLocalPlayer is still false on the new player object...
     
  7. seanr

    seanr

    Unity Technologies

    Joined:
    Sep 22, 2014
    Posts:
    669
    You shouldnt use "NetworkManager.client.connection.playerControllers[0].playerControllerId" in this case - that is client code being called on the server. It just happens to work when you are the host, it would not work on a dedicated server.

    isLocalPlayer would be true on the client, not on the server (assuming LocalPlayerAuthority is set on the NetworkIdentity).
     
  8. trialnterrorgames

    trialnterrorgames

    Joined:
    Feb 20, 2014
    Posts:
    57
    Then how do I get to the playerControllerId? I can for now fill in just 0, but I assume this will give problems in the future.

    Anyway, even when I'm the server and client (so being LAN host) and this parameter filled out as 0, it's still not working (new instantiated gameobject still has isLocalPlayer set on false) and it's driving me nuts.

    Too bad the documentation isn't as comprehensive on UNET as it is on other parts of Unity.
     
  9. seanr

    seanr

    Unity Technologies

    Joined:
    Sep 22, 2014
    Posts:
    669
    The playerControllerId will be zero unless you have multiple players per client.

    Can you post a sample that reproduces this?
     
  10. trialnterrorgames

    trialnterrorgames

    Joined:
    Feb 20, 2014
    Posts:
    57
    Here is an example.

    Look in the Scripts folder in the GameManager.cs script. This has a Respawn function which
    • destroys the current player object (normally I'd do this by NetworkServer.Destroy(), but now I've tried it with NetworkServer.DestroyPlayersForConnection() )
    • instantiate a new GameObject from a prefab
    • spawns the new gameobject on the server
    • calls the ReplacePlayerForConnection() function
    This results in the following error:
    "Local invoke:Failed to find message handler for message ID 4
    UnityEngine.Networking.NetworkServer:ReplacePlayerForConnection(NetworkConnection,GameObject,Int16)"
     

    Attached Files:

  11. seanr

    seanr

    Unity Technologies

    Joined:
    Sep 22, 2014
    Posts:
    669
    You are calling Respawn on the client directly form the UI. It needs to be called on the server, so put it into a command.
    You also dont need to spawn the player object before calling NetworkServer:ReplacePlayerForConnection.

    Code (CSharp):
    1. public class GameManager : NetworkBehaviour {
    2.     public GameObject playerPrefab;
    3.  
    4.     static public GameManager singleton;
    5.  
    6.     void Awake()
    7.     {
    8.         singleton = this;
    9.     }
    10.  
    11.     // this is called on the client from the UI (should find the right player properly)  
    12.     public void Respawn()
    13.     {
    14.         GameObject.FindObjectOfType<PlayerScript>().CmdRespawn();
    15.     }
    16.  
    17.     // called on the server
    18.     public void ServerRespawn(PlayerScript oldPlayer)
    19.     {
    20.         var conn = oldPlayer.connectionToClient;
    21.         var newPlayer = Instantiate<GameObject>(playerPrefab);
    22.         Destroy(oldPlayer.gameObject);
    23.  
    24.         NetworkServer.ReplacePlayerForConnection(conn, newPlayer, 0);
    25.     }
    26.  
    27. }
    Code (CSharp):
    1. public class PlayerScript : NetworkBehaviour {
    2.     public bool _isLocalPlayer;
    3.  
    4.     void Start(){
    5.         if (isLocalPlayer)
    6.         {
    7.             GetComponent<CharacterController>().enabled = true;
    8.             GetComponent<UnityStandardAssets.Characters.FirstPerson.FirstPersonController>().enabled = true;
    9.         }
    10.     }
    11.    
    12.     // Update is called once per frame
    13.     void Update () {
    14.         _isLocalPlayer = isLocalPlayer;
    15.     }
    16.  
    17.     [Command]
    18.     public void CmdRespawn()
    19.     {
    20.         GameManager.singleton.ServerRespawn(this);
    21.     }
    22. }
     
    Sylker and Tomnnn like this.
  12. trialnterrorgames

    trialnterrorgames

    Joined:
    Feb 20, 2014
    Posts:
    57
    Alright, this is working, and I understand where I went wrong.

    I was actually trying to do this in a slightly different way, but it didn't work (still figuring out why...)

    Won't this be a problem when multiple clients have connected? Every instance of every client will have a PlayerScript, so I'd need to find the PlayerScript of the current clients gameObject, no?

    PS: Thank you so much for your help!
     
  13. seanr

    seanr

    Unity Technologies

    Joined:
    Sep 22, 2014
    Posts:
    669
    Yes. You will need to send the command from the right client object.. or have server-side logic drive this.
     
  14. WillTM

    WillTM

    Joined:
    Nov 11, 2013
    Posts:
    4
    Hi I'm trying a Similar thing, the issue i am having is using ReplacePlayerForConnection() on an object that already has had the connection once. IE. a network player cycling through his squad. i get either a: warning Duplicate observer localClient added for CREEP (UnityEngine.GameObject) or an error
    IndexOutOfRangeException: NetworkReader:ReadBytes out of range: (2395) NetBuf sz:64 pos:35
    UnityEngine.Networking.NetBuffer.ReadBytes (System.Byte[] buffer, UInt32 count) (at C:/buildslave/unity/build/Extensions/Networking/Runtime/NetworkBuffer.cs:45)
    UnityEngine.Networking.NetworkReader.ReadBytes (Int32 count) (at C:/buildslave/unity/build/Extensions/Networking/Runtime/NetworkReader.cs:330)

    but usually both.. sorry to hijack the thread but its related I promise.

    so i guess i need to do some work with the observers but is that the cause of the error?
     
  15. trialnterrorgames

    trialnterrorgames

    Joined:
    Feb 20, 2014
    Posts:
    57
    Alright, I've been able to get things working in the following way:


    [Client]
    public void Respawn()
    {
    CmdRespawn(ClientScene.localPlayers[0].gameObject.GetComponent<PlayerScript>());
    }

    [Command]
    public void CmdRespawn(PlayerScript oldPlayer)
    {
    var conn = oldPlayer.connectionToClient;
    var newPlayer = Instantiate<GameObject>(playerPrefab);

    Destroy(oldPlayer.gameObject);

    NetworkServer.ReplacePlayerForConnection(conn, newPlayer, 0);
    }

    Now, what I'd like to be able to do is the following:



    [Command]
    public void CmdRespawn(NetworkConnection playerConnection)
    {
    var newPlayer = Instantiate<GameObject>(playerPrefab);

    Destroy(playerConnection.playerControllers[0].gameObject);

    NetworkServer.ReplacePlayerForConnection(playerConnection, newPlayer, 0);
    }

    But this gives some VERY weird errors:
    • UNetWeaver error: Exception :System.ArgumentException: Member 'UnityEngine.Networking.NetBuffer' is declared in another module and needs to be imported
    • Failure generating network code. UnityEditor.Scripting.Serialization.Weaver:WeaveUnetFromEditor(String, String, String, String, Boolean)
    • error CS0016: Could not write to file `Temp/Assembly-CSharp.dll', cause: Sharing violation on path G:\_Data Backup\projects\Trial and Terror\test\networking\networking\Temp\Assembly-CSharp.dll.mdb
    I don't know what that's all about
     
  16. seanr

    seanr

    Unity Technologies

    Joined:
    Sep 22, 2014
    Posts:
    669
    You can't pass a NetworkConnection in a command. Anyway, your client doesn't have NetworkConnection objects for other players on the server to pass to that command. You could pass the NetworkIdentity of another player (base.netId) and look it up on the server with NetworkServer.FindLocalObject().
     
  17. trialnterrorgames

    trialnterrorgames

    Joined:
    Feb 20, 2014
    Posts:
    57
    I think I'm starting to understand. I've changed my code to work with the netId of an object.

    Code (CSharp):
    1.     [Client]
    2.     public void Respawn()
    3.     {
    4.         Debug.Log(currentPlayerGameObject.GetComponent<NetworkIdentity>().netId);
    5.         CmdRespawn(currentPlayerGameObject.GetComponent<NetworkIdentity>().netId);
    6.     }
    7.    
    8.     [Command]
    9.     public void CmdRespawn(NetworkInstanceId playerNetId)
    10.     {
    11.         GameObject oldPlayer = NetworkServer.FindLocalObject(playerNetId);
    12.         var conn = oldPlayer.GetComponent<NetworkIdentity>().connectionToClient;
    13.         var newPlayer = Instantiate<GameObject>(playerPrefab);
    14.        
    15.         Destroy(oldPlayer.gameObject);
    16.        
    17.         NetworkServer.ReplacePlayerForConnection(conn, newPlayer, 0);
    18.         Camera.main.GetComponent<FollowTarget>().target = newPlayer.transform;
    19.     }
    Now, this works fine on the host client, but when a second client connects and tries to respawn it gets the following warning:
    And nothing happens. However, when I write the netId to the console and check the netId in the gameManager, it passes the correct netId.
     
  18. seanr

    seanr

    Unity Technologies

    Joined:
    Sep 22, 2014
    Posts:
    669
    That error is because both player objects on the new client are calling respawn. One of them is for the other player. You probably need to wrap your input handling with "if (isLocalPlayer)".
     
  19. trialnterrorgames

    trialnterrorgames

    Joined:
    Feb 20, 2014
    Posts:
    57
    What I've done is this:

    The player has a respawn script attached to it as follows:
    Code (CSharp):
    1. public class Player_respawn : NetworkBehaviour {
    2.  
    3.     // Use this for initialization
    4.     void Start () {
    5.         if (isLocalPlayer)
    6.         {
    7.             Button respawnButton = GameManager.singleton.respawnButton.GetComponent<Button>();
    8.             respawnButton.onClick.AddListener(() => { RespawnPlayer(); });
    9.         }
    10.     }
    11.  
    12.     void RespawnPlayer()
    13.     {
    14.         if (isLocalPlayer)
    15.         {
    16.             Button respawnButton = GameManager.singleton.respawnButton.GetComponent<Button>();
    17.             respawnButton.onClick.RemoveAllListeners();
    18.             GameManager.singleton.CmdRespawn(netId);
    19.         }
    20.     }
    21. }
    This will call the GameManagers code:

    Code (CSharp):
    1.  
    2.     [Command]
    3.     public void CmdRespawn(NetworkInstanceId playerNetId)
    4.     {
    5.         GameObject oldPlayer = NetworkServer.FindLocalObject(playerNetId);
    6.         var conn = oldPlayer.GetComponent<NetworkIdentity>().connectionToClient;
    7.         var newPlayer = Instantiate<GameObject>(playerPrefab);
    8.  
    9.         Destroy(oldPlayer.gameObject);
    10.  
    11.         NetworkServer.ReplacePlayerForConnection(conn, newPlayer, 0);
    12.     }
    I assumed, because I checked when I clicked the button if the object is local, that the command would be send for the local player. However, unity is telling me this is not the case.

    I've attached the project.
     

    Attached Files:

    Last edited: Jun 18, 2015
  20. seanr

    seanr

    Unity Technologies

    Joined:
    Sep 22, 2014
    Posts:
    669
    The non-localPlayer object is the GameManager. You can only have commands on player objects.

    Your code is insecure - a malicious client could pass the netId of a different player and cause them to respawn. Put the command on the player object, then that cannot happen.
     
  21. trialnterrorgames

    trialnterrorgames

    Joined:
    Feb 20, 2014
    Posts:
    57
    I thought Unity prevented that already, because it doesn't allow you to change other players (hence the warning).
     
  22. trialnterrorgames

    trialnterrorgames

    Joined:
    Feb 20, 2014
    Posts:
    57
    So in order to be able to remove the player and show a respawn UI first, and only after clicking the respawn button actually respawning, I have to:

    - respawn as a "dead player" (in essence, a spectator or something like that)
    - have a respawn script attached to the "dead player" gameobject
    - show the respawn UI

    and after clicking the button
    - respawn again, but this time as a "living player"
     
  23. seanr

    seanr

    Unity Technologies

    Joined:
    Sep 22, 2014
    Posts:
    669
    No.. you don't have to do that. The code I posted above in this thread works.
     
  24. trialnterrorgames

    trialnterrorgames

    Joined:
    Feb 20, 2014
    Posts:
    57
    Yeah, but that code assumes that the player object still exists when you click on the "respawn" button, while it actually already has been removed from the server and clients (well, in my specific case I mean).

    Edit: Or will the Command to the server still work without an instance?
     
  25. seanr

    seanr

    Unity Technologies

    Joined:
    Sep 22, 2014
    Posts:
    669
    Ok fair point. You cannot run commands without a player object.

    So you options are:
    • disable the object instead of destroying it - you can run a command on a disabled object
    • use a message instead of a command
     
  26. Tomnnn

    Tomnnn

    Joined:
    May 23, 2013
    Posts:
    4,137
    Hey, how come we can do this but we can't send NetworkConnection types? I have been banging my head against a wall for hours trying to figure out how to get the connection of the local player so I could...

    -spawn an empty object that runs on the client to determine their device type
    -use information obtained from the empty object to spawn the correct type of player

    But I've been making 0 progress on passing a network connection. So in your example... NetworkConnection can't be passed, but a NetworkBehavior can which contains a networkconnection!? How does that make sense? I get compiler errors if I try to have a NetworkConnection as a parameter for a function that is an Rpc or Command.
     
    TehGM likes this.
  27. TehGM

    TehGM

    Joined:
    Nov 15, 2013
    Posts:
    89
    That gave me a lot of headaches too. I worked it around, but I agree that it makes no sense. And I now pass connection ID instead, but hell yeah, it is insecure.
    As for now I am starting the development of this game so I can put making it more secure on my todo list, but hell...
     
  28. Tomnnn

    Tomnnn

    Joined:
    May 23, 2013
    Posts:
    4,137
    I figured it out. The difference in the example is that it isn't passed over the network. Classes and complex data cannot be passed as a parameter to an rpc function. What you can do instead is send serialized data or if you do things the unity way, every copy of your client has the same data, so just pass the function to your network singleton on the server side with a command.

    I got around most of the issues I was having. I've gone an interesting route where the games are local but the game singletons are aware of player controlled objects in other player's games and the only thing seen in both games is an input receiver.
     
  29. Sylker

    Sylker

    Joined:
    Sep 22, 2012
    Posts:
    14
    This is gold! After wandering around tons of documentation and other posts/Answers I finally stumbled over the right question. Thanks!

    Just want to understand the need of _isLocalPlayer = isLocalPlayer;