Synced Variables ([Synced])¶
Mark a public field on a NexusBehaviour with [Synced] and it replicates to every other player — and to anyone who joins later. Whoever has authority writes it; everyone else receives it.
public class Scoreboard : NexusBehaviour
{
[Synced] public int redScore;
bool _addPending;
// Called from a UI button, OnUsed, OnGrabbed, etc.
public void AddPoint()
{
// Ask for ownership up front — the grant is asynchronous.
if (!Network.IsOwner(gameObject)) Network.RequestOwnership(gameObject);
_addPending = true;
}
void Update()
{
// Write only once ownership has actually landed.
if (_addPending && Network.IsOwner(gameObject))
{
redScore += 1;
Network.RequestSerialization(); // send it
_addPending = false;
}
}
}
Nothing to wire up — [Synced] adds its own networking
Just declaring a [Synced] field is enough. The runtime automatically gives that object the networking it needs — you don't add a network component, list the field anywhere, or attach an interactable, and Network.IsOwner / Network.RequestOwnership work out of the box. Each object keeps its own synced values (a per‑object channel), so two copies of the same script don't share state.
Network.RequestOwnership() is asynchronous — don't serialize on the next line
A [Synced] object is server‑owned at rest, so a client's first write has to wait. On a client, RequestOwnership(gameObject) only sends a request — you don't actually own the object until the server round‑trips a grant a frame or more later. Network.RequestSerialization() silently does nothing when you're not (yet) the owner (no error, no warning), so calling RequestOwnership() and RequestSerialization() back‑to‑back loses that first push. Take ownership early (on grab/use), then gate the write on Network.IsOwner(gameObject) in Update, exactly as above.
Send mode — Manual vs Continuous¶
[Synced(mode)] chooses when the value is sent:
| Mode | When it sends | Use for |
|---|---|---|
Sync.Manual (default) |
only when you call Network.RequestSerialization() |
scores, inventory, flags — reliable, cheap |
Sync.Continuous |
automatically, whenever the value changes | positions, gauges, anything smoothly moving |
[Synced] public int redScore; // manual
[Synced(Sync.Continuous)] public Vector3 puckPos; // auto-sends on change
Smoothing — interpolation¶
For a Continuous number/vector, choose how the receiver eases to each new value:
Interp |
Behaviour |
|---|---|
Interp.None (default) |
snap to the received value |
Interp.Linear |
move toward it linearly |
Interp.Smooth |
eased (damped) toward it |
[Synced(Sync.Continuous, Interp.Smooth)] public Vector3 puckPos;
Interpolation only applies to float / Vector2 / Vector3 / Vector4 / Quaternion / Color on Continuous fields; discrete or exact values (every integer type, bool, char, string, and double) always snap to the received value.
Who writes it — authority¶
Authority |
Who writes | Use for |
|---|---|---|
Authority.Owner (default) |
whoever owns the object (take it with Network.RequestOwnership) |
per‑object state a player drives |
Authority.Server |
the server only — un‑spoofable | anything that must be trustworthy: health, currency, match state |
[Synced(Authority.Server)] public int matchState; // only the server can change it
One authority per script
All the [Synced] fields on a single script must use the same authority — a script is either owner‑authoritative or server‑authoritative. Mixing is a compile error. (Put server‑owned state on its own script/object.)
Choosing between [Synced] and World.*/Player.*
[Synced] is per‑object field state (a pet's mood, a vehicle's gauges, a held prop). The World.* / Player.* session variables are scoped, server‑authoritative shared state (a world score, a player's coins). Use [Synced(Authority.Server)] or World.* for anything that must be un‑spoofable.
React to changes — [SyncedChange]¶
Mark a method with [SyncedChange(nameof(field))] and the runtime calls it on the receiver right after that field changes:
public class Scoreboard : NexusBehaviour
{
[Synced] public int redScore;
[SyncedChange(nameof(redScore))]
public void OnRedScore() { UpdateScoreUI(redScore); }
}
When [SyncedChange] fires:
- Once per changed field — a handler runs only for the fields whose value actually changed, not once per
RequestSerializationand never on a same‑value resend. - In declaration order — when several fields change in the same update they apply top‑to‑bottom in the order you declared them, and each field's handler runs right after that field is applied. A handler on a later‑declared field therefore sees the earlier fields already updated.
- On the receivers only, never on the writer — the player who wrote the value doesn't get its own callback, so react to your own change directly in the code that made it rather than relying on the handler.
- Once on join — a late joiner runs each
[SyncedChange]handler once for every field whose snapshot value differs from its declared default, right after it receives the current state. A field still at its default value doesn't fire.
Reference¶
| Call | Does |
|---|---|
Network.RequestSerialization() |
Send the current [Synced] values now (Manual mode). |
Network.IsOwner(gameObject) |
Are you the current owner (allowed to write owner‑auth fields)? |
Network.RequestOwnership(gameObject) |
Take ownership so you can write. |
Supported field types: bool; the integer types byte, sbyte, short, ushort, int, uint, long, ulong; float and double; char; string; and Vector2, Vector3, Vector4, Quaternion, Color.
Anything else cannot be synced — arrays, List, Dictionary, Color32, Rect, decimal, object references, your own types. Mark one [Synced] anyway and you get told twice: the Networking Budget panel flags it in the inspector, and at runtime the field is reported by name and simply stays local to each machine rather than replicating.
[NexusVM] 'Scoreboard': field 'entries' is marked [Synced] but List<int> can't be
replicated, so it will NOT sync (it stays local to each machine).
To share a collection, use an RPC
[Synced] carries one fixed-size value per field. A whole list or table goes by
RPC instead — arrays, List<T> and Dictionary<K,V> are all supported
there. A common pattern is a [Synced] int version that changes whenever the collection does, with the
contents sent by RPC.
There's a per‑object size cap on the combined synced payload; if you hit it, use fewer / smaller [Synced] fields (a string counts for its length).
See it before you ship — the inspector's Networking Budget
Select the object and look at the Networking Budget on its script component. It lists every synced field with its send mode and authority, estimates the payload against the per‑object cap, and flags an un‑syncable type or mixed authority before you compile — so you catch the mistake in the editor, not in a live session.
→ See Networking for World.* / Player.* session variables and RPCs.