Search Unity

How to set individual playerPrefab form client in the NetworkManger?

Discussion in 'Multiplayer' started by ClausKleber, Aug 17, 2015.

  1. ClausKleber

    ClausKleber

    Joined:
    Aug 17, 2015
    Posts:
    6
    Hello,

    i am trying to set the playerPrefab from a client before the NetworkManager is spawning it. In this way the client has the possibility to change the playerPrefab before he is joining the game. But i cannot find a way to tell the server which prefab should be used.

    I've tried to do a RPC request before OnServerAddPlayer(...) spawnes the prefab but the RpcRequestPrefab() function only runs on server side.

    Code (CSharp):
    1. public override void OnServerAddPlayer(NetworkConnection conn, short playerId)
    2. {
    3.      playerPrefab = RpcRequestPrefab();  
    4.      base.OnServerAddPlayer(conn, playerId);
    5. }
    6.    
    7. [ClientRpc]
    8. public GameObject RpcRequestPrefab()
    9. {
    10.      return character;
    11. }
    What is the right way to get the playerPrefab from client so that the server is able to spawn it?

    Thanks a lot!
     
  2. SuperNeon

    SuperNeon

    Joined:
    Mar 16, 2014
    Posts:
    85
    Hi !

    You can't do that in this way.
    Server side:
    When you client is ready (OnServerReady) send a network message to the connection to request the prefab.

    Client side:
    When you received the message send the index of your array of player prefab (have to added somewhere and know on both side).

    Server side:
    When the server received the message you can set the client as ready (NetworkServer.SetClientReady) and keep the info of your player. And now in the callback OnServerAddPlayer you can set the playerPrefab from the previous info you kept.

    It should work but need a bit of work. I'm not totally sure as I can't test the code for now.

    Good luck :)
     
  3. ClausKleber

    ClausKleber

    Joined:
    Aug 17, 2015
    Posts:
    6
    Thank you for your reply.

    I've tried to implement your suggestion, but unfortunately OnServerAddPlayer(...) was always called before the server got the response from the client. Even if I've called NetworkServer.SetClientReady() only after the server got the prefab index.
    But now I've found a way. It seems to work well when requesting the player prefab in the OnServerAddPlayer(...) callback function. Only after getting the response on server side I call base.OnServerAddPlayer(...).

    Here is my simplified code. If you have some suggestions to improve this code snippet, please let me know :)

    Code (CSharp):
    1. public class MsgTypes
    2. {
    3.    public const short PlayerPrefab = MsgType.Highest + 1;
    4.  
    5.    public class PlayerPrefabMsg : MessageBase
    6.    {
    7.      public short controllerID;    
    8.      public short prefabIndex;
    9.    }
    10. }
    11.  
    12. public class NetworkController : NetworkManager
    13. {
    14.    public short playerPrefabIndex;
    15.  
    16.    public override void OnStartServer()
    17.    {
    18.      NetworkServer.RegisterHandler(MsgTypes.PlayerPrefab, OnResponsePrefab);
    19.      base.OnStartServer();
    20.    }
    21.  
    22.    public override void OnClientConnect(NetworkConnection conn)
    23.    {
    24.      client.RegisterHandler(MsgTypes.PlayerPrefab, OnRequestPrefab);
    25.      base.OnClientConnect(conn);
    26.    }
    27.  
    28.    private void OnRequestPrefab(NetworkMessage netMsg)
    29.    {
    30.      MsgTypes.PlayerPrefabMsg msg = new MsgTypes.PlayerPrefabMsg();
    31.      msg.controllerID = netMsg.ReadMessage<MsgTypes.PlayerPrefabMsg>().controllerID;
    32.      msg.prefabIndex = playerPrefabIndex;
    33.      client.Send(MsgTypes.PlayerPrefab, msg);
    34.    }
    35.  
    36.    private void OnResponsePrefab(NetworkMessage netMsg)
    37.    {
    38.      MsgTypes.PlayerPrefabMsg msg = netMsg.ReadMessage<MsgTypes.PlayerPrefabMsg>();  
    39.      playerPrefab = spawnPrefabs[msg.prefabIndex];
    40.      base.OnServerAddPlayer(netMsg.conn, msg.controllerID);
    41.      Debug.Log(playerPrefab.name + " spawned!");
    42.    }
    43.  
    44.    public override void OnServerAddPlayer(NetworkConnection conn, short playerControllerId)
    45.    {
    46.      MsgTypes.PlayerPrefabMsg msg = new MsgTypes.PlayerPrefabMsg();
    47.      msg.controllerID = playerControllerId;
    48.      NetworkServer.SendToClient(conn.connectionId, MsgTypes.PlayerPrefab, msg);
    49.    }
    50. }
     
    Last edited: Aug 21, 2015
    D-nax, joecarver, felsi and 1 other person like this.
  4. SuperNeon

    SuperNeon

    Joined:
    Mar 16, 2014
    Posts:
    85
    It is good for me !
     
  5. hersheys72

    hersheys72

    Joined:
    Sep 4, 2015
    Posts:
    7
    Awesome! Thank you so much for this..
    Spent a whole day trying to get this working!
     
  6. Eiseno

    Eiseno

    Joined:
    Nov 1, 2015
    Posts:
    86
    Hi,
    I want first player take 1st prefab and 2nd player take second prefab how can i do this ?
     
  7. seanr

    seanr

    Unity Technologies

    Joined:
    Sep 22, 2014
    Posts:
    669
    a better solution would be use the version of ClientScene.AddPlayer() that takes a extra message parameter, and in OnServerAddPlayer look at the contents of that message to choose which prefab to use
     
    wuxiao likes this.
  8. Eiseno

    Eiseno

    Joined:
    Nov 1, 2015
    Posts:
    86
    How make this for lobby manager ?
     
    Guhanesh likes this.
  9. jinnindo

    jinnindo

    Joined:
    Dec 8, 2015
    Posts:
    17
    Is there an example of this somewhere? It sounds like a good solution, but I can't well make sense of it.
     
    MMOERPG likes this.
  10. moco2k

    moco2k

    Joined:
    Apr 29, 2015
    Posts:
    294
    Here is a simple example of overriding some NetworkManager functions in order to pass an integer value on client connection using OnServerAddPlayer with a message parameter as seanr has suggested. For example, you can use the message value as an identifier to select a particular prefab.

    Code (CSharp):
    1. // Client
    2. public override void OnClientConnect(NetworkConnection conn)
    3. {
    4.     // A custom identifier we want to transmit from client to server on connection
    5.     int id = GetCustomValue();
    6.  
    7.     // Create message which stores our custom identifier
    8.     IntegerMessage msg = newIntegerMessage(id);
    9.  
    10.     // Call Add player and pass the message
    11.     ClientScene.AddPlayer(conn,playerControllerId,msg);
    12. }
    13.  
    14. // Server
    15. public override void OnServerAddPlayer(NetworkConnection conn, short playerControllerId, NetworkReader extraMessageReader )
    16. {
    17.     // Variable to store the identifier
    18.     int id = 0;
    19.  
    20.     // Read client message and receive identifier
    21.     if (extraMessageReader != null)
    22.     {
    23.         var i = extraMessageReader.ReadMessage<IntegerMessage> ();
    24.         id = i.value;
    25.     }
    26.  
    27.     // Select a specific prefab using the identifier (e.g. available prefabs could be stored in an array)
    28.     GameObject playerPrefab = GetPlayerPrefabById(id);
    29.  
    30.     // Create player object with prefab
    31.     GameObject player = (GameObject)Instantiate(playerPrefab, spawnPoint, Quaternion.identity);
    32.  
    33.     // Add player object for connection
    34.     NetworkServer.AddPlayerForConnection(conn, player, playerControllerId);
    35. }
    In order to use standard messages such as IntegerMessage, do not forget to include:
    Code (CSharp):
    1. using UnityEngine.Networking.NetworkSystem;
     
    Last edited: Jul 29, 2016
    mickdagger, Oni07R and Leoo like this.
  11. jinnindo

    jinnindo

    Joined:
    Dec 8, 2015
    Posts:
    17
  12. moco2k

    moco2k

    Joined:
    Apr 29, 2015
    Posts:
    294
    My example is just exactly what seanr suggested. I also think it is way more simple and straight-forward in comparison to the implementation variant you've posted above.

    You just use ClientScene.AddPlayer(conn,playerControllerId,msg) while passing a message as third parameter (by default, a message is not passed). You can then use the contents of this message to select your prefab. For example, a client selects hero class 3. This value is send to the server with the message and the server knows that he needs to spawn prefab with id 3.

    Note that the code snippet just shows the relevant parts. It is not a complete solution you can copy + paste and you're done.
     
    Last edited: Sep 9, 2016
  13. jinnindo

    jinnindo

    Joined:
    Dec 8, 2015
    Posts:
    17
    The difference is I could make his work, but I don't understand yours or the errors I get with it. Maybe that's my fault, but that's why I ask questions and keep looking.

    I will continue trying to make sense of it. Do you mind if I pm you questions?
     
  14. moco2k

    moco2k

    Joined:
    Apr 29, 2015
    Posts:
    294
    I guess you need to do some further research on how the Network Manager works and how to use and override it's functions.
     
  15. Klounas

    Klounas

    Joined:
    Oct 10, 2013
    Posts:
    7
    This works amazingly, except for the fact that I get this error:
    It does not seem to affect the game at all. I can still play on both client and host without any interruption. Is there a way to get rid of this error? I've looked at unity's NetworkManager source code and can't figure out how they handle it.
     
  16. moco2k

    moco2k

    Joined:
    Apr 29, 2015
    Posts:
    294
    The issue is because the client connection is set as ready multiple times.

    Indeed, you need to check which functions include the ready call. In addition to the NetworkManager source, you should also look into related source like ClientScene. For example, ClientScene.AddPlayer also sets a connection as ready.

    Then, you can override the particular network manager functions and remove redundant calls in order to make sure that it is only called once.
     
    Last edited: Sep 9, 2016
  17. Klounas

    Klounas

    Joined:
    Oct 10, 2013
    Posts:
    7
    I'm sorry but I'm not quite sure I understand the solution here. Am I to completely remove the ready sections of the AddPlayer method and/or all other methods by overriding them?

    Code (CSharp):
    1. public virtual void OnClientConnect(NetworkConnection conn)
    2.         {
    3.             if (!clientLoadedScene)
    4.             {
    5.                 // Ready/AddPlayer is usually triggered by a scene load completing. if no scene was loaded, then Ready/AddPlayer it here instead.
    6.                 ClientScene.Ready(conn);
    7.                 if (m_AutoCreatePlayer)
    8.                 {
    9.                     ClientScene.AddPlayer(0);
    10.                 }
    11.             }
    12.         }
    If the original OnClientConnect indicates that the AddPlayer/Ready should be triggered by a scene load completing, shouldn't the custom code be added there?

    My apologies if I'm completely off the tracks, this has me very confused.

    Thank you for your time.
    __________________________________________________________
    (Edit)
    SOLUTION (?):

    So by tinkering a bit, I figured(?) that OnClientSceneChanged also needs to be overridden to avoid calling the ready function more than once.

    Anyways, here's the code that I use temporarily to test everything out:


    Code (CSharp):
    1.     public GameObject[] playerPrefabList;
    2.     public int index = 0;
    3.  
    4.     // Client
    5.     public override void OnClientConnect(NetworkConnection conn)
    6.     {
    7.         // A custom identifier we want to transmit from client to server on connection
    8.         //int id = GetCustomValue();
    9.  
    10.         // Create message which stores our custom identifier
    11.         IntegerMessage msg = new IntegerMessage(index);
    12.  
    13.         if (!clientLoadedScene)
    14.         {
    15.             // Call Add player and pass the message
    16.             ClientScene.AddPlayer(conn, 0, msg);
    17.         }
    18.     }
    19.  
    20.     public override void OnClientSceneChanged(NetworkConnection conn)
    21.     {
    22.         // Create message which stores our custom identifier
    23.         IntegerMessage msg = new IntegerMessage(index);
    24.  
    25.         // always become ready.
    26.         ClientScene.Ready(conn);
    27.  
    28.         bool addPlayer = (ClientScene.localPlayers.Count == 0);
    29.         bool foundPlayer = false;
    30.         for (int i = 0; i < ClientScene.localPlayers.Count; i++)
    31.         {
    32.             if (ClientScene.localPlayers[i].gameObject != null)
    33.             {
    34.                 foundPlayer = true;
    35.                 break;
    36.             }
    37.         }
    38.         if (!foundPlayer)
    39.         {
    40.             // there are players, but their game objects have all been deleted
    41.             addPlayer = true;
    42.         }
    43.         if (addPlayer)
    44.         {
    45.             // Call Add player and pass the message
    46.             ClientScene.AddPlayer(conn, 0, msg);
    47.         }
    48.     }
    49.  
    50.     // Server
    51.     public override void OnServerAddPlayer(NetworkConnection conn, short playerControllerId, NetworkReader extraMessageReader)
    52.     {
    53.         // Variable to store the identifier
    54.         int id = 0;
    55.  
    56.         // Read client message and receive identifier
    57.         if (extraMessageReader != null)
    58.         {
    59.             var i = extraMessageReader.ReadMessage<IntegerMessage>();
    60.             id = i.value;
    61.         }
    62.  
    63.         // Select a specific prefab using the identifier (e.g. available prefabs could be stored in an array)
    64.         GameObject playerPrefab = playerPrefabList[id];
    65.  
    66.         // Create player object with prefab
    67.         GameObject player = (GameObject)Instantiate(playerPrefab, NetworkManager.singleton.GetStartPosition().position, Quaternion.identity);
    68.  
    69.         // Add player object for connection
    70.         NetworkServer.AddPlayerForConnection(conn, player, playerControllerId);
    71.     }
    If someone can confirm that everything's fine, it would be awesome.
     
    Last edited: Sep 11, 2016
    mickdagger and remo_10 like this.
  18. jinnindo

    jinnindo

    Joined:
    Dec 8, 2015
    Posts:
    17
    Amusingly, after updating, I had to delete moco2k's code for its red errors.

    Klounas, after experiments to understand your code, it's working perfectly for me. Thank you.