World Utilities / Concepts
Local, Synchronized & Persisted
Every value type comes in up to three kinds which decide who sees the state and how long it lives.
| Local | Synchronized | Persisted | |
|---|---|---|---|
| Who sees the state | only the local player | everyone in the instance | only the local player |
| Survives leaving | no | while the instance exists | yes, across instances & visits |
| Network traffic | none | on every change | none (stored by VRChat) |
| Available from | Start() |
Start(), then updated by the owner |
after VRChat restores player data |
| Typical use | graphics & audio settings | instance owner settings, shared state | preferences players expect to keep |
| Namespace | VRCTools.World.LocalValues |
VRCTools.World.SynchronizedValues |
VRCTools.World.PersistedValues |
Player A Player B Player A (next visit)
┌───────────────────┐ ┌───────────────────┐ ┌───────────────────┐
│ Local Float 0.4 │ ✗ │ Local Float 1.0 │ │ Local Float 1.0 │ ← default again
│ Synced Bool true │ ─────▶ │ Synced Bool true │ │ (new instance) │
│ Persisted 0.4 │ ✗ │ Persisted 0.8 │ │ Persisted 0.4 │ ← restored
└───────────────────┘ └───────────────────┘ └───────────────────┘
Local
Local values live entirely on the client of the player looking at them. Use them for anything that is a matter of personal taste or comfort: mirror toggles, music volume, post processing intensity, a chosen color scheme.
Local values never touch the network, which makes them the cheapest kind - you can update them every frame if you need to.
Synchronized
Synchronized values keep their state identical for everyone in the instance using Udon’s manual synchronization.
- Ownership follows the writer. When a player changes a synchronized value, they automatically take ownership of
the game object and broadcast the new state. Everybody else receives it and emits
EVENT_STATE_UPDATEDlocally. - Late joiners are covered. The current owner re-sends the state when players join or leave, and when ownership changes hands.
- Congestion aware. If the network is clogged or a serialization fails, the value retries after
Synchronization Backoffseconds (default2). Changes made in the meantime are folded into the next attempt.
Every synchronized value shows a Networking section in its inspector:
| Parameter | Default | Description |
|---|---|---|
| Synchronization Backoff | 2 |
Seconds to wait before retrying a failed or congested synchronization. Must be above zero. |
| Enable Debug Logging | off | Advanced. Logs every synchronization step to the game log - handy when debugging networking. |
Synchronized values use Manual synchronization. Udon requires all behaviours on the same game object to share a sync mode, so keep synchronized values on their own game objects (the provided prefabs already do) instead of adding them next to continuously synced behaviours. The inspector warns you when it detects a conflict.
Every synchronized value needs a unique hierarchy path (e.g.
Global Settings/Lights Enabled). Two networked objects with the same path can break VRChat’s network id generation - see Best Practices.
Anyone can change a synchronized value. If only the instance owner (or a list of moderators) should be allowed to change it, restrict access to the UI that writes to it - for example by only enabling the controls for the owner.
Persisted
Persisted values behave exactly like local values, but additionally save their state using VRChat’s
persistence (PlayerData) whenever it changes. When the player
returns to your world - even in a different instance - the last saved state is restored.
Persisted values extend their local counterpart (PersistedFloat is a LocalFloat). That means you can plug a
persisted value into any field that accepts a local value: sliders, labels, applicators and converters with Source
set to LOCAL all work unchanged.
Persisted values are networked objects as well, so the same naming rules as for synchronized values apply.
| Parameter | Description |
|---|---|
| Key | The PlayerData key the value is stored under. Required and must be unique within your world. |
Persisted values add two events and one property on top of the local value API:
| Member | Description |
|---|---|
EVENT_LOADED |
Emitted once the stored state (or the default value) has been restored. |
EVENT_PERSIST |
Emitted whenever the state has been written to player data. |
Loaded |
true once the stored state is available. |
_Persist() |
Writes the current state to player data. Called automatically on every change. |
Loading
VRChat restores player data shortly after the local player joins (OnPlayerRestored). Until then the value holds its
Default Value but is not loaded: assignments and _Reset() calls made before that point are ignored and log a
warning. Once data is restored the value emits EVENT_LOADED followed by EVENT_STATE_UPDATED, so consumers update
automatically.
If your script needs to change a persisted value right at start-up, wait for EVENT_LOADED (or check Loaded)
first.
Choosing keys
Keys share a single namespace per world and player. Pick descriptive, stable names - renaming a key later effectively resets everybody’s saved preference:
settings.audio.music_volume
settings.graphics.bloom_enabled
settings.lobby.welcome_seen
See Best Practices for a convention that derives keys from the hierarchy.
VRChat limits how much data a world can store per player. Prefer a handful of meaningful values over persisting everything. See VRChat’s persistence limits for details.
Mixing kinds
Most converters, applicators, presets and generic UI components work with every kind. They expose a Source
(or Target) drop-down which decides which reference field is used:
| Option | Uses |
|---|---|
LOCAL |
the Local Value field - also accepts persisted values |
SYNCHRONIZED |
the Synchronized Value field |
STATIC |
a constant typed into the inspector (converter inputs only) |
A few components are specific to one kind because they need to reference several values at once (for example the Material Property and Animator Parameter applicators). Those come in a Local and a Synchronized flavor.
A classic pattern combines kinds with a converter: let the instance owner enable a feature for everyone with a
Synchronized Boolean, let each player opt out with a Persisted Boolean, and combine both with an AND
Boolean Operation into a Local Boolean that drives the
feature. The owner-controlled features guide walks through
it step by step.