Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Dismiss Notice

Client is not allowed to write to this Network Variable

Discussion in 'Netcode for GameObjects' started by AltTabb3d, Jun 11, 2023.

  1. AltTabb3d

    AltTabb3d

    Joined:
    Nov 14, 2020
    Posts:
    5
    Hi. I'm just starting to learn how to use ServerRPCs and Network Variables using Unity's new netcode system, and I believe I'm running into a Write Permissions issue, but I need a little insight.

    For context, I am making a bomberman style game where players drop bombs to try and kill each other. Before I learned how to use the netcode, I would have the player gameobject spawn bombs, and then the "stats" of the bombs (things like power, piercing, and remote control) would be synchronized by serializing my "PlayerStats" class component into the bomb gameobject, and then the bomb would be able to customize itself using that information. Then when I learned about netcode, I realized that I can't synchronize and network serialize a class over the network, but I could do that with a struct. So I created a custom struct that I use to serialize the stats from the player and send them over the network so that the server can use them to customize the bomb appropriately.

    I'm running into some issues doing this, however. Whenever I spawn a bomb using the host player, I don't get any errors. But if I attempt to spawn a bomb using a client player, I run into an error, which I will attach in a screenshot. Here is the code used inside the "PlayerStats" class component:

    Code (CSharp):
    1.  
    2.     public struct PlayerStatsStruct : INetworkSerializable
    3.     {
    4.         public int _health;
    5.         public int _power;
    6.         public int _speed;
    7.         public int _ammo;
    8.         public bool _bombKick;
    9.         public bool _lineBomb;
    10.         public bool _piercing;
    11.         public bool _satellite;
    12.         public Character _character;
    13.         public void NetworkSerialize<T>(BufferSerializer<T> serializer) where T : IReaderWriter
    14.         {
    15.             serializer.SerializeValue(ref _health);
    16.             serializer.SerializeValue(ref _power);
    17.             serializer.SerializeValue(ref _speed);
    18.             serializer.SerializeValue(ref _ammo);
    19.             serializer.SerializeValue(ref _bombKick);
    20.             serializer.SerializeValue(ref _lineBomb);
    21.             serializer.SerializeValue(ref _piercing);
    22.             serializer.SerializeValue(ref _satellite);
    23.             serializer.SerializeValue(ref _character);
    24.         }
    25.     }
    26.     public void SerializeVariables()
    27.     {
    28.         playerStatsNetworkVariable = new NetworkVariable<PlayerStatsStruct>(
    29.             new PlayerStatsStruct
    30.             {
    31.                 _health = health,
    32.                 _power = power,
    33.                 _speed = speed,
    34.                 _ammo = ammo,
    35.                 _bombKick = bombKick,
    36.                 _lineBomb = lineBomb,
    37.                 _piercing = piercing,
    38.                 _satellite = satellite,
    39.                 _character = character,
    40.              }, NetworkVariableReadPermission.Everyone, NetworkVariableWritePermission.Owner);
    41.     }
    42.  
    And here is the ServerRPC inside of my "PlayerBehaviour" class component that actually spawns the bomb:

    Code (CSharp):
    1.  
    2.     [ServerRpc]
    3.     void PlaceBombServerRpc(Vector3 gridSpace)
    4.     {
    5.         var placedBomb = Instantiate(bomb, gridSpace, Quaternion.identity);
    6.         placedBomb.GetComponent<NetworkObject>().Spawn(true);
    7.         placedBomb.layer = bombLayer.Value;
    8.         placedBomb.name = _clientID.Value + _bombString.Value.ToString() + _bombNum.Value;
    9.         playerStats.SerializeVariables();
    10.         var bombBehaviour = placedBomb.GetComponent<BombBehaviour>();
    11.         bombBehaviour.playerStatsNetworkVariable = playerStats.playerStatsNetworkVariable;
    12.         bombBehaviour.bombsPlacedNetworkVariable = playerStats.bombsPlaced;
    13.         _bombNum.Value++;
    14.     }
    15.  
    You'll notice there are a couple of extra things I didn't mention, like naming and assigning a layer to the bomb when it is spawned. the layer assignment works from both sides, but the _bombNum value only updates from the server side, meaning all bombs spawned by a client always spawn as "0 Bomb 0". It doesn't really matter because I'm only worried about the
    Code (CSharp):
    1. bombBehaviour.playerStatsNetworkVariable = playerStats.playerStatsNetworkVariable;
    part because it doesn't seem like the power of the bombs is being properly assigned, and I'm receiving an error that the client is not allowed to write to the object.

    Any insight would be greatly appreciated, please let me know if you need any more information, hopefully I provided enough.
     

    Attached Files:

  2. CodeSmile

    CodeSmile

    Joined:
    Apr 10, 2014
    Posts:
    4,063
    You are probably just missing the RequiresOwnership=false parameter in the ServerRPC attribute.

    Regarding your struct I advise against this design. You are packing all the game stats into a single struct which is already 36 bytes minimum. Every time a single attribute changes, like health, the entire struct needs to be sent across the network. This is highly inefficient. It may work for now, it may work okay for a very simple game, but this design simply does not scale. Instead it would be better to have every attribute that changes frequently as a networkvariable itself. And whatever changes rarely, like kicking a bomb, should be a ServerRPC. Actually I would say that these bool probably don‘t need to be state at all but just (literally) kick off an action.

    Lastly, even if you go ahead with this, you can improve the efficiency by packing all bools (16 bytes) into a single byte of bit flags, and possibly reduce the size of other variables to short or byte.
     
  3. AltTabb3d

    AltTabb3d

    Joined:
    Nov 14, 2020
    Posts:
    5
    SOLID ADVICE BROTHER.

    I'll check it out when I get home and update if it resolves the issue.
     
  4. AltTabb3d

    AltTabb3d

    Joined:
    Nov 14, 2020
    Posts:
    5
    I'm sorry this is a late reply, but how do you make bit flags? I would like save on bandwidth, but I've never done that before. I can't find a simple explanation online, would you mind explaining it to me simply?
     
  5. robotgrapefruit

    robotgrapefruit

    Joined:
    May 12, 2014
    Posts:
    4
    A byte has 8 bits (1 or 0) so with binary operators you can store and read boolean values in each bit.
    For example you can check if the fourth bit is a 1 using
    Code (CSharp):
    1. (_flags & (1 << 3)) != 0
    Enums have a HasFlag method that checks flags in a more readable way. You can define the underlying type of the enum as byte or else it will default to 32bit int
    Code (CSharp):
    1. [System.Flags] public enum EnumName : [B]byte [/B]{
    2.     None = 0,
    3.     Flag1 = 1,
    4.     Flag2 = 2,
    5.     Flag3 = 4
    6. }
     
    Last edited: Jul 19, 2023