Overview
Even attractive world content won't be enjoyable for players in a choppy environment with low frame rate (FPS). Especially in VRChat, since avatars and other players' presence itself creates load, world creators must always design with performance in mind.
Performance optimization in UdonSharp means more than just writing clean code. It requires approaches tailored to VRChat's characteristics, considering both CPU load and network load.
This article covers specific techniques for creating comfortable worlds, from basic optimization concepts to advanced techniques like object pooling and network load reduction.
1. Breaking Away from Update(): Event-Driven Design
The most important and fundamental optimization in UdonSharp (and Unity) development is avoiding Update() methods as much as possible.
-
Problem:
Update()executes every frame. Even a few lines of code, when executed every frame across many objects in a scene, quickly increases CPU load. This impact becomes serious especially as player count increases. -
Solution: Switch to an event-driven approach. Design to "process only when something happens" instead of "constantly monitoring."
| What You Want to Do | Bad Example with Update | Good Example with Event-Driven |
|---|---|---|
| Check if button pressed | if (Input.GetKeyDown(KeyCode.E)) { ... } | public override void Interact() { ... } |
| Check if player is in area | if (Vector3.Distance(...) < 5) { ... } | public override void OnPlayerTriggerEnter(...) |
| Check if health reached 0 | if (health <= 0) { Respawn(); } | Check if (health <= 0) at the moment of taking damage |
Even when Update() is necessary (e.g., continuously moving objects), that processing should be consolidated into a single manager object, minimizing the number of UdonSharpBehaviours with Update() in the scene.
2. Object Pooling: Eliminating Creation and Destruction Costs
Processing that repeatedly creates (Instantiate) and destroys (Destroy) objects in short periods—like gun bullets, particles, or enemy characters—causes large CPU spikes (momentary high load). Object pooling solves this.
This topic was already explained in detail in the article "Gimmick Creation 8: Object Pool (VRChat SDK)." It's important to utilize the VRCObjectPool component to replace high-cost Instantiate/Destroy with low-cost SetActive(true)/SetActive(false).
3. Reducing Network Load
UdonSharp's sync processing is convenient, but misuse can strain network bandwidth and destabilize the entire instance.
Update Synced Variables Carefully
-
Problem: Changing
[UdonSynced]variables every frame inUpdate()and callingRequestSerialization()is the worst pattern. This causes all sync data for that object to be sent to all players every frame, generating enormous network traffic. -
Solution: Only call
RequestSerialization()when values actually change.
// Bad example
private int _score;
public int Score
{
set
{
_score = value;
RequestSerialization(); // Sends every time even if value didn't change
}
get => _score;
}
// Good example
private int _score;
public int Score
{
set
{
if (_score == value) return; // Do nothing if value hasn't changed
_score = value;
RequestSerialization();
}
get => _score;
}
Choosing BehaviourSyncMode
The Behaviour Sync Mode of the Udon Behaviour component determines sync frequency.
Continuous: Automatically attempts to sync when variables change. Easy to use, but may cause unintended frequent syncing. This mode is very dangerous when changing synced variables inUpdate.Manual: Only syncs whenRequestSerialization()is called. Provides complete control over when data is sent, soManualis recommended for performance-conscious development.
4. Other Optimization Techniques
- Cache GetComponent: Calling
GetComponent<T>()inUpdate()is high-load. Get necessary components once inStart()orAwake()and hold (cache) them in variables.
// Bad example
void Update()
{
GetComponent<Rigidbody>().AddForce(Vector3.up);
}
// Good example
private Rigidbody rb;
void Start()
{
rb = GetComponent<Rigidbody>();
}
void Update()
{
rb.AddForce(Vector3.up);
}
-
Using Delayed Events: Coroutines can't be used in UdonSharp, but you can use
SendCustomEventDelayedSeconds("MethodName", seconds)orSendCustomEventDelayedFrames("MethodName", frames)to execute processing after a certain time or frames. This eliminates the need to continuously check flags inUpdate. -
Disabling Processing by Distance: Gimmicks far from players may not need to operate. Get player position with
VRCPlayerApi.GetPosition(), calculate distance to the gimmick, and skip processing if beyond a certain distance to reduce load.
Summary
- Performance optimization needs to consider both CPU load and network load.
- Avoiding
Update()and designing event-driven is the most basic and effective CPU load countermeasure. - Introduce object pooling for frequent object creation/destruction.
- To reduce network load, only update synced variables when values change, and select
ManualforBehaviour Sync Modeis safer. - Steady improvements like caching
GetComponentand utilizing delayed events (SendCustomEventDelayedSeconds) lead to overall world comfort.
Optimization isn't something to do at the end of world creation, but an important element to always be aware of from the design stage. Use these techniques to create comfortable and enjoyable VRChat experiences for everyone.