VRChat Udon Skill
Version: 1.0 Stack: VRChat SDK, UdonSharp
Udon looks like regular Unity scripting until you add networking. Then everything changes. Modifying a synced variable without ownership silently fails — no error, no warning, just nothing happens. Forgetting RequestSerialization means your changes never leave your machine. Testing in the editor gives you zero signal about networking bugs because there's only one player. Late joiners see the default state, not the current state, unless you explicitly handle re-serialization.
Every rule in this skill exists because the default Udon behavior is to silently fail.
Scope and Boundaries
This skill covers:
-
UdonSharp syntax and patterns
-
Networking and synchronization
-
Player interactions
-
World events
-
Common Udon behaviors
Defers to other skills:
-
unity-csharp : C# patterns and performance optimization
-
vrc-worlds : World setup and optimization
Use this skill when: Writing Udon scripts for VRChat worlds.
Core Principles
-
Network Awareness — Every synced variable has bandwidth cost.
-
Owner Model — Only owner can modify synced variables.
-
Late Joiners — State must be correct for players who join later.
-
Local vs Global — Be explicit about what's local vs networked.
-
Event-Driven — Use SendCustomEvent, not polling.
Patterns
Basic UdonSharp Script
using UdonSharp; using UnityEngine; using VRC.SDKBase; using VRC.Udon;
public class MyBehavior : UdonSharpBehaviour { [UdonSynced] private bool _isActive;
public override void Interact()
{
// Take ownership before modifying synced var
Networking.SetOwner(Networking.LocalPlayer, gameObject);
_isActive = !_isActive;
RequestSerialization();
}
public override void OnDeserialization()
{
// Called when synced vars update from network
UpdateVisuals();
}
private void UpdateVisuals()
{
// Update based on _isActive state
}
}
Ownership Transfer Pattern
public void TakeOwnershipAndModify() { // Always check and transfer ownership first if (!Networking.IsOwner(gameObject)) { Networking.SetOwner(Networking.LocalPlayer, gameObject); }
// Now safe to modify synced variables
_syncedValue = newValue;
RequestSerialization();
}
Late Joiner Support
[UdonSynced] private bool _doorOpen;
public override void OnPlayerJoined(VRCPlayerApi player) { // If we're owner, re-serialize state for the new player if (Networking.IsOwner(gameObject)) { RequestSerialization(); } }
public override void OnDeserialization() { // Late joiners get current state here SetDoorState(_doorOpen); }
Custom Event Communication
// Script A - sends event public UdonBehaviour targetScript;
public void OnButtonPress() { targetScript.SendCustomEvent("HandleButtonPress"); }
// Script B - receives event public void HandleButtonPress() { // Handle the event }
// Network event (all players) public void TriggerGlobalEvent() { SendCustomNetworkEvent(NetworkEventTarget.All, "OnGlobalEvent"); }
public void OnGlobalEvent() { // Runs on all players }
Anti-Patterns
Anti-Pattern Problem Fix
Modifying synced vars without ownership Silent failure SetOwner first, then modify
Forgetting RequestSerialization Changes don't sync Always call after modifying synced vars
Syncing too much data Network lag Minimize synced variables
Update() for network checks Performance waste Use OnDeserialization, events
No late joiner handling Broken state for joiners Re-serialize on player join
Checklist
-
Ownership transferred before synced var changes
-
RequestSerialization called after changes
-
OnDeserialization updates all visual state
-
Late joiners handled (OnPlayerJoined + serialize)
-
Network events used for global triggers
-
Minimal synced variable count
References
- references/networking.md — Udon networking, sync, and ownership patterns
Assets
- assets/udon-checklist.md — Udon script development checklist