unity-vrc-udon-sharp
UdonSharp Skill
Why This Skill Matters
UdonSharp looks like regular Unity C# scripting — until you hit its hidden walls. Many standard C# features (List<T>, async/await, try/catch, LINQ, generics) silently fail or refuse to compile in Udon. Networking is even more treacherous: modifying a synced variable without ownership produces no error — it just does nothing. Forgetting RequestSerialization means your state changes never leave your machine. Standard single-player local testing gives zero signal about these networking bugs because there is only one player.
Every rule in this skill exists because UdonSharp's default behavior is to fail silently. Read the Rules before generating any code.
Before Writing Network Code
Four architectural decisions that must be made before choosing sync modes or writing any synced variable. Changing them mid-implementation typically requires a full rewrite:
- Who owns this state? One owner writes; all others read. If two players can both write (e.g., a shared toggle), you need an ownership transfer protocol — writes without ownership are silently discarded.
- When does ownership transfer? On grab? Interact? Game event?
OnPlayerLeft?Networking.SetOwneris locally immediate on the calling client —Networking.IsOwner(gameObject)istruesynchronously after the call, and writing[UdonSynced]fields plusRequestSerialization()immediately afterwards is safe under anIsOwnerguard. ConcurrentSetOwnercalls from multiple clients are resolved by network arrival order — there is no client-side arbitration, so accept that the loser's write is overwritten. - What do late joiners see? State set only by one-time events (
SendCustomNetworkEvent) is invisible to late joiners. Late-joiner-visible state must live in[UdonSynced]variables, which are delivered automatically viaOnDeserialization; no manualRequestSerialization()on join is needed. - What if the owner leaves mid-session? VRChat automatically transfers ownership to a remaining player (selection rule is not publicly documented), and
OnOwnershipTransferredfires on all clients. Synced variables are preserved, so state is not frozen; decide upfront whether to keep the current value, reset to a known default, or re-apply/re-broadcast derived state inOnOwnershipTransferred.