unity-vrc-udon-sharp

UdonSharp scripting skill for VRChat SDK 3.10.5 (active and verified target). Use when writing, reviewing, debugging, or migrating UdonSharp C# and UdonBehaviour code. Positive triggers include UdonSharp, NetworkCallable, NetworkCalling, CallingPlayer, Udon network authorization, synced runtime stat

By niaka3dayo · 782 installs

npx skills add niaka3dayo/agent-skills-vrc-udon --skill unity-vrc-udon-sharp

Source repository · Upstream listing

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 code that runs in the Udon runtime. Editor evaluated field initializers are a separate context: they can use some ordinary C features to generate a final value that Udon can hold. 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.SetOwner is locally immediate on the calling client — Networking.IsOwner(gameObject) is true synchronously after the call, and writing [UdonSynced] fields plus RequestSerialization() immediately afterwards is safe under an IsOwner guard. Concurrent SetOwner calls 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 via OnDeserialization ; no manual RequestSerialization() 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 OnOwnershipTransferred fires 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 in OnOwnershipTransferred . Context Preservation For complex synced systems, ownership sensitive refactors, or work resumed after compaction/handoff, consider loading references/context preservation.md . It provides a lightweight task context note for source of truth, transport, sync mode, storage, ownership, late joiner behavior, and validation rationale. This is optional guidance for complex work, not a step for small mechanical edits. Keep private data and raw transcripts out of any note. For VRChat SDK Build Panel validation alerts, red/yellow/white warnings, or Auto Fix side effects that involve world scene setup rather than UdonSharp compiler constraints, use unity vrc world sdk 3 and read references/build validation.md . Core Principles 1. Constraints First — For Udon runtime code, assume standard C features are blocked until verified. Treat Editor evaluated field initializers separately and require a final Udon supported value. Check udonsharp constraints.md before using any API. 2. Ownership Before Mutation — Only the owner of an object can modify its synced variables. Always SetOwner → modify → RequestSerialization . 3. Late Joiner Correctness — State must be correct for players who join after events have occurred. Design for re serialization, not just live updates. 4. Sync Minimization — Every synced variable costs bandwidth (see data budget in udonsharp sync selection.md ). Derive what you can locally; sync only the source of truth. 5. Event Driven, Not Polling — Use OnDeserialization , [FieldChangeCallback] , and SendCustomEvent instead of checking state in Update() for state change reactions; for hot path or periodic work, see [Event Dispatch & Cross Behaviour Call Cost Tiers](references/patterns performance.md event dispatch cross behaviour call cost tiers) . Synced arrays: always apply them from OnDeserialization() . Array element changes do not provide a reliable FieldChangeCallback signal, and the same guidance applies to same length changes, array reassignments, and length changes. Have the owner call the same idempotent apply method immediately after mutation, then request Manual serialization once. If a revision guard protects a historical one shot side effect, a late joiner's first OnDeserialization() receives the current revision and may otherwise replay that effect. Baseline the first received revision without the side effect, but run durable ApplyValues() before the baseline check; only later revisions should trigger the one shot. Revision is not ordering or stale packet protection. SDK 3.10.4 event receiver arguments SDK 3.10.5 is the active and verified target for this Skill. SDK 3.10.4: UdonSharpBehaviour implements IUdonEventReceiver directly. An API that requires a receiver can therefore receive this directly: The receiver argument is still required; only the explicit (IUdonEventReceiver) cast is unnecessary on the active SDK. Keep any pre 3.10.4 cast in the historical migration reference only. This follows the [official SDK 3.10.4 release notes](https://creators.vrchat.com/releases/release 3 10 4/) and the SDK source declaration. Public fields: Inspector visibility is not persistence policy [HideInInspector] only hides a public field from the Inspector; Unity still serializes it. Use [HideInInspector] public when Editor time DI, baking, or autowiring must persist the value into a Scene/Prefab. Use [System.NonSerialized] public for a runtime only value that another UdonBehaviour must access through direct access or SetProgramVariable . When [HideInInspector] is intentional, leave a comment explaining why the value must be persisted. Editor evaluated field initializers Field initializers are evaluated as ordinary C on the Unity/Editor side to produce initial data for the compiled Udon program; their expressions do not run in the Udon runtime. A Random.Range call in an initializer is evaluated in the Editor and stored as a baked default, not runtime randomness. LINQ, lambdas, or a same behaviour static helper that uses List<T can therefore generate an array initializer even though the same code is unavailable from Start() , Interact() , or another Udon runtime method. The final field type and value must be supported by Udon. Keep generation independent of scene, player, and runtime state, and do not call main thread only Unity APIs because field initializers and constructors can run on a loading thread. See references/constraints.md for both supported forms and their boundaries. Use Start() or a lazy init guard only for local or per client randomness. For shared per object or per session seed/state, the owner generates it and stores it in a [UdonSynced] field; with Manual sync, establish ownership before writing and then call RequestSerialization() . Receivers may apply derived state in OnDeserialization() when needed, but that callback is not required for the field synchronization itself, and late joiners receive the current synced state. Common Mistakes (NEVER List) These Udon runtime and Unity serialization constraints cause either compile time failures or silent data errors . Check this list before writing UdonSharp code or serialized initial values. NEVER do this Why it fails silently Use instead 1 Use List<T , Dictionary<T,K , or any generic collection in Udon runtime code Compile error — blocked by Udon compiler T[] arrays, DataList , DataDictionary ( DataDictionary.EnsureCapacity / custom capacities require SDK 3.10.4+); Editor evaluated field generation is the limited exception above 2 Use async / await , System.Threading , or coroutines Udon is single threaded; these features do not exist SendCustomEventDelayedSeconds() 3 Modify [UdonSynced] fields without owning the object Change appears local but is silently reverted on next deserialization Networking.SetOwner() before modify, then RequestSerialization() 4 Forget RequestSerialization() after modifying synced fields (Manual sync) State changes never leave the local client — no error, no warning Always call RequestSerialization() after modifying [UdonSynced] fields 5 Use try / catch / finally / throw Compile error — exception handling is blocked Defensive null checks + early return 6 Access Networking.LocalPlayer in field initializers Editor side initial value generation has no player or Udon runtime state Initialize in Start() or use lazy init guard 7 Use static fields for per instance state Static fields are shared across all instances on the same client and are not synced Instance fields with [UdonSynced] if sync is needed 8 Call RequestSerialization() every frame in Manual sync Floods the ~11 KB/s network budget, causing congestion for the entire world Throttle to 1 10 Hz with change detection; check Networking.IsClogged 9 Use LINQ ( .Where , .Select , etc.) or lambda expressions in Udon runtime code Compile error — not supported by Udon compiler Manual for loops with named methods; Editor evaluated field generation is the limited exception above 10 Use Button.onClick.AddListener() Not available in Udon — no runtime delegate support Configure SendCustomEvent via Inspector OnClick 11 Mix Continuous and Manual sync concerns on one behaviour Wastes bandwidth (discrete values in Continuous) or loses control (redundant RequestSerialization in Continuous) Separate behaviours: Continuous for position/rotation, Manual for discrete state 12 Write to [UdonSynced] fields without an IsOwner guard Non owner writes are purely local and silently reverted on the next deserialization from the actual owner Networking.SetOwner first if needed (locally immediate), then write under IsOwner and call RequestSerialization() 13 Use [NetworkCallable] on an unsupported SDK below 3.8.1 (historical migration only) Compile error — the attribute and parameterized network event API are unavailable Use the active SDK target, 3.10.5; for historical migration notes, use synced variables and react in OnDeserialization / FieldChangeCallback instead of pairing them with a network event 14 Use PhysBones/Contacts API ( OnPhysBoneGrabbed , OnContactEnter , etc.) on an unsupported SDK below 3.10.0 (historical migration only) Compiles but silently ignored at runtime — world side Dynamics did not exist pre 3.10.0, so callbacks never fire Use the active SDK target, 3.10.5; for historical migration, verify the project is at least SDK 3.10.0 15 Use PlayerData persistence API on an unsupported SDK below 3.7.4 (historical migration only) Compile error — missing symbol; PlayerData , PlayerObject , and OnPlayerRestored were added in 3.7.4 and are not in the Udon whitelist before then Use the active SDK target, 3.10.5; for historical migration, verify the project is at least SDK 3.7.4 16 Put a Unity .asmdef around UdonSharpBehaviour without matching U Assembly Definition Unity compiles the C assembly, but UdonSharp reports the script does not belong to a U assembly For simple world scripts, avoid asmdef; for package/asmdef workflows, create the corresponding U Assembly Definition and set Source Assembly to the Unity .asmdef (see references/assembly definitions.md ) 17 Create a .cs script without a corresponding .asset file