【VRChat】Performance Optimization: Reducing Update Calls and Network Load

Created: 2025-12-19

Optimization techniques for comfortable VRChat worlds. Drawbacks of Update overuse, event-driven design, object pooling, and methods for efficient network synchronization.

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 DoBad Example with UpdateGood Example with Event-Driven
Check if button pressedif (Input.GetKeyDown(KeyCode.E)) { ... }public override void Interact() { ... }
Check if player is in areaif (Vector3.Distance(...) < 5) { ... }public override void OnPlayerTriggerEnter(...)
Check if health reached 0if (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 in Update() and calling RequestSerialization() 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 in Update.
  • Manual: Only syncs when RequestSerialization() is called. Provides complete control over when data is sent, so Manual is recommended for performance-conscious development.

4. Other Optimization Techniques

  • Cache GetComponent: Calling GetComponent<T>() in Update() is high-load. Get necessary components once in Start() or Awake() 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) or SendCustomEventDelayedFrames("MethodName", frames) to execute processing after a certain time or frames. This eliminates the need to continuously check flags in Update.

  • 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 Manual for Behaviour Sync Mode is safer.
  • Steady improvements like caching GetComponent and 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.