Search Unity

Question Netcode Spawn Prefab

Discussion in 'Netcode for GameObjects' started by murolofilippo, Oct 19, 2023.

  1. murolofilippo

    murolofilippo

    Joined:
    Jan 23, 2023
    Posts:
    9
    Good morning guys, I am struggling with the netcode. Let's start with the fact that I'm a neophyte and I've been using it really recently, I'm faced with this scenario:
    In the scene I have two classic Host and Client buttons, as well as having two other buttons referring to two creatures. The user before connecting has to select the creature he wants to use, then he clicks, the creature is set to an int 0 or 1 based on the one clicked and then he decides whether to be a host or be a client.
    So far so good.

    Now, I would like when a user logs in, it passes the selected creature to the server. When there are 2 creatures selected (so 2 players connected) I would like the server to send the clients the creatures to be spawned, so their own creature and the opponent's creature. Eventually in the scene should be: on the right player creature, on the left opponent creature.

    Any advice?
     
  2. cerestorm

    cerestorm

    Joined:
    Apr 16, 2020
    Posts:
    664
    You can select the correct creature prefab by passing a value on connection and selecting the correct prefab hash in Connection Approval. There's an explanation of how to do it there but here's an abridged version:
    Code (CSharp):
    1.     public class ApprovalSceneController : MonoBehaviour
    2.     {
    3.         [SerializeField] List<uint> playerPrefabHashes;
    4.  
    5.         void Start()
    6.         {
    7.             Application.targetFrameRate = 15;
    8.  
    9.             if (!ParrelSync.ClonesManager.IsClone())
    10.             {
    11.                 NetworkManager.Singleton.ConnectionApprovalCallback += OnConnectionApproval;
    12.                 NetworkManager.Singleton.NetworkConfig.ConnectionData = BitConverter.GetBytes(0);
    13.                 NetworkManager.Singleton.StartHost();
    14.             }
    15.             else
    16.             {
    17.                 NetworkManager.Singleton.NetworkConfig.ConnectionData = BitConverter.GetBytes(1);
    18.                 NetworkManager.Singleton.StartClient();
    19.             }
    20.         }
    21.  
    22.         private void OnConnectionApproval(NetworkManager.ConnectionApprovalRequest request, NetworkManager.ConnectionApprovalResponse response)
    23.         {
    24.             Debug.Log("OnConnectionApproval playerPrefabHashes: " + playerPrefabHashes.Count);
    25.  
    26.             if (request.Payload.Length > 0)
    27.             {
    28.                 int playerPrefabIndex = BitConverter.ToInt32(request.Payload, 0);
    29.  
    30.                 Debug.Log($"OnConnectionApproval playerPrefabIndex: {playerPrefabIndex} hash: {playerPrefabHashes[playerPrefabIndex]}");
    31.  
    32.                 response.PlayerPrefabHash = playerPrefabHashes[playerPrefabIndex];
    33.                 response.CreatePlayerObject = true;
    34.                 response.Approved = true;
    35.             }
    36.             else
    37.             {
    38.                 response.Approved = false;
    39.             }
    40.         }
    41.     }
     
  3. murolofilippo

    murolofilippo

    Joined:
    Jan 23, 2023
    Posts:
    9
    Thank you very much for your reply!
    Isn't it a bit complicated as a solution? Maybe I didn't explain it well myself... I was thinking more something like when the player spawns a serverrpc call is made that passes its id and the creature's id. The server adds the player to the dictionary. When the server adds the second player with an if count check on the dictionary it sends clientrpc calls to the two clients with the contents of the dictionary (two calls then). The clientrpc functiond checks if the id is different from the local player and sets the enemy of the other client's id in a local manager.
     
  4. cerestorm

    cerestorm

    Joined:
    Apr 16, 2020
    Posts:
    664
    I thought it was the simplest solution :) as long as players are allowed to select the same creature, if not you'd need the selection to happen after connection.

    What you're suggesting is a different approach which is fine. You can send the creature id in an rpc and have it stored on a network variable on the player, that way host and client will receive the value without having to worry about more rpcs. It does depend though if they're allowed to be the same creature or not.
     
  5. murolofilippo

    murolofilippo

    Joined:
    Jan 23, 2023
    Posts:
    9
    Yes they're allowed to be the same creature. I'm trying to the rpc method but i'm facing some issues.

    This is the Player prefab that is spawned.
    Code (CSharp):
    1. public class PlayerManager : NetworkBehaviour
    2. {
    3.  
    4.     [SerializeField]
    5.     public GameObject creaturePrefab;
    6.  
    7.     public static PlayerManager LocalInstance { get; private set; }
    8.  
    9.     public int Selected;
    10.     public int Enemy;
    11.     public override void OnNetworkSpawn()
    12.     {
    13.         base.OnNetworkSpawn();
    14.         if (IsOwner)
    15.         {
    16.             LocalInstance = this;
    17.             gameObject.name = "Player";
    18.             int selected = SceneInitializer.Instance.selectedCreature;
    19.          
    20.             PlayersInfo.Instance.AddPlayerInfoServerRpc(selected, new ServerRpcParams());
    21.         }
    22.         else
    23.         {
    24.             gameObject.name = "Enemy";
    25.         }
    26.     }
    27.  
    28. }
    Selected is the creature I have selected before, Enemy is the creature selected by the enemy of course.

    This is the script in a GameObject called PlayersInfo
    Code (CSharp):
    1. public class PlayersInfo : NetworkBehaviour
    2. {
    3.     public static PlayersInfo Instance;
    4.     public event EventHandler OnPlayerAdded;
    5.     public event EventHandler OnPlayersReady;
    6.  
    7.     private Dictionary<ulong, int> Players;
    8.  
    9.     private void Awake()
    10.     {
    11.         Instance = this;
    12.  
    13.         Players = new Dictionary<ulong, int>();
    14.     }
    15.  
    16.     [ServerRpc]
    17.     public void AddPlayerInfoServerRpc(int i, ServerRpcParams serverRpcParams)
    18.     {
    19.         Debug.Log("CALLED");
    20.      
    21.         if (!IsServer) return;
    22.      
    23.         if(!Players.ContainsKey(serverRpcParams.Receive.SenderClientId))
    24.         {
    25.             Players.Add(serverRpcParams.Receive.SenderClientId, i);
    26.             OnPlayerAdded?.Invoke(this, EventArgs.Empty);
    27.             Debug.Log("Added " + serverRpcParams.Receive.SenderClientId);
    28.         }
    29.      
    30.         if(Players.Count == 2)
    31.         {
    32.             foreach(var p in Players)
    33.             {
    34.                 SetClientRpc(p.Key, p.Value);
    35.             }
    36.         }
    37.      
    38.     }
    39.     [ClientRpc]
    40.     public void SetClientRpc(ulong id, int creature)
    41.     {
    42.         Debug.Log(OwnerClientId);
    43.  
    44.         OnPlayersReady?.Invoke(this, EventArgs.Empty);
    45.     }
    46.  
    47.     public void PrintDictionary()
    48.     {
    49.         foreach (KeyValuePair<ulong, int> entry in Players)
    50.         {
    51.             Debug.Log("Client ID: " + entry.Key + ", Creature: " + entry.Value);
    52.         }
    53.     }
    54. }
    The problem is that the ServerRpc AddPlayerInfoServerRpc is never called... Do yuou know why?

    EDIT: The clientRpc call is only to know if it's working
     
  6. cerestorm

    cerestorm

    Joined:
    Apr 16, 2020
    Posts:
    664
    You could try moving the code from OnNetworkSpawn to Start, I can't remember if the object is fully initialised at that point, IsOwner might not be set. Best tip I can give you is log EVERYTHING. :D
     
  7. murolofilippo

    murolofilippo

    Joined:
    Jan 23, 2023
    Posts:
    9
    Yeah it is initialized because the gameobject name is set to Player!
     
  8. cerestorm

    cerestorm

    Joined:
    Apr 16, 2020
    Posts:
    664
    The name isn't a network related value, IsOwner will be false initially. If that's not the issue check the object in the editor and make sure the NetworkObject component has a NetworkObjectId and the flag values look correct, as well as the observers. If it has a NetworkObjectId I can only think the rpc is called too early. Put a log in the rpc to know when it's called.
     
  9. murolofilippo

    murolofilippo

    Joined:
    Jan 23, 2023
    Posts:
    9
    The Log is already in the RPC ("Called") and is never trigger. I've just tried to connect an host and then a client, when i connect the client i've an error on it

    Only the owner can invoke a ServerRpc that requires ownership!
     
  10. cerestorm

    cerestorm

    Joined:
    Apr 16, 2020
    Posts:
    664
    For AddPlayerInfoServerRpc? It sounds like PlayersInfo is owned by the host and only the owner can make the rpc calls. You can get around this changing the annotation to
    [ServerRpc(RequireOwnership = false)]
    for now but you might want to re-work your code later on.