Network Variables not synchronized on non-owner late joined client

Hello,

I have a problem with synchronizing network variables when using Distributed Authority mode. My code is pretty simple:

  • Each player that joins spawns his PlayerObject
  • PlayerObject has one NetworkVariable
  • PlayerObject ownership is set to None - as per documentation

The problem is that when SessionOwner (host) spawns his PlayerObject first (as he loads his scene first), the client who joins later will not have host’s PlayerObject network variable synced correctly.

However, host will have client’s PlayerObject NetworkVariable synced correctly. Also, when I wait for all players to be fully connected before spawning any PlayerObject, everything works as intended.

Method to spawn Local Player - Called on every client after he is connected:

private void SpawnPlayer()
{
   var player = Instantiate(humanPlayerPrefab);
   player.NetworkObject.SpawnAsPlayerObject(NetworkManager.LocalClientId, destroyWithScene: true);       
}

Network variable initialization in Player.cs script:

NetworkVariable<float> _timeLeft = new();
...
public override void OnNetworkSpawn()
{
   base.OnNetworkSpawn();
   if (HasAuthority) _timeLeft.Value = _playerAssets.DefaultTimer; //15 minutes
   _timeLeft.OnValueChanged += OnTimeLeftChanged;
}

Initial value is set correctly - to 15 minutes, but OnValueChanged never triggers on late joining client - for Host’s owned PlayerObject. The NetworkVariable itself is not synchronized at all.

If anyone is interested on how I update the timeLeft value:

protected virtual void Update()
{
   if (HasAuthority && IsMyTurn())
   {
      _oneSecondTimer += Time.deltaTime;
      if (_oneSecondTimer >= 1f)
      {
         _timeLeft.Value -= _oneSecondTimer;
         _oneSecondTimer = 0f;
      }
   }
}

To reduce network traffic I update it every one second.

Hi @digifoxreg,

When are you spawning the session owner’s player?
Using the Distributed authority general quick start ConnectionManager as a reference, are you spawning the player prefabs right after MultiplayerService.Instance.CreateOrJoinSessionAsync or are you waiting for the NetworkManager.OnClientConnectedCallback to be invoked?

When and where does this get called?

The host (or generally: the owner) doesn’t “sync” his own NetworkVariables, for them it’s a direct assignment of the value. :wink:

Did you check if HasAuthority is true? You would also not see value change events if the value isn’t actually changing.

Avoid accumulating delta time, this accumulates rounding errors. Set the time as the time when it should happen:

if (Time.time >= _oneSecondTimerElapsed)
{
    _oneSecondTimerElapsed = Time.time + 1f;
    _timeLeft.Value--;
}

If it’s a problem that it will fire once instantly, you can set the timer value once to prevent that.
Time left can be calculated by subtracting Time.time.
Note that time may progress slower that way if you reset it every second because Update may do the reset at 1.016 seconds and that also accumulates. Perhaps even better is to cast time to int and record whenever that changes:

var secondsElapsed = (int)Time.time;

Hello,

I’m waiting for NetworkManager.OnClientConnectedCallback to be invoked on LocalClient before spawning local PlayerObject. As I said this works as intended if I wait for everyone to connect before spawning any PlayerObjects, but it doesn’t work when I don’t.

Its called on every LocalClient after he is fully connected. Basically right after NetworkManager.OnClientConnectedCallback is invoked, as per documentation.

I probably didn’t explain this in a good way, what I meant is that for the Host, other client’s PlayerObject NetworkVariables are synchronized correctly no matter what but its not working in reverse - Host’s NetworkVariables are not synchronized on other client. Basically the one who joins first will have everything working fine.

Yes its true because the timer is updating for Host but not for other client so the code is running on Host’s (Authority) machine.

Thanks for the advice :slight_smile: