Networking
Synced values that tell you when they change, and typed messages anyone can send, with nothing to wire up.
Two clients in a world have to agree on something. VRChat gives you two ways to do that, and they solve different problems badly if you pick the wrong one.
A synced variable is a fact that everybody holds. A score, a door's open or shut, whose turn it is. It survives: somebody walking in ten minutes late gets the current value without anybody resending it.
A message is something that happened once. A hit landed, a round started, a button was pressed. Sending it as a variable goes wrong in ways that look like nothing at all: assigning the same value twice is one change, so two identical hits become one; a late joiner receives the last hit of a fight they missed as though it just happened; and only the owner may write, so two players hitting the same target fight over the variable and one hit disappears with nothing said.
This package is both, with the parts that usually go wrong handled.
public class Door : UdoniteBehaviour
{
private Synced<bool> open = new Synced<bool>();
public override void Start()
{
open.Subscribe(OnOpenChanged);
Room.Subscribe<Knocked>(OnKnocked);
}
public override void Interact()
{
open.Value = !open.Value; // replicated, and every client's handler runs
Room.Send(new Knocked()); // heard once by everybody, sender included
}
private void OnOpenChanged(bool value) => panel.SetActive(value);
private void OnKnocked(Knocked message) => sound.Play();
}
Room.Send goes to the whole room, and anybody may send. A message about one particular object
travels on that object instead, written as this.Send on the behaviour that is the object, and
that one requires owning it. Messages has both.
open.Value = ... writes the variable and asks VRChat to replicate it; there is no
RequestSerialization to forget. Subscribe runs your handler on every client when the value
arrives, once the whole update has landed rather than while half of it is still the old value.
Who needs to own what
Ownership is VRChat's, not this package's: only the owner's write to an object replicates. What differs is what each thing does when you are not the owner.
| what you do | who has to own it | if you do not |
|---|---|---|
Write a Synced<T> value |
the object holding the field | the write stays on your client, and the next value to arrive overwrites it |
this.Send on this object's channel |
this object | nothing is staged and nothing is delivered, said once in the console |
channel.Send on a NetworkBus |
that bus's object | the same |
Room.Send |
nobody | every client writes to its own player object's bus, so there is nothing to contend for |
Move a NetworkTransform |
the object | the owner sends and everybody else interpolates towards it |
A room message is the one that needs no owner, which is the main reason to prefer it when any client may act. An object's channel and a synced field both come down to one writer, so a world where anybody may act on the object claims it first:
if (!Ownership.IsMine(gameObject))
Ownership.Claim(gameObject);
A pickup or an Interact usually already claims it. MasterOwned is the opposite case: derive from
it and only the arbitrating client can own the object, for state nobody else may write.
What is here
Synced<T> |
A variable every client holds, that tells you when it changes |
Room |
Send and subscribe to typed messages; who is here, who is arbitrating |
NetworkEvent |
A message with fields, rather than a bag of bytes |
NetworkBus |
One object's own message channel, when a message is about that object |
NetworkTransform |
Position, rotation and scale, interpolated, with teleport detection |
Ownership |
Who owns an object, and taking or giving it |
MasterOwned |
A base class for an object only the arbitrating client may own |
What you do not have to set up
Room messages need one object in the scene to carry them, because a message has to be written to something a client owns and nothing can create that while a world runs. The package adds it for you the first time a behaviour in your world sends or subscribes, and says so once in the console. A world that never sends gets nothing added to it.
If you would rather place it yourself, put a NetworkBus on an object carrying VRCPlayerObject
and yours is used instead. It is found by its components, never by its name.
Udonite → Open Window → Settings → Net has a toggle for this: turn it off and the package
never touches the scene, whether or not your world sends anything — placing the bus is then
entirely yours to do. On is the default. This one setting lives in ProjectSettings/, committed
like the rest of what the project excludes, because it is a fact about who owns the scene rather
than one developer's own preference.