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. We have updated the language to the Editor Terms based on feedback from our employees and community. Learn more.
    Dismiss Notice

Question Am I misunderstanding OnValueChanged() in Netcode?

Discussion in 'Netcode for GameObjects' started by SamohtVII, Jul 19, 2023.

  1. SamohtVII

    SamohtVII

    Joined:
    Jun 30, 2014
    Posts:
    366
    I hope I can explain this simply.

    I have 3 scenes open, a server and 2 clients with this network variable.

    Code (CSharp):
    1. public NetworkVariable<HandleState.WeaponStateRW> currentServerWeaponState = new();
    2. private void OnServerStateChanged(HandleState.WeaponStateRW previousValue, HandleState.WeaponStateRW newValue)
    3.    {
    4.        Debug.Log("previousValue " + previousValue.equippedWeapon);
    5.        Debug.Log("newValue " + newValue.equippedWeapon);
    6.        previousWeaponState = previousValue;
    7.    }
    8.  
    9.    private void OnEnable()
    10.    {
    11.        currentServerWeaponState.OnValueChanged += OnServerStateChanged;
    12.    }
    So when the value changes it should print the 2 values and changed the weapon.

    This Debug statement will show correctly on the server so the weapons with be different (i.e. weapon 1 and weapon2)

    It will show the same weapon for both Debugs when on either client. Am i missing something on how this should work so I can see if the player has changed gun on the clients?

    How can i keep track of the last weapon and the newly changed to weapon?

    Thanks

    Here is a condensed serverRPC for changing weapon

    Code (CSharp):
    1. [ServerRpc]
    2. private void ChangeWeaponWithServerTickServerRpc(int tick, int _currentWeapon, bool _aiming)
    3.    {
    4.        HandleState.WeaponStateRW weaponState = new()
    5.        {
    6.            tick = tick,
    7.            equippedWeapon = _currentWeapon,
    8.            aiming = _aiming
    9.        };
    10.  
    11.        previousWeaponState = currentServerWeaponState.Value;
    12.        currentServerWeaponState.Value = weaponState;
    13. }
     
  2. cerestorm

    cerestorm

    Joined:
    Apr 16, 2020
    Posts:
    610
    I did a very crude test and what you're trying should be working. These are the classes I used:

    Code (CSharp):
    1. public class HandleState
    2. {
    3.     public struct WeaponStateRW : INetworkSerializeByMemcpy
    4.     {
    5.         int tick;
    6.         int equippedWeapon;
    7.         bool aiming;
    8.  
    9.         public WeaponStateRW(int tick, int equippedWeapon, bool aiming)
    10.         {
    11.             this.tick = tick;
    12.             this.equippedWeapon = equippedWeapon;
    13.             this.aiming = aiming;
    14.         }
    15.  
    16.         public override string ToString()
    17.         {
    18.             StringBuilder builder = new StringBuilder("WeaponStateRW");
    19.             builder.Append(" tick ").Append(tick);
    20.             builder.Append(" equippedWeapon ").Append(equippedWeapon);
    21.             builder.Append(" aiming ").Append(aiming);
    22.  
    23.             return builder.ToString();
    24.         }
    25.     }
    26. }
    Code (CSharp):
    1. public class NetVarPlayer : NetworkBehaviour
    2. {
    3.     HandleState.WeaponStateRW previousWeaponState;
    4.  
    5.     public NetworkVariable<HandleState.WeaponStateRW> currentServerWeaponState = new NetworkVariable<HandleState.WeaponStateRW>();
    6.  
    7.     private void OnServerStateChanged(HandleState.WeaponStateRW previousValue, HandleState.WeaponStateRW newValue)
    8.     {
    9.         Debug.Log("Player: " + NetworkObject.NetworkObjectId);
    10.         Debug.Log("previousValue " + previousValue);
    11.         Debug.Log("newValue " + newValue);
    12.         previousWeaponState = previousValue;
    13.     }
    14.  
    15.     private void OnEnable()
    16.     {
    17.         currentServerWeaponState.OnValueChanged += OnServerStateChanged;
    18.     }
    19.  
    20.     private void Start()
    21.     {
    22.         if (IsLocalPlayer)
    23.         {
    24.             ChangeWeaponWithServerTickServerRpc(NetworkManager.Singleton.LocalTime.Tick, (int)NetworkObject.NetworkObjectId, true);
    25.         }
    26.     }
    27.  
    28.     [ServerRpc]
    29.     private void ChangeWeaponWithServerTickServerRpc(int tick, int currentWeapon, bool aiming)
    30.     {
    31.         HandleState.WeaponStateRW weaponState = new HandleState.WeaponStateRW(tick, currentWeapon, aiming);
    32.  
    33.         previousWeaponState = currentServerWeaponState.Value;
    34.         currentServerWeaponState.Value = weaponState;
    35.     }
    36. }
     
  3. SamohtVII

    SamohtVII

    Joined:
    Jun 30, 2014
    Posts:
    366
    I was reading the docs and I found this bit

    Netcode has made efforts to minimize Garbage Collected allocations for managed INetworkSerializable types (for example, a new value is only allocated if the value changes from null to non-null).

    That is the reason yours works because it starts at null and changes. I believe if you tried to change it again it wouldn't work.

    This matches my problem because I realised it is showing the correct previousValue and newValue when the previous value is initially null.

    So I guess my question is how do i make it so it changes when currentWeapon changes from 0 to 1?

    Thanks

    https://docs-multiplayer.unity3d.com/netcode/current/basics/networkvariable/
     
  4. cerestorm

    cerestorm

    Joined:
    Apr 16, 2020
    Posts:
    610
    Ah okay, I'll have a look at it again tomorrow.
     
  5. cerestorm

    cerestorm

    Joined:
    Apr 16, 2020
    Posts:
    610
    I put the rpc call in Update and the old and new values are correct for each call.

    It sounds like your WeaponStateRW is a class where mine is a struct so it won't be null. Try changing yours to a struct if possible and if not post its code and I'll test with that instead.
     
  6. jackward84

    jackward84

    Joined:
    Jan 26, 2017
    Posts:
    87
    If you want to update a networkvariable reference without changing the instance you need to do it like this:

    Code (CSharp):
    1. private NetworkVariable<Weapon> weapon = new NetworkVariable<Weapon>();
    2.  
    3. void UpdateWeaponDamage(int newDamage)
    4. {
    5.     weapon.Value.weaponDamage = 50;
    6.     weapon.SetDirty(true);
    7. }
    That will trigger the variable to be synced to clients via NetworkSerialize (and thus trigger OnValueChanged).
     
  7. SamohtVII

    SamohtVII

    Joined:
    Jun 30, 2014
    Posts:
    366
    This seems to be the issue. Thanks
    Is there any problem or difference in using a struct?

    Thanks
     
  8. cerestorm

    cerestorm

    Joined:
    Apr 16, 2020
    Posts:
    610
    Keeping in mind they're value types they're often preferred over classes due to less overhead. I've also found them useful in networking to replace multiple fields where I wanted their updates to appear on the client at the same time.
     
  9. neviovalsa

    neviovalsa

    Joined:
    Jun 24, 2019
    Posts:
    30
    Not the issue here but you also shouldn't subscribe to network variables onchanged events inside `OnEnable`, rather do that on `OnNetworkSpawn`.

    (and don't forget to unsubscribe `OnNetworkDespawn`)