Search Unity

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

Bug [Netcode] Changing NetworkVariable in one NetworkeBehavior changesit inside ALL NetworkBehaviours

Discussion in 'Netcode for GameObjects' started by ANLevant, Nov 25, 2023.

  1. ANLevant

    ANLevant

    Joined:
    Jun 10, 2014
    Posts:
    21
    So I'm making a multiplayer card game, based in Triple Triad using Netcode. I have a Card NetworkBehaviour with an attribute called IsBlue, which determines the color of the background for the card (and player currently owning the card)

    Code (CSharp):
    1. public class Card : Interactible //Interacbile is a NetworkBehaviour
    2. {
    3.     ...
    4.     public NetworkVariable<bool> IsBlue = new NetworkVariable<bool>(false);
    5.     ...
    6.    
    7.     void Update()
    8.     {
    9.         if (IsChangingSides.Value)
    10.         {
    11.             ChangeSides();
    12.         }
    13.     }
    14.     ....
    15.     public override void OnNetworkSpawn()
    16.     {
    17.         CardName.OnValueChanged += (FixedString64Bytes previousValue, FixedString64Bytes newValue) => {              ChangeCardMaterial(); };
    18.         IsBlue.OnValueChanged += (bool previousValue, bool newValue) =>
    19.         {
    20.             Debug.Log("Changed Color!");
    21.             ChangeCardMaterial();
    22.         };
    23.     ...  
    24.     private void ChangeSides()
    25.     {
    26.         ....
    27.         ChangeColorClientRpc(!IsBlue.Value);
    28.         IsChangingSides.Value = false;
    29.         RevealCardClientRpc();
    30.     }
    31.     ...
    32.     [ClientRpc]
    33.     public void ChangeColorClientRpc(bool isBlue)
    34.     {
    35.         Debug.Log(name);
    36.         IsBlue.Value = isBlue;
    37.         ChangeCardMaterial();
    38.     }
    39.     ....
    40. }
    41.  
    When I first start the game, I randomly decide which player will be the first one and change the value of their IsBlue variable individually inside the server, then I register a listener for the OnValueChanged event, which updates the material for each card. This is working like a charm

    Code (CSharp):
    1.  
    2. public class GameController : NetworkBehaviour
    3. {
    4.     ...
    5.     public Hand[] PlayerHands;
    6.     ...
    7.     private void SetupCards()
    8.     {
    9.         foreach (Hand playerHand in PlayerHands)
    10.         {
    11.             foreach (Card card in playerHand.cards)
    12.             {
    13.                 ...
    14.                 card.IsBlue = card.OwningPlayer.IsBlue;
    15.                 ...
    16.             }
    17.         }
    18.     }
    19. ...
    20. }
    21.  
    Now, when playing, after a player makes a move and takes the enemy's card, the card is supposed to change colors, but when I change the IsBlue value for the card, it changes on all of the other cards too! wht's worse, the OnValueChanged listener for the variable is never called!

    Code (CSharp):
    1. public class Player : NetworkBehaviour
    2. {
    3.     ...
    4.     //This is the entry point for players using their card, this triggers ther est of the process
    5.     [ServerRpc(RequireOwnership = false)]
    6.     public void SelectInteractibleServerRpc(NetworkObjectReference interactibleNetworkReference,
    7.         ServerRpcParams serverRpcParams = default)
    8.     {
    9.         NetworkObject rawInteractible;
    10.         bool couldFetchIteractible = interactibleNetworkReference.TryGet(out rawInteractible);
    11.  
    12.         if (couldFetchIteractible)
    13.         {
    14.             Interactible interactible = rawInteractible.gameObject.GetComponent<Interactible>();
    15.             if (IsPlaying.Value && (rawInteractible.OwnerClientId == serverRpcParams.Receive.SenderClientId || interactible is BoardSlot))
    16.             {
    17.                 ...
    18.                 interactible.ToggleSelect();
    19.                ....
    20.            }
    21.         ...
    22.         }
    23.     ...
    24.     }
    25. ....
    26. }
    Code (CSharp):
    1. public class BoardSlot : Interactible //Interacbile is a NetworkBehaviour
    2. {
    3.     private Card OccupyingCard;
    4.  
    5.     public BoardSlot TopSlot;
    6.     ...
    7.     private void CalculateMove()
    8.     {
    9.         if (TopSlot != null && TopSlot.OccupyingCard != null && TopSlot.OccupyingCard.BottomValue.Value < OccupyingCard.TopValue.Value && TopSlot.OccupyingCard.IsBlue.Value != OccupyingCard.IsBlue.Value)
    10.         {
    11.             TopSlot.OccupyingCard.IsChangingSides.Value = true;
    12.             Debug.Log("Brute Force Taken!");
    13.         }
    14. ...
    15. }
    As a footnote, I am now changing the value inside an RPC because, as mentioned above, when I tried changing it inside the server itself, it was just changing the value for the variable for all Card NetworkObjects in scene, never calling the listener so the materials where never updating. I decided to create a ClientRpc and manually chagne the material, but the issue of all cards having their NetworkVariable changed at the same time persisted when I change the variable inside the ClientRpc

    Am I doing something wrong? Or am I just the unluckiest person in the world to stumble upon two bugs at the same time? Help please :(
     
  2. NoelStephens_Unity

    NoelStephens_Unity

    Unity Technologies

    Joined:
    Feb 12, 2022
    Posts:
    244
    When you exchange cards do you change the ownership of the card(s)?
    Without all of your scripts, I just whipped together a quick example of how I would go about handling changing color based on ownership and using NetworkVariables:

    Code (CSharp):
    1.  
    2. /// <summary>
    3. /// Add this to your player prefab
    4. /// The player color selector assigns a color to a spawned player (you can adjust the colors however you want, but the general idea)
    5. /// </summary>
    6. public class PlayerColorSelector : NetworkBehaviour
    7. {
    8.     private static Color[] s_Colors = { Color.red, Color.yellow, Color.green, Color.blue, Color.cyan, Color.magenta, Color.white };
    9.     public NetworkVariable<Color> PlayerColor = new NetworkVariable<Color>();
    10.     public override void OnNetworkSpawn()
    11.     {
    12.         PlayerColor.Value = s_Colors[OwnerClientId % Convert.ToUInt64(s_Colors.Length)];
    13.         base.OnNetworkSpawn();
    14.     }
    15. }
    16. /// <summary>
    17. /// Add this to your card(s) prefab(s)
    18. /// The owner always sets the CardColor which will be synchronized with everyone when it changes
    19. /// </summary>
    20. public class PlayingCardColor : NetworkBehaviour
    21. {
    22.     public NetworkVariable<Color> CardColor = new NetworkVariable<Color>(default, NetworkVariableReadPermission.Everyone, NetworkVariableWritePermission.Owner);
    23.     private PlayerColorSelector m_PlayerColorSelector;
    24.     private MeshRenderer m_MeshRenderer;
    25.     private void Awake()
    26.     {
    27.         m_MeshRenderer = GetComponent<MeshRenderer>();
    28.     }
    29.     public override void OnNetworkSpawn()
    30.     {
    31.         // Everyone subscribes to when the color of the card changes
    32.         CardColor.OnValueChanged += OnCardColorChanged;
    33.         // All instances assign their own relative player color selector to each *local* card instance.
    34.         // This assures when ownership changes the owner will apply its color to the card (which will then get synchronized with everyone)
    35.         m_PlayerColorSelector = NetworkManager.LocalClient.PlayerObject.GetComponent<PlayerColorSelector>();
    36.         // When the card is first spawned, it is assigned the current owner's color
    37.         if (IsOwner)
    38.         {
    39.             SetCardPlayerColor();
    40.         }
    41.         // Everyone applies the current card color when the card is spawned
    42.         ApplyCurrentOwnerColor();
    43.         base.OnNetworkSpawn();
    44.     }
    45.     private void ApplyCurrentOwnerColor()
    46.     {
    47.         // Since the script to change the color wasn't provided, I just am applying the color to the
    48.         // MeshRenderer's material's color
    49.         m_MeshRenderer.material.color = CardColor.Value;
    50.     }
    51.     private void OnCardColorChanged(Color previous, Color current)
    52.     {
    53.         ApplyCurrentOwnerColor();
    54.     }
    55.     /// <summary>
    56.     /// Requires NGO v1.7.0 or higher
    57.     /// When ownership of the card changes, the owner will apply its assigned color
    58.     /// to the card
    59.     /// </summary>
    60.     protected override void OnOwnershipChanged(ulong previous, ulong current)
    61.     {
    62.         if (current == NetworkManager.LocalClientId)
    63.         {
    64.             SetCardPlayerColor();
    65.         }
    66.         base.OnOwnershipChanged(previous, current);
    67.     }
    68.     /// <summary>
    69.     /// Uncomment this for anything below NGO v1.7.0
    70.     /// When ownership of the card changes, the owner will apply its assigned color
    71.     /// to the card
    72.     /// (also comment out the above OnOwnershipChanged)
    73.     /// </summary>
    74.     //public override void OnGainedOwnership()
    75.     //{
    76.     //    // We do this check because this is triggered on the server-host side too when a client gains ownership
    77.     //    if (IsOwner)
    78.     //    {
    79.     //        SetCardPlayerColor();
    80.     //    }
    81.     //    base.OnGainedOwnership();
    82.     //}
    83.     /// <summary>
    84.     /// Just made this a method in the event you have
    85.     /// </summary>
    86.     private void SetCardPlayerColor()
    87.     {
    88.         // Just a safety check in the event, later, you invoke this as a non-owner
    89.         if (!IsOwner)
    90.         {
    91.             Debug.LogWarning($"Client-{NetworkManager.LocalClientId} is trying to set the card color when the card is owned by Client-{OwnerClientId}!");
    92.             return;
    93.         }
    94.         // Owner applies its player assigned color to the CardColor NetworkVariable
    95.         CardColor.Value = m_PlayerColorSelector.PlayerColor.Value;
    96.     }
    97. }
    98.  
    Let me know if this helps you resolve your issue?
     
    Last edited: Nov 26, 2023
  3. ANLevant

    ANLevant

    Joined:
    Jun 10, 2014
    Posts:
    21

    I do! i forgot to flag this as solved.
    The problem was on my code, I was setting the network variable IsBlue of the card as the IsBlue variable of the played, which obvioously misconfigured everything haha so yeah, my bad xD
     
    NoelStephens_Unity likes this.