Search Unity

  1. Unity support for visionOS is now available. Learn more in our blog post.
    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:
    368
    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:
    638
    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:
    368
    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:
    638
    Ah okay, I'll have a look at it again tomorrow.
     
  5. cerestorm

    cerestorm

    Joined:
    Apr 16, 2020
    Posts:
    638
    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:
    368
    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:
    638
    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:
    33
    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`)