Late Join Synchronization

How to synchronize non-network-variables for late joiners?

Lets imagine every player in the game has a specific skin (as in visuals). This is set once for every joined player, using a ClientRPC.

Now another player joins the game. Because they never received the RPC, and therefore does not know the players skin.

I could hook into the NetworkManager’s OnClientConnectedCallback, but this is a hassle if there is many different objects to late-sync.

Of course there is also the per-object OnSynchronize<T>, but this requires “manual” serialization. I also cannot send the RPC from here, as I dont have the clients network id at hand. Not to mention how unholy this sounds.

Ideally there would be a per-object callback, which is called everytime a client initializes a given object on their side, but I have not found that. :thinking:

In general by making those network variables or by sending one or more RPCs to give new players that information.

Why is that not a NetworkVariable?

I use that to record each player’s choice of avatars, which are indexed and the list is in a ScriptableObject asset. I would and likely will do the same with color choices - using indexes.

Index just about everything that’s network shared and you’ll be surprised how much easier it’ll be to work with and you will also reduce traffic by a fair amount. I chose Byte simply because there’s no way there’ll be more than 255 avatar choices but if there where, it’ll be a single change (the byte conversion happens only in NetworkVariable or RPCs, the properties and code all use int for indexes).

Side note: I also send float as bytes whenever possible. Where the float is in the range 0-1 or at most -1 to +1. Typically dividing this range into 255 discrete steps (eg 0.004 or 0.008 increments) doesn’t matter and saves sending three extra bytes.

That’s how you do it and to properly support late joining clients you will want to make sure you have few, ideally no objects to manually late-sync. If you need to manually late-sync anything you haven’t designed the game to be late joinable and that’s what you need to fix first and foremost. :wink:

That’s NetworkVariable and its OnValueChanged event. :wink:

If you need late joining, refactor all the RPCs with late-join data to be NetworkVariable and you fixed your problem.

Well,

  • It seems unnecessary to have a structure that handles frequent updating, when I know it will never be needed.
  • I’m a sucker for immutability. Set something once and dont have to worry about it changing. This handicaps me heavily in the unity environment so I’m aware thats not … productive.
  • Sometimes there is information that is not easily representable in a blittable way. I’d like to have the convenience of sending a few RPC’s the clients way instead of trying to package that up as one thing in, for example, OnSynchronize.

Yea I’m already doing that. Contrary to how it sounds I do have few RPC’s floating around.

Because of some perfectionistic traits or because your game actually benefits from it? :stuck_out_tongue:

Sure, I can see that. So far I only used this once to late-join synchronize the current prices of each product the, player can buy, to the clients.

Oh well, OnValueChange isn’t called initially for late joiners. I’m sometimes manually invoking that callback in the OnNetworkSpawn(). Is that kosher?

To try and preserve my credibility: I’m not stuck, its purely that I’d like to get my code to conform to whats considered “unity standard”. Its important for me that anyone can take over any piece of my code at any time. :thinking:

But it also handles the synchronization with late joining clients, automatically.
If the value doesn’t change it doesn’t create any traffic.

It absolutely isn’t. This isn’t C++ where everything is const something const. Double const all the way. It’s crazy every time I see Unreal C++ code that we had to manually adorn these parameters with const everywhere.

Because albeit it’s a micro-optimization it’s easily done and encapsulated, and contrary to CPU cycles network traffic matters a lot more. I can have an animated player character sending 4 bytes each tick per player. I may have hundreds of them (enemies, not players).

I suppose you are subscribing to the event too late. You should do so in Awake of the script containing the net var, not in OnNetworkSpawn or Start.

Taken from the docs.

Typically, clients should register for NetworkVariable.OnValueChanged within the OnNetworkSpawn method.

If you ask me, late joining is very typical in modern games.

The docs really didn’t make this very clear. :cry:

Hmmm maybe I was thinking of something else.

But you’re right about the docs. Even though they are extensive the pages often lack clarity. Like in this instance, what is meant by “typically” and what are the exceptions/alternatives and why?

I’ve noticed it as well. Same late joining scenario, same network variable event issue.

My understanding is that the initial value doesn’t trigger OnValueChanged.

My solution was to call the event once while registering the OnValueChanged event.

Example:

public override void OnNetworkSpawn()
{
	// everyone
	OnFlipped(false, isFlippedLeft.Value);
	isFlippedLeft.OnValueChanged += OnFlipped;
}