Search Unity

[MLAPI ] need help to sync spawned objects

Discussion in 'Netcode for GameObjects' started by antromagames, Mar 29, 2021.

  1. antromagames

    antromagames

    Joined:
    Oct 20, 2020
    Posts:
    6
    Hi,
    First i used to use Mirror for a long time ago project and for this new one , i'm trying to use the new MLAPI .
    I'm sorry for my non native English . I 'm a beginner too in multiplayer coding.

    What i'm trying to do : make a simple app in 2D , i have a UI canvas with 2 Panels : Entry and Exit ; Entry contains two scrollviews : one for player waiting and one for players playing .

    Player is a 2d UI Button which contains network object component and network transform .

    I want to create a kind of lobby, very basically when a player click on his name on the first panel : the button mooves to the second panel and vice versa .

    When i spawn a player with networkObject.Spawn() the player appears in client but has not the wanted transform , i don't know how to implement the good transform and the name of button to be synced with the one on server ....

    I tried to use [rpc] and networkvariable<Transform> but did'nt work .

    Can someone help me ( showing simple example of lobby room code ...) ?

    Thanks in advance !
     
  2. firaui

    firaui

    Joined:
    Nov 14, 2018
    Posts:
    12
    In the Docs, it is stated that Spawn method should only be done on the server as the object will automatically replicate on the other clients. You must Instantiate and then call spawn, all in the server.

    To sync the position you can use NetworkVariableVector3 to indicate object position. You can assign that in server or owner of that object and read the value if not. Read more in here

    For example my simple spawn method

    Code (CSharp):
    1.  
    2. public class GameClient : NetworkBehaviour
    3. {
    4.     public override void NetworkStart()
    5.     {
    6.         base.NetworkStart();
    7.         SpawnServerRPC(NetworkManager.Singleton.LocalClientId);
    8.     }
    9.  
    10.     [ServerRpc]
    11.     private void SpawnServerRPC(ulong clientId)
    12.     {
    13.         // get yourGameObject here
    14.         NetworkObject obj = Instantiate(yourGameObject).GetComponent<NetworkObject>();
    15.         obj.SpawnWithOwnership(clientId);
    16.         InitializeClientRPC(clientId, obj.NetworkObjectId);
    17.     }
    18.  
    19.     [ClientRpc]
    20.     private void InitializeClientRPC(ulong clientId, ulong objectId)
    21.     {
    22.         if(clientId == NetworkManager.Singleton.LocalClientId)
    23.         {
    24.             GameObject spawnedObject = NetworkSpawnManager.SpawnedObjects[objectId].gameObject;
    25.         }
    26.     }
    27. }
     
    TheAutoGamer and antromagames like this.
  3. antromagames

    antromagames

    Joined:
    Oct 20, 2020
    Posts:
    6
    Thank you for your answer firaui ;

    I get an null referenceException now and i don't know why :


    Steps to reproduce i made a simple scene with 4 network buttons Starthost start client and start Spawning ( fig 1 )

    Start host calls a public method and invoke NetworkManager.singleton.Starthost ; the same for startServer ...

    I added a GameClientSpawner in scene with your script ( i just SerializeField the gameobject )
    Code (CSharp):
    1. using System;
    2. using System.Collections;
    3. using System.Collections.Generic;
    4. using System.Text;
    5. using UnityEngine;
    6. using UnityEngine.UI;
    7. using UnityGoogleDrive;
    8. using UnityGoogleDrive.Data;
    9. using MLAPI.Messaging;
    10. using MLAPI.NetworkVariable;
    11. using MLAPI;
    12. using MLAPI.Spawning;
    13. public class GameClient : NetworkBehaviour
    14. {
    15.  
    16.     [SerializeField] GameObject gamePrefab;
    17.  
    18.     public override void NetworkStart()
    19.     {
    20.         base.NetworkStart();
    21.         SpawnServerRPC(NetworkManager.Singleton.LocalClientId);
    22.     }
    23.  
    24.     [ServerRpc]
    25.     private void SpawnServerRPC(ulong clientId)
    26.     {
    27.         // get yourGameObject here
    28.         NetworkObject obj = Instantiate(gamePrefab).GetComponent<NetworkObject>();
    29.         obj.SpawnWithOwnership(clientId);
    30.         InitializeClientRPC(clientId, obj.NetworkObjectId);
    31.     }
    32.  
    33.     [ClientRpc]
    34.     private void InitializeClientRPC(ulong clientId, ulong objectId)
    35.     {
    36.         if (clientId == NetworkManager.Singleton.LocalClientId)
    37.         {
    38.             GameObject spawnedObject = NetworkSpawnManager.SpawnedObjects[objectId].gameObject;
    39.         }
    40.     }
    41. }
    And i call the networkstart function with the button "StartSpawning"

    my gameObject is a simple diamond with a script attached to moove it following the mouse cursor ; it has the networkObject component and a network prefab
    i added this gameobject in networkManager (in prefabs)

    Then i build my game, i start the game in editor and then in the built version ;

    i start server in the editor playing version and start client on the built one , and when i start Sapwning in editor i get this nullreferenceexception ( see in picture)

    I don't know what i m doing wrong here ? plz help me and thanks a lot in advance !!!
     

    Attached Files:

  4. antromagames

    antromagames

    Joined:
    Oct 20, 2020
    Posts:
    6
    So ; i'm here again and facing the same problem , after googling a lot to find a solution , i'm coming back to try to explain much better what's going wrong :

    i'm making a simple app for the place where i work , basically we have to face with covid constraint as we receive people inside a place where we need to stop people coming if there are too many ...

    For this i have to create an app working on two tablet /phone :eek:ne will be at the entry and the other at the exit

    in the first tablet , after selecting the entry panel the admin enters a googlesheet id to get a list of users( only firstname and name ) and they have to appear in the scrollview content " player list" , when the person arrives and watches his name he clicks on the button and then it mooves to "present list" scrollview

    It has to be synced with the exit tablet as the person will have to click again at the exit and then his name disappear from the present list to return at the playerlist ( i hope i am clearer ... ) and once again sorry for my English ;) .

    Without networking , it works like a charm !

    I copied the unity folder and opened with unityhub both project (original and the copy) when i run as a server the first one and client the second one i can and i import the sheet i can see playerbuttons appear in hierarchy , with the same position, but with no names and not under the parent transform ( the content of the scrollview)

    I'm trying to figure out why it doesn't work but i'm new to networking ...


    that's my googledriveimportand spawning script :
    Code (CSharp):
    1. using System;
    2. using System.Collections;
    3. using System.Collections.Generic;
    4. using System.Text;
    5. using UnityEngine;
    6. using UnityEngine.UI;
    7. using UnityGoogleDrive;
    8. using UnityGoogleDrive.Data;
    9. using MLAPI.Messaging;
    10. using MLAPI.NetworkVariable;
    11. using MLAPI;
    12. using MLAPI.Spawning;
    13.  
    14. public class DriveManager : NetworkBehaviour
    15. {
    16.  
    17.     // the id of the googlesheet in google drive
    18.     [SerializeField] private string fileId ;
    19.     [SerializeField] InputField inputField;
    20.     // Prefab of playerbutton
    21.     [SerializeField] GameObject playerPrefab;
    22.  
    23.     private GoogleDriveFiles.ExportRequest request;
    24.  
    25.  
    26.     GameObject playerGo;
    27.     [SerializeField] Transform playerList_Scroll_Transform;
    28.  
    29.     //When we click on the validate button for the googlesheet
    30.     public void OnClick()
    31.     {
    32.         SetGoogleSheetId();
    33.         GetDataFromDrive();
    34.     }
    35.  
    36.     // Methode pour aller chercher une liste sur google drive ( file id indiqué dans les paramètres
    37.     private void GetDataFromDrive()
    38.     {
    39.         Debug.Log("Get Data from Google Drive...");
    40.         if (fileId == "") { return; }
    41.             request = GoogleDriveFiles.Export(fileId, "text/csv");
    42.             request.Send().OnDone += SetResult;  
    43.     }
    44.     // indique une id de google drive  et entre l id comme parametre pour aller chercher le bon fichier
    45.     public void SetGoogleSheetId()
    46.     {
    47.         fileId = inputField.text;
    48.  
    49.     }
    50.  
    51.     //Make the list  of button with google sheet params and spawn all objects in scene
    52.     private void SetResult(File file)
    53.     {
    54.  
    55.         if(NetworkManager.Singleton.IsServer || NetworkManager.Singleton.IsHost) {
    56.         Debug.Log("GetResult from google Drive , waiting to encode and make prefab ");
    57.  
    58.            // decode et transform  le fichier téléchargé en une string
    59.            string eleveData = Encoding.UTF8.GetString(file.Content);
    60.  
    61.            // transforme la string copntenant les données en caracteres UTF8 pour séparer les mots
    62.             string[] data = eleveData.Split(new char[] { '\n' });
    63.  
    64.             // reads the first name from the first word and name from the second
    65.             for (int i = 1; i < data.Length - 1; i++)
    66.             {
    67.                 string[] row = data[i].Split(new char[] { ',' });
    68.                 //instantiate button on server
    69.                 playerGo = Instantiate(playerPrefab, playerList_Scroll_Transform);
    70.  
    71.                 // set the player variables in player component
    72.                 Player player = playerGo.GetComponent<Player>();
    73.                 if (row[0] != "")
    74.                 {
    75.                     player.player_FirstName = row[0];
    76.                     player.player_Name = row[1];
    77.                     player.heureEntree = System.DateTime.MinValue;
    78.                     player.heureSortie = System.DateTime.MinValue;
    79.                     player.playerState = PlayerState.absent;
    80.                     playerGo.name = "" + player.player_FirstName + player.player_Name;
    81.                     GameManager.singleton.AddPlayerToList(player);
    82.                 }
    83.  
    84.                 Debug.Log("Getting parentId ");
    85.                 // Get the parent id of the player object
    86.                 var transformParentId = playerList_Scroll_Transform.GetComponent<NetworkObject>().NetworkObjectId;
    87.  
    88.                 Debug.Log("adding  parentId to playerScript ");
    89.                 //put the parentid to the player component
    90.                 player.parentNetId.Value = (int) transformParentId;
    91.                 //Add the text to the button
    92.                 playerGo.GetComponentInChildren<Text>().text = player.player_FirstName + " " + player.player_Name;
    93.                 Debug.Log("Spawn network player object ");
    94.                 //Spawn player network object
    95.                 playerGo.GetComponent<NetworkObject>().Spawn();
    96.  
    97.             }
    98.         }
    99.     }
    100.  
    101. }
    102.  


    And that's my player's script :

    Code (CSharp):
    1. using MLAPI;
    2. using MLAPI.NetworkVariable;
    3. using UnityEngine;
    4.  
    5.  
    6. public enum PlayerState { absent, present, enAttente }
    7.  
    8. [RequireComponent(typeof(NetworkObject))]
    9. public class Player : NetworkBehaviour
    10. {
    11.     //Player state ( presnt or missing or waiting )
    12.     public PlayerState playerState = PlayerState.absent;
    13.     // players name and surname and hours (at what time does he enter and leave
    14.     public string player_FirstName;
    15.     public string player_Name;
    16.     public System.DateTime heureEntree;
    17.     public System.DateTime heureSortie;
    18.  
    19.     // the id of the parent object( the scroll view content )
    20.     public NetworkVariableInt parentNetId;
    21.  
    22.     // Method to change playerbutton's list ( from playerlist to presentlist )
    23.     public void OnClick()
    24.     {
    25.         GameManager.singleton.ToggleList(this);
    26.     }
    27.  
    28.     private void Start()
    29.     {
    30.         if (NetworkManager.Singleton.IsClient)
    31.         {
    32.             // When we are spawned on the client,
    33.             // find the parent object using its ID,
    34.             // and set it to be our transform's parent.
    35.             NetworkObject[] parentObjectArray = FindObjectsOfType<NetworkObject>();
    36.             for (int i = 0; i < parentObjectArray.Length; i++)
    37.             {
    38.                 if ((int)parentObjectArray[i].NetworkObjectId == parentNetId.Value)
    39.                 {
    40.                     var parentObject = parentObjectArray[i];
    41.                     transform.SetParent(parentObject.transform);
    42.                 }
    43.             }
    44.         }
    45.     }
    46.  
    47.  
    48.     // TO DO  implement the player variables over the network  ( NOT sure if it can work like this )
    49.     //public void SetPlayerStat()
    50.     //{
    51.     //    if (NetworkManager.Singleton.IsServer)
    52.     //    {
    53.     //        playerStats.Value = this;
    54.     //    }
    55.     //    playerState = playerStats.Value.playerState;
    56.     //    player_FirstName = playerStats.Value.player_FirstName;
    57.     //    player_Name = playerStats.Value.player_Name;
    58.     //    heureEntree = playerStats.Value.heureEntree;
    59.     //    heureSortie = playerStats.Value.heureSortie;
    60.     //}
    61.  
    62.     //public NetworkVariable<Text> playerText = new NetworkVariable<Text>(new NetworkVariableSettings
    63.     //{
    64.     //    WritePermission = NetworkVariablePermission.ServerOnly,
    65.     //    ReadPermission = NetworkVariablePermission.Everyone
    66.     //});
    67. }
    in the two pictures you can see from the original that the server doesnt spawn any prefab on the screen

    But in the client playerbutton spawn but not within the good parent tranqform

    the error in the console say :[MLAPI] Cannot find parent. Parent objects always have to be spawned and replicated BEFORE the child

    i don't know if i have to spawn parent gameobject in the script ( actually i tried and i got an other error ) ; also i put the parent gameObject in the player prefab tab within the networkmanager but it doesn t change ...

    1) Do i have to put all possible parents transform ( my 2 scrollview contents) in this tab within Networkmanager component ?


    2) Is that a simpler way to sync the prefab of a gameobject ?
    3) is the fact that my player gameobject is a UIcompoonent (button ) a bad thing ?

    Sorry for the long thread but i 'm learning how all these thing work at the same time and i feel i'm almost succeeding...
     

    Attached Files:

  5. firaui

    firaui

    Joined:
    Nov 14, 2018
    Posts:
    12
    You must apply NetworkObject component to GameObject that you want to spawn for it to work

    something like this
     

    Attached Files:

  6. antromagames

    antromagames

    Joined:
    Oct 20, 2020
    Posts:
    6
    Ok so i fixed it , i used the rpc command to tell where to find the parent transform id and it worked ( that makes sense , also first time i tried i was not enabling the entry panel on the client side so the transform id couldn 't be found ...) But now it works .

    Firaui , thanks for the help ; i already put the network object on playerbutton prefab and on all scroollviews content object .
    I could see that there is a networkTransform component , , i tried to add it but it doesn't seem to sync automatically transform parent ...

    So i put the transform parent id to networkvar , as my code shows belong :

    my player script

    Code (CSharp):
    1. using MLAPI;
    2. using MLAPI.NetworkVariable;
    3. using UnityEngine;
    4. using UnityEngine.UI;
    5.  
    6.  
    7. public enum PlayerState { absent, present, enAttente }
    8.  
    9. [RequireComponent(typeof(NetworkObject))]
    10. public class Player : NetworkBehaviour
    11. {
    12.     [SerializeField] Text buttonText;
    13.  
    14.     //Player state ( presnt or missing or waiting )
    15.     public PlayerState playerState = PlayerState.absent;
    16.     //players name and surname and hours(at what time does he enter and leave
    17.     public string player_FirstName;
    18.     public string player_Name;
    19.     public System.DateTime heureEntree;
    20.     public System.DateTime heureSortie;
    21.  
    22.  
    23.     public NetworkVariableString n_player_FirstName;
    24.     public NetworkVariableString n_player_Name;
    25.  
    26.     public NetworkVariable<PlayerState> n_playerstate;
    27.  
    28.     public NetworkVariableULong parentNetId;
    29.  
    30.     // the id of the parent object( the scroll view content )
    31.     public NetworkVariable<System.DateTime> n_player_heureEntree;
    32.     public NetworkVariable<System.DateTime> n_player_heureSortie;
    33.  
    34.     // Method to change playerbutton's list ( from playerlist to presentlist )
    35.     public void OnClick()
    36.     {
    37.         GameManager.singleton.ToggleList(this);
    38.     }
    39.  
    40.     private void Start()
    41.     {
    42.         if (NetworkManager.Singleton.IsClient)
    43.         {
    44.             SetPlayerTransform();
    45.             SetPlayer();
    46.         }
    47.     }
    48.  
    49.     private void SetPlayerTransform()
    50.     {
    51.         // When we are spawned on the client,
    52.         // find the parent object using its ID,
    53.         // and set it to be our transform's parent.
    54.         NetworkObject[] parentObjectArray = FindObjectsOfType<NetworkObject>();
    55.         for (int i = 0; i < parentObjectArray.Length; i++)
    56.         {
    57.             if (parentObjectArray[i].NetworkObjectId == parentNetId.Value)
    58.             {
    59.                 var parentObject = parentObjectArray[i];
    60.                 transform.SetParent(parentObject.transform);
    61.             }
    62.         }
    63.     }
    64.  
    65.     private void Update()
    66.     {
    67.         //buttonText.text = player_FirstName + "" + player_Name;
    68.         //gameObject.name = buttonText.text;
    69.     }
    70.  
    71.  
    72.     public void SetPlayer()
    73.     {
    74.         if (NetworkManager.Singleton.IsClient)
    75.         {
    76.             player_FirstName = n_player_FirstName.Value;
    77.             player_Name = n_player_Name.Value;
    78.             heureEntree = n_player_heureEntree.Value;
    79.             heureSortie = n_player_heureSortie.Value;
    80.             playerState = n_playerstate.Value;
    81.         }
    82.         buttonText.text = player_FirstName + "" + player_Name;
    83.         gameObject.name = buttonText.text;
    84.     }
    85. }
    and my importer script
    Code (CSharp):
    1. using System;
    2. using System.Collections;
    3. using System.Collections.Generic;
    4. using System.Text;
    5. using UnityEngine;
    6. using UnityEngine.UI;
    7. using UnityGoogleDrive;
    8. using UnityGoogleDrive.Data;
    9. using MLAPI.Messaging;
    10. using MLAPI.NetworkVariable;
    11. using MLAPI;
    12. using MLAPI.Spawning;
    13.  
    14. public class DriveManager : MonoBehaviour
    15. {
    16.  
    17.     // the id of the googlesheet in google drive
    18.     [SerializeField] private string fileId ;
    19.     [SerializeField] InputField inputField;
    20.     // Prefab of playerbutton
    21.     [SerializeField] GameObject playerPrefab;
    22.  
    23.     private GoogleDriveFiles.ExportRequest request;
    24.  
    25.  
    26.     GameObject playerGo;
    27.     [SerializeField] Transform playerList_Scroll_Transform;
    28.  
    29.     //When we click on the validate button for the googlesheet
    30.     public void OnClick()
    31.     {
    32.         SetGoogleSheetId();
    33.         GetDataFromDrive();
    34.     }
    35.  
    36.     // Methode pour aller chercher une liste sur google drive ( file id indiqué dans les paramètres
    37.     private void GetDataFromDrive()
    38.     {
    39.         Debug.Log("Get Data from Google Drive...");
    40.         if (fileId == "") { return; }
    41.             request = GoogleDriveFiles.Export(fileId, "text/csv");
    42.             request.Send().OnDone += SetResult;  
    43.     }
    44.     // indique une id de google drive  et entre l id comme parametre pour aller chercher le bon fichier
    45.     public void SetGoogleSheetId()
    46.     {
    47.         fileId = inputField.text;
    48.  
    49.     }
    50.  
    51.     //Make the list  of button with google sheet params and spawn all objects in scene
    52.     private void SetResult(File file)
    53.     {
    54.  
    55.         if(NetworkManager.Singleton.IsServer || NetworkManager.Singleton.IsHost) {
    56.         Debug.Log("GetResult from google Drive , waiting to encode and make prefab ");
    57.  
    58.            // decode et transform  le fichier téléchargé en une string
    59.            string eleveData = Encoding.UTF8.GetString(file.Content);
    60.  
    61.            // transforme la string copntenant les données en caracteres UTF8 pour séparer les mots
    62.             string[] data = eleveData.Split(new char[] { '\n' });
    63.  
    64.             // reads the first name from the first word and name from the second
    65.             for (int i = 1; i < data.Length - 1; i++)
    66.             {
    67.                 string[] row = data[i].Split(new char[] { ',' });
    68.                 //instantiate button on server
    69.                 playerGo = Instantiate(playerPrefab, playerList_Scroll_Transform);
    70.  
    71.                 // set the player variables in player component
    72.                 Player player = playerGo.GetComponent<Player>();
    73.                 if (row[0] != "")
    74.                 {
    75.                     player.player_FirstName = row[0];
    76.                     player.player_Name = row[1];
    77.                     player.heureEntree = System.DateTime.MinValue;
    78.                     player.heureSortie = System.DateTime.MinValue;
    79.                     player.playerState = PlayerState.absent;
    80.                     //playerGo.name = "" + player.player_FirstName + player.player_Name;
    81.                     GameManager.singleton.AddPlayerToList(player);
    82.                 }
    83.  
    84.  
    85.                 SetNetworkingValues(player);
    86.                 SetTransformetworkingValues(player);
    87.                 //Add the text to the button
    88.                 // playerGo.GetComponentInChildren<Text>().text = player.player_FirstName + " " + player.player_Name;
    89.                 Debug.Log("Spawn network player object ");
    90.                 //Spawn player network object
    91.                 playerGo.GetComponent<NetworkObject>().Spawn();
    92.  
    93.  
    94.             }
    95.         }
    96.     }
    97.     [ServerRpc]
    98.     private void SetNetworkingValues(Player player)
    99.     {
    100.         player.n_player_Name.Value = player.player_Name;
    101.         player.n_player_FirstName.Value = player.player_FirstName;
    102.         player.n_player_heureEntree.Value = player.heureEntree;
    103.         player.n_player_heureSortie.Value = player.heureSortie;
    104.         player.n_playerstate.Value = player.playerState;
    105.     }
    106.     [ServerRpc]
    107.     private void SetTransformetworkingValues(Player player)
    108.     {
    109.         Debug.Log("Getting parentId ");
    110.         // Get the parent id of the player object
    111.         var transformParentId = playerList_Scroll_Transform.GetComponent<NetworkObject>().NetworkObjectId;
    112.         //And spawn it
    113.         //playerList_Scroll_Transform.GetComponent<NetworkObject>().SpawnAsPlayerObject(transformParentId);
    114.         Debug.Log("adding  parentId to playerScript ");
    115.         //put the parentid to the player component
    116.         player.parentNetId.Value = transformParentId;
    117.     }
    118. }
    119.  
    LAst question as i'm a bit confused even if it works , isn't it a simpler way to sync component inside playerprefab, i mean have we always to script a rpc command to set all network var and then set the var in the player prefab as i did?

    I find it redundant and not code efficient so i guess i missed an important concept in syncing ?