Getting started

Share a value between clients, hear when it changes, and send a message everybody acts on once.

Updated 2026-09-10

Installation

First, add the networking package to your Unity project via VCC (VRChat Creator Companion). Open your project's vccproject.json and add the package, or use the VCC interface to install it directly.

Once installed, the package is available in your project and you can start using the networking APIs below.

Share a value

public class Scoreboard : UdoniteBehaviour
{
    private Synced<int> score = new Synced<int>();

    public override void Interact()
    {
        score.Value = score.Value + 1;
    }
}

Synced<T> is a field on your behaviour. Writing .Value stores it and asks VRChat to replicate, so there is no serialization call to remember and no attribute to pair with a sync mode.

Only the owner of the object may write it. A write on a client that does not own the object stays on that client. Take ownership first if anyone should be able to change it:

public override void Interact()
{
    Ownership.Claim(gameObject);
    score.Value = score.Value + 1;
}

Hear when it changes

public override void Start()
{
    score.Subscribe(OnScoreChanged);
}

private void OnScoreChanged(int value)
{
    label.text = value.ToString();
}

The handler runs on every client, including the one that wrote it, so the display is built once and not twice. It runs after the whole update has arrived, which matters the moment you have two synced values: a handler that reads a sibling gets the value that came in the same update rather than whatever was there before.

The handler can be private. It is called from inside your own behaviour, so nothing outside needs to be able to name it.

Send a message

A message is a class with the fields you want to send:

public class Scored : NetworkEvent
{
    public int playerId;
    public int points;

    public override void Serialize(ByteWriter writer)
    {
        writer.WriteInt32(playerId);
        writer.WriteInt32(points);
    }

    public override void Deserialize(ByteReader reader)
    {
        playerId = reader.ReadInt32();
        points = reader.ReadInt32();
    }
}

Send it, and hear it:

public override void Start()
{
    Room.Subscribe<Scored>(OnScored);
}

public override void Interact()
{
    Room.Send(new Scored { playerId = 1, points = 10 });
}

private void OnScored(Scored message)
{
    Debug.Log(message.points + " to player " + message.playerId);
}

Room.Send sends to the whole room, and being static it works from a plain class as well as from a behaviour. 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. Both are in Messages.

Anybody can send. Unlike a synced value there is no owner to be, which is why a message is the right shape for "somebody did something" and a variable is the right shape for "this is how things are".

Every client hears it once, in the order it was sent, including the client that sent it. A player who joins afterwards hears nothing they missed, which is what you want from an event and the opposite of what you want from a value.

Who sent it

Take a second parameter when it matters:

private void OnScored(VRCPlayerApi sender, Scored message)
{
    Debug.Log(sender.displayName + " scored");
}

Both shapes can subscribe to the same message type and both run. Most handlers do not care who sent it, which is why the sender is not in every signature.

Where to go next

Synced<T> in full, including arrays and the one case the compiler cannot see, is in Synced values. Addressing, object channels and what the wire actually costs are in Messages.

Something went wrong Reload