Attributes
Forms that compile (19)
Section titled “Forms that compile (19)”| Syntax | Description | Note |
|---|---|---|
[UdonSynced] | Variable sync | |
[UdonSynced(UdonSyncMode.Linear)] | Sync with interpolation | Which kinds can be specified depends on the type. |
[UdonBehaviourSyncMode(BehaviourSyncMode.Manual)] | Specifying send timing | |
[UdonBehaviourSyncMode(BehaviourSyncMode.Continuous)] | Continuous sending | |
[FieldChangeCallback(nameof(Value))] | Change notification | |
[NetworkCallable] | Allowing calls over the network | |
[RecursiveMethod] | Allowing recursion | Because storage for parameters and local variables isn’t created per call. |
[RecursiveMethod] int Fact(int n) | Recursion in a local function | |
[RecursiveMethod] public static int Fact(int n) | Recursion in methods of a static class, struct and record | |
[RecursiveMethod] IsEven / [RecursiveMethod] IsOdd | Methods that call each other | Marking only one of them is an error (Forms that don’t compile (4)). |
other.SendCustomEvent("Tick") | Entering a method again while it runs | The compiler saves the arguments and local variables before the call and restores them after it. |
[Inline] | Inlining calls | |
[SerializeField] | Showing a private field | |
[HideInInspector] | Hiding a public field | |
[Header] [Tooltip] | Header and description | |
[Range(0f, 1f)] | Specifying a value range | |
[Space] | Spacing | |
[SerializeField, Range(0f, 1f)] | Multiple attributes on one field | |
[System.Obsolete] | Marking as obsolete |
Forms that don’t compile (4)
Section titled “Forms that don’t compile (4)”| Syntax | Description | Error | Reason | Alternative |
|---|---|---|---|---|
private int Down(int n) { return Down(n - 1); } | Calling itself without the attribute | TUKI0001 | runtime | Add [RecursiveMethod] (or write it with a loop or your own stack) |
IsEven with [RecursiveMethod] and IsOdd without it call each other | Marking only one of the methods that call each other | TUKI0001 | by design | Add [RecursiveMethod] to every method in the cycle |
SendCustomNetworkEvent(…, nameof(Take), 1) (without [NetworkCallable]) | Calling a method with arguments over the network by name, without the attribute | TUKI0001 | runtime | Add [NetworkCallable] on the receiving side |
[DllImport("x")] | Calls an external library | TUKI0001 | runtime | Use a method the runtime provides |
| The registered name of a method with parameters | Without the attribute it is registered under a name that includes the parameter types, which doesn’t match the name you wrote with nameof, so it can’t be referenced by name. Add [NetworkCallable] and it is registered under the name as written |
| The namespace of the sync attributes | They can be written from this language’s namespace. using the namespace you migrated from at the same time makes two attributes of the same name visible, which is a compile error (removing one of the usings makes it compile. File layout and namespaces) |
| The 5 not in this table | [Reactive], [Computed], [Effect], and [On] are under Reactive programming; [TsukimiTest] is under Testing |
Variable sync
Section titled “Variable sync”A variable with [UdonSynced] keeps the same value across players.
using UnityEngine;using Tsukimi;
public class AttributesSynced : TsukimiBehaviour{ [UdonSynced] private int value;
void Start() { value = 1; RequestSerialization(); Debug.Log(value); // => 1 }}Sync with interpolation
Section titled “Sync with interpolation”UdonSyncMode specifies the kind of interpolation.
using UnityEngine;using Tsukimi;
public class AttributesSyncedInterpolated : TsukimiBehaviour{ [UdonSynced(UdonSyncMode.Linear)] private float value;
void Start() { Debug.Log(value); // => 0 }}Specifying send timing
Section titled “Specifying send timing”With Manual, the value is sent when you call RequestSerialization().
using UnityEngine;using Tsukimi;
[UdonBehaviourSyncMode(BehaviourSyncMode.Manual)]public class AttributesSyncModeManual : TsukimiBehaviour{ [UdonSynced] private int value;
void Start() { value = 1; RequestSerialization(); }}Continuous sending
Section titled “Continuous sending”With Continuous, the value is sent without calling RequestSerialization().
using UnityEngine;using Tsukimi;
[UdonBehaviourSyncMode(BehaviourSyncMode.Continuous)]public class AttributesSyncModeContinuous : TsukimiBehaviour{ [UdonSynced] private int value;
void Start() { value = 1; }}Change notification
Section titled “Change notification”When a synced value arrives, the setter of the specified property is called.
using UnityEngine;using Tsukimi;
public class AttributesFieldChangeCallback : TsukimiBehaviour{ [UdonSynced, FieldChangeCallback(nameof(Value))] private int backing;
public int Value { get { return backing; } set { backing = value; } }
void Start() { Value = 1; Debug.Log(Value); // => 1 }}Allowing calls over the network
Section titled “Allowing calls over the network”A method called across the network with arguments needs this attribute.
using UnityEngine;using Tsukimi;using VRC.SDK3.UdonNetworkCalling;
public class AttributesNetworkCallable : TsukimiBehaviour{ [NetworkCallable] public void Receive(int n) { Debug.Log(n); }
void Start() { Debug.Log(1); // => 1 }}Allowing recursion
Section titled “Allowing recursion”A method that calls itself needs this attribute.
using UnityEngine;using Tsukimi;
public class AttributesRecursiveMethod : TsukimiBehaviour{ [RecursiveMethod] private int CountDown(int n) { return n <= 0 ? 0 : CountDown(n - 1); }
void Start() { Debug.Log(CountDown(3)); // => 0 }}Recursion in a local function
Section titled “Recursion in a local function”It can also be put on a local function.
using UnityEngine;using Tsukimi;
public class AttributesRecursiveLocal : TsukimiBehaviour{ void Start() { [RecursiveMethod] int Fact(int n) { return n <= 1 ? 1 : n * Fact(n - 1); }
Debug.Log(Fact(5)); // => 120 }}Recursion in methods of a static class, struct and record
Section titled “Recursion in methods of a static class, struct and record”It can also be put on a method of a static class, and on a method of a struct or a record.
using UnityEngine;using Tsukimi;
public static class RecursiveMath{ [RecursiveMethod] public static int Fact(int n) { return n <= 1 ? 1 : n * Fact(n - 1); }}
public struct RecursiveStep{ public int Step;
[RecursiveMethod] public int Sum(int n) { return n <= 0 ? 0 : Step + Sum(n - 1); }}
public record RecursiveTag(int Step){ [RecursiveMethod] public int Sum(int n) { return n <= 0 ? 0 : Step + Sum(n - 1); }}
public class AttributesRecursiveHelpers : TsukimiBehaviour{ void Start() { RecursiveStep s = new RecursiveStep(); s.Step = 2; RecursiveTag t = new RecursiveTag(3); Debug.Log(RecursiveMath.Fact(5)); // => 120 Debug.Log(s.Sum(3)); // => 6 Debug.Log(t.Sum(3)); // => 9 }}Methods that call each other
Section titled “Methods that call each other”When methods call each other, every method in the cycle needs it.
using UnityEngine;using Tsukimi;
public class AttributesRecursiveMutual : TsukimiBehaviour{ [RecursiveMethod] private bool IsEven(int n) { return n == 0 ? true : IsOdd(n - 1); }
[RecursiveMethod] private bool IsOdd(int n) { return n == 0 ? false : IsEven(n - 1); }
void Start() { Debug.Log(IsEven(4)); // => true }}Entering a method again while it runs
Section titled “Entering a method again while it runs”Entering a method that is still running again, from a call that runs other code on the spot, needs no attribute.
using Tsukimi;using VRC.Udon;
public class AttributesReentryThroughOther : TsukimiBehaviour{ public UdonBehaviour other; public int depth; public int last;
public void Hit() { int mine = depth; depth = depth + 1; // Even if other's Tick calls this Behaviour's Hit back, mine still holds this call's value after it returns. if (depth < 2) other.SendCustomEvent("Tick"); last = mine; }}| Calls that save | Events sent to this Behaviour (SendCustomEvent to itself, and SendCustomNetworkEvent to itself unless the target is Others), and calls that may run another Behaviour’s code on the spot. Another Behaviour of any class may call this one back, so this covers method calls, SendCustomEvent and SetProgramVariable on it, SetActive, writing enabled, Destroy, Networking.SetOwner, calls that change a Transform’s parent or sibling order (SetParent, assigning parent, DetachChildren, SetSiblingIndex and the like; OnTransformParentChanged and OnTransformChildrenChanged run before the call returns), VRCObjectPool.TryToSpawn and the like |
| Calls that save nothing | Reading another Behaviour’s fields (GetProgramVariable included) and delayed sends (SendCustomEventDelayedFrames and the like). A delayed send runs later, so it is a different program, not a cheaper way to write the same one |
| Exceptions to the calls that save | Instantiate, and writing enabled on a 3D collider. Nothing runs during these calls (the copy’s OnEnable and Start run on a later frame, and switching a collider off sends no trigger event) |
| Cost | The save carries every local variable and intermediate value the method holds at that call, and a method called from several places also compares against each caller to find where to return, so facts.cost can grow well past the call itself. To compare two ways of writing it, measure facts.cost with inspect |
| Checking | The methods that save are listed in facts.recursiveMethods of inspect (Hit in this example) |
| Exporting to UdonSharp | A method that may be entered again through another Behaviour is exported with [RecursiveMethod], so UdonSharp also keeps its local variables across the call. A Behaviour with a method that may be entered again through one of its own events that starts on the spot is not exported (Entering a method again while it runs) |
Inlining calls
Section titled “Inlining calls”A method with this attribute is expanded at the call site regardless of its size.
using UnityEngine;using Tsukimi;
public class AttributesInline : TsukimiBehaviour{ [Inline] private int Twice(int n) { return n * 2; }
void Start() { Debug.Log(Twice(2)); // => 4 }}The Inspector
Section titled “The Inspector”Showing a private field
Section titled “Showing a private field”Even a non-public field can be assigned a value from the Inspector.
using UnityEngine;using Tsukimi;
public class AttributesSerializeField : TsukimiBehaviour{ [SerializeField] private int value;
void Start() { Debug.Log(value); // => 0 }}
Hiding a public field
Section titled “Hiding a public field”The field stays public but no longer appears in the Inspector.
using UnityEngine;using Tsukimi;
public class AttributesHideInInspector : TsukimiBehaviour{ [HideInInspector] public int value;
void Start() { Debug.Log(value); // => 0 }}
Header and description
Section titled “Header and description”[Header] shows a heading, and [Tooltip] shows a description on hover, in the Inspector.
using UnityEngine;using Tsukimi;
public class AttributesHeaderAndTooltip : TsukimiBehaviour{ [Header("Group")] [Tooltip("note")] public int value;
void Start() { Debug.Log(value); // => 0 }}
The description only appears on hover, so it isn’t in this image.
Specifying a value range
Section titled “Specifying a value range”It becomes a slider in the Inspector, and values outside the given range can no longer be assigned.
using UnityEngine;using Tsukimi;
public class AttributesRange : TsukimiBehaviour{ [Range(0f, 1f)] public float value;
void Start() { Debug.Log(value); // => 0 }}
When the Inspector is narrow, no slider appears and only the numeric field is shown. The range restriction applies just the same.
Spacing
Section titled “Spacing”Adds spacing between items in the Inspector.
using UnityEngine;using Tsukimi;
public class AttributesSpace : TsukimiBehaviour{ [Space] public int value;
void Start() { Debug.Log(value); // => 0 }}
The one above has the spacing. Without it, they sit flush like this.

Multiple attributes on one field
Section titled “Multiple attributes on one field”using UnityEngine;using Tsukimi;
public class AttributesMultipleOnOneField : TsukimiBehaviour{ [SerializeField, Range(0f, 1f)] private float value;
void Start() { Debug.Log(value); // => 0 }}Marking as obsolete
Section titled “Marking as obsolete”Using a member with this attribute produces a warning.
using UnityEngine;using Tsukimi;
public class AttributesObsolete : TsukimiBehaviour{ [System.Obsolete] public void Old() { }
void Start() { Debug.Log(1); // => 1 }}