Search Unity

  1. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

Question how to synchronize variables? (boolean for example)

Discussion in 'Netcode for GameObjects' started by foxyyhappyw, Jul 29, 2023.

  1. foxyyhappyw

    foxyyhappyw

    Joined:
    Aug 8, 2022
    Posts:
    59
    maybe I'm stupid, but since yesterday I started learn Netcode and I have one problem - How can I synchronize a boolean value?

    I have a problem that when changing the boolean value "isSpawned.Value" - it changes only for the person who executed the function (pressed "F"), not for the server (spawning an object works for both players).
    The problem is that when one player presses "F" and spawns an object, and then the other player also presses "F", the object spawns twice because the server doesn't know that one object already exists.

    I know the solution is probably simple, but I have no idea.

    My current code:
    Code (CSharp):
    1. using UnityEngine;
    2. using Unity.Netcode;
    3.  
    4. public class PlayerNetwork : NetworkBehaviour
    5. {
    6.     [SerializeField] private Transform spawnedObjectPrefab;
    7.     private Transform spawnedObjectTransform;
    8.  
    9.     private NetworkVariable<bool> isSpawnedNetwork = new NetworkVariable<bool>(false, NetworkVariableReadPermission.Everyone, NetworkVariableWritePermission.Server);
    10.  
    11.     private void Update()
    12.     {
    13.         if (!IsOwner) return;
    14.  
    15.         if (Input.GetKeyDown(KeyCode.F))
    16.         {
    17.             TestServerRpc();
    18.         }
    19.  
    20.         Debug.Log(isSpawnedNetwork.Value);
    21.  
    22.         // Simple movement system (only for testing)
    23.         Vector3 moveDir = new Vector3(0, 0, 0);
    24.         if (Input.GetKey(KeyCode.W)) moveDir.z = +1f;
    25.         if (Input.GetKey(KeyCode.S)) moveDir.z = -1f;
    26.         if (Input.GetKey(KeyCode.A)) moveDir.x = -1f;
    27.         if (Input.GetKey(KeyCode.D)) moveDir.x = +1f;
    28.         float moveSpeed = 3f;
    29.         transform.position += moveDir * moveSpeed * Time.deltaTime;
    30.     }
    31.    
    32.     [ServerRpc(RequireOwnership = false)]
    33.     private void TestServerRpc()
    34.     {
    35.         if (!isSpawnedNetwork.Value)
    36.         {
    37.             spawnedObjectTransform = Instantiate(spawnedObjectPrefab);
    38.             spawnedObjectTransform.GetComponent<NetworkObject>().Spawn(true);
    39.  
    40.             isSpawnedNetwork.Value = true;
    41.             Debug.Log("Client " + OwnerClientId + " spawned object :D" + " | bool: " + isSpawnedNetwork.Value);
    42.         }
    43.         else
    44.         {
    45.             Destroy(spawnedObjectTransform.gameObject);
    46.  
    47.             isSpawnedNetwork.Value = false;
    48.             Debug.Log("Client " + OwnerClientId + " destroyed object :(" + " | bool: " + isSpawnedNetwork.Value);
    49.         }
    50.     }
    51.  
    52. }
    53.  

    (the objects have the same position, so you can't see that they are duplicates, but they are lol)
     
  2. cerestorm

    cerestorm

    Joined:
    Apr 16, 2020
    Posts:
    638
    How you handle this depends on your preference as there's numerous ways to do it. You can do a server check or a client check but preferably both as you can never trust the client.

    On the server you can have some sort of manager that keeps track of spawns so that the next time there's a spawn rpc call it can check against that to decide whether to spawn that object.

    On the client side you can use the OnNetworkSpawn() callback of the spawned object to set a boolean somewhere on the client and check that before sending the spawn rpc.
     
    foxyyhappyw likes this.
  3. Mj-Kkaya

    Mj-Kkaya

    Joined:
    Oct 10, 2017
    Posts:
    175
    Hi @foxyyhappyw ,

    This happens because, "isSpawnedNetwork" is not a global value, it's "PlayerNetwork" variable.
    So there are 2 "PlayerNetwork" object in the scene, this means there is 2 "isSpawnedNetwork" variable and these are independent.
    When HostPlayer triggers the "TestServerRpc();" method, HostPlayer's "PlayerNetwork" object receives this event.
    And when ClientPlayer triggers the "TestServerRpc();" method, ClientPlayer's "PlayerNetwork" object receives this event.

    Simple solution: You can create scene object and hold the isSpawnedNetwork variable on this.
     
    foxyyhappyw likes this.
  4. foxyyhappyw

    foxyyhappyw

    Joined:
    Aug 8, 2022
    Posts:
    59
    Thanks for your response!
    I just changed the code a bit. It seems to work, but I'm still not sure if it's a good solution xD

    Code (CSharp):
    1. using UnityEngine;
    2. using Unity.Netcode;
    3.  
    4. public class PlayerNetwork : NetworkBehaviour
    5. {
    6.     [SerializeField] private Transform spawnedObjectPrefab;
    7.     private Transform spawnedObjectTransform;
    8.  
    9.     private static NetworkVariable<bool> isSpawned = new NetworkVariable<bool>(false, NetworkVariableReadPermission.Everyone, NetworkVariableWritePermission.Server);
    10.     private bool isSpawned_Host;
    11.  
    12.     private void Update()
    13.     {
    14.         if (!IsOwner) return;
    15.  
    16.         if (Input.GetKeyDown(KeyCode.F))
    17.         {
    18.             TestServerRpc();
    19.         }
    20.  
    21.         if (IsHost)
    22.         {
    23.             if (isSpawned.Value && !isSpawned_Host)
    24.             {
    25.                 isSpawned_Host = true;
    26.                 Debug.Log("object spawned");
    27.  
    28.                 spawnedObjectTransform = Instantiate(spawnedObjectPrefab);
    29.                 spawnedObjectTransform.GetComponent<NetworkObject>().Spawn(true);
    30.             }
    31.             else if (!isSpawned.Value && isSpawned_Host)
    32.             {
    33.                 isSpawned_Host = false;
    34.                 Debug.Log("object destoryed");
    35.  
    36.                 Destroy(spawnedObjectTransform.gameObject);
    37.             }
    38.         }
    39.  
    40.         // Simple test movement system
    41.         Vector3 moveDir = new Vector3(0, 0, 0);
    42.         if (Input.GetKey(KeyCode.W)) moveDir.z = +1f;
    43.         if (Input.GetKey(KeyCode.S)) moveDir.z = -1f;
    44.         if (Input.GetKey(KeyCode.A)) moveDir.x = -1f;
    45.         if (Input.GetKey(KeyCode.D)) moveDir.x = +1f;
    46.         float moveSpeed = 3f;
    47.         transform.position += moveDir * moveSpeed * Time.deltaTime;
    48.     }
    49.  
    50.     [ServerRpc(RequireOwnership = false)]
    51.     private void TestServerRpc()
    52.     {
    53.         isSpawned.Value = !isSpawned.Value;
    54.     }
    55.  
    56. }
    57.  
     
    Last edited: Jul 29, 2023
  5. Mj-Kkaya

    Mj-Kkaya

    Joined:
    Oct 10, 2017
    Posts:
    175
    I don't think this is a good approach, I can suggest you this documentation page.
    And as I mentioned before, use another NetworkObject for this variable.
     
    foxyyhappyw likes this.
  6. cerestorm

    cerestorm

    Joined:
    Apr 16, 2020
    Posts:
    638
    I think this will achieve what you want, within the scope of what you're asking, as the static isSpawned will be changed across Player objects. You can likely do away with isSpawned_Host.
     
    foxyyhappyw likes this.
  7. foxyyhappyw

    foxyyhappyw

    Joined:
    Aug 8, 2022
    Posts:
    59
    okay, thanks - I will try that too :D
     
    Mj-Kkaya likes this.
  8. foxyyhappyw

    foxyyhappyw

    Joined:
    Aug 8, 2022
    Posts:
    59
    i think the isSpawned_Host boolean is needed, because without it the object spawns all the time, in a loop
     
  9. cerestorm

    cerestorm

    Joined:
    Apr 16, 2020
    Posts:
    638
    That's true, I wouldn't handle the spawning/despawning that way though, I'd let the rpc handle it.
     
  10. foxyyhappyw

    foxyyhappyw

    Joined:
    Aug 8, 2022
    Posts:
    59
    I don't know if that's what you meant, but I did it like this:
    I made 2 scripts, where one is on the player and the other which controls the spawning of the object on a separate object in the hierarchy.

    Script attached on player object:
    Code (CSharp):
    1. using UnityEngine;
    2. using Unity.Netcode;
    3.  
    4. public class PlayerNetwork : NetworkBehaviour
    5. {
    6.     public NetworkObjectsManager NetworkObjectsManagerScript;
    7.  
    8.     public override void OnNetworkSpawn()
    9.     {
    10.         NetworkObjectsManagerScript = FindObjectOfType<NetworkObjectsManager>();
    11.     }
    12.  
    13.     private void Update()
    14.     {
    15.         if (!IsOwner) return;
    16.  
    17.         if (Input.GetKeyDown(KeyCode.F))
    18.         {
    19.             TestServerRpc();
    20.         }
    21.  
    22.         // Simple test movement system
    23.         Vector3 moveDir = new Vector3(0, 0, 0);
    24.         if (Input.GetKey(KeyCode.W)) moveDir.z = +1f;
    25.         if (Input.GetKey(KeyCode.S)) moveDir.z = -1f;
    26.         if (Input.GetKey(KeyCode.A)) moveDir.x = -1f;
    27.         if (Input.GetKey(KeyCode.D)) moveDir.x = +1f;
    28.         float moveSpeed = 3f;
    29.         transform.position += moveDir * moveSpeed * Time.deltaTime;
    30.     }
    31.  
    32.     [ServerRpc(RequireOwnership = false)]
    33.     private void TestServerRpc()
    34.     {
    35.         NetworkObjectsManagerScript.isSpawned.Value = !NetworkObjectsManagerScript.isSpawned.Value;
    36.     }
    37.  
    38. }
    39.  
    Script attached on Empty GameObject in Hierarchy:
    Code (CSharp):
    1. using UnityEngine;
    2. using Unity.Netcode;
    3.  
    4. public class NetworkObjectsManager : NetworkBehaviour
    5. {
    6.     public NetworkVariable<bool> isSpawned = new NetworkVariable<bool>(false, NetworkVariableReadPermission.Everyone, NetworkVariableWritePermission.Server);
    7.  
    8.     [SerializeField] private Transform spawnedObjectPrefab;
    9.     private Transform spawnedObjectTransform;
    10.     private bool isSpawned_Host;
    11.  
    12.     public override void OnNetworkSpawn()
    13.     {
    14.         isSpawned.OnValueChanged += OnStateChanged;
    15.     }
    16.  
    17.     public override void OnNetworkDespawn()
    18.     {
    19.         isSpawned.OnValueChanged -= OnStateChanged;
    20.     }
    21.  
    22.     public void OnStateChanged(bool previous, bool current)
    23.     {
    24.         if (IsHost)
    25.         {
    26.             if (isSpawned.Value && !isSpawned_Host)
    27.             {
    28.                 isSpawned_Host = true;
    29.                 Debug.Log("object spawned");
    30.  
    31.                 spawnedObjectTransform = Instantiate(spawnedObjectPrefab);
    32.                 spawnedObjectTransform.GetComponent<NetworkObject>().Spawn(true);
    33.             }
    34.             else if (!isSpawned.Value && isSpawned_Host)
    35.             {
    36.                 isSpawned_Host = false;
    37.                 Debug.Log("object destoryed");
    38.  
    39.                 Destroy(spawnedObjectTransform.gameObject);
    40.             }
    41.         }
    42.     }
    43. }
    44.  
     
  11. Mj-Kkaya

    Mj-Kkaya

    Joined:
    Oct 10, 2017
    Posts:
    175
    Yes, this is better.
     
    foxyyhappyw likes this.