Guides:UdonSharp Nitpicks
[Reason: No reason provided.]
Here are some things that Fax considers useful or practical when writing U# code, in no particular order.
This page was originally posted on VRCLibrary, and was updated for the VRChat wiki. These suggestions are somewhat personal, so if you have any suggestions, please discuss them!
Attributes
Attributes, such as [SerializeField], can help make your classes, variables, or methods more useful.
UdonBehaviourSyncMode
Use U#'s UdonBehaviourSyncMode attribute to make sure your UdonBehaviour always uses the sync mode you intended. For example:
using UdonSharp;
using UnityEngine;
[UdonBehaviourSyncMode(BehaviourSyncMode.Manual)]
public class Foo : UdonSharpBehaviour
{
// This behaviour always uses manual sync.
}
Alternatively, you can choose the sync mode UdonBehaviours in the Unity inspector. This is more flexible and allows you to use different sync modes for different instances of the same behaviour - but that's a very rare use case. Instead, you may accidentally select the wrong mode without realizing it, causing networking to break unexpectedly. UdonBehaviourSyncMode is usually more reliable.
SerializeField
Use the [SerializeField] attribute to make private variable visible in the Unity inspector, allowing you to assign references to assets or scene objects:
[SerializeField] private Transform foo; // This shows up in the Unity inspector.Instead of using [SerializeField], you can make the variable public instead. However, this also makes those variables accessible from any other UdonBehaviour. If that's what you want, you should consider using [HideInInspector] attribute to prevent your public variables from causing clutter in the inspector.
Tooltip and Header
Use the [Header] and [Tooltip] attributes to organize variables in the Unity inspector. For example:
[Header("Materials")]
[SerializeField] private Material materialFoo;
[SerializeField] private Material materialBar;
[SerializeField] private Material materialBaz;
[Header("Transforms")]
[Tooltip("This tooltip appears when hovering your mouse over transformFoo in the inspector.")]
[SerializeField] private Transform transformFoo;
Networking
This section contains some tips related to networking and synchronizing changes between players.
Call OnDeserialization locally
When the networking owner of an UdonBehaviour uses RequestSerialization(), all players except the owner execute OnDeserialization().
But if you override OnDeserialization() in your script, the owner can manually execute it. This is useful if you'd like the owner of the script to execute OnDeserialization(), to replicate the same behaviour as other players.
For example, the following script allows the owner to increase a score variable and update scoreText for all players:
[UdonSynced] private int score;
[SerializeField] private TextMeshProUGUI scoreText;
// If the owner calls the method below, the score increases by one.
private void IncreaseScore()
{
if (!Networking.IsOwner(gameObject)) return;
score++;
RequestSerialization();
OnDeserialization();
}
public override void OnDeserialization()
{
scoreText.text = score.ToString();
}
Note that OnDeserialization() without parameters is a deprecated method. More complex scripts may require OnDeserialization(DeserializationResult) , so consider the following approach:
- Write your own method, e.g.,
UpdateScoreText(). - Make the owner call it immediately after
RequestSerialization(). - Make remote players call it from
OnDeserialization(DeserializationResult result).
FieldChangeCallback
Use the [FieldChangeCallback] attribute to respond to synced variable changes without using OnDeserialization(). For example:
[SerializeField] private TextMeshProUGUI scoreText;
[UdonSynced, FieldChangeCallback(nameof(Score))] private int score;
private bool Score
{
get => score;
set
{
score = value;
scoreText.text = score.ToString();
}
}
// If the owner calls the method below, the score increases by one.
private void IncreaseScore()
{
if (!Networking.IsOwner(gameObject)) return;
Score++;
RequestSerialization();
}
This approach is especially useful for synced variables that interact with your scene in simple ways, e.g., updating text or animators. You can still use OnDeserialization() elsewhere, where appropriate. Especially because [FieldChangeCallback] has a drawback that's easy to forget:
FieldChangeCallback execution order issues
Note: This section has not been proofread yet! It may contain factual errors.
If you use [FieldChangeCallback]to execute cute, avoid referencing other variables that may not have been deserialized yet. For example:
[SerializeField] private TextMeshProUGUI scoreText;
[SerializeField] private TextMeshProUGUI roundText;
[UdonSynced, FieldChangeCallback(nameof(Score))] private int score;
[UdonSynced] private int round;
private bool Score
{
get => score;
set
{
score = value;
scoreText.text = score.ToString();
roundText.text = round.ToString(); // ⚠️ round might be outdated for remote players!
}
}
// If the owner calls the method below, the score and round increase by one.
private void IncreaseScoreAndRound()
{
if (!Networking.IsOwner(gameObject)) return;
round++;
Score++; // ⚠️ Only the owner will see the round and score text correctly.
RequestSerialization();
}That's because [FieldChangeCallback] may execute before all synced variables have been deserialized. You may accidentally use outdated values without realizing it! Instead, use OnDeserialization or SendCustomEventDelayed to wait until all variables are deserialized.
Optimization
These tips can help reduce the CPU utilization caused by your scripts.
Cache LocalPlayer
Cache Networking.LocalPlayer by saving it as a local variable. Re-use the variable instead of calling Networking.LocalPlayer again. For example:
private VRCPlayerApi localPlayer;
private void Start()
{
localPlayer = Networking.LocalPlayer;
}
private void Update()
{
Debug.Log(localPlayer.GetPosition());
}
This slightly improves the performance of your behaviour, especially if you reference the local player one or more times per update.