Skip to content

How components interact

SyntaxDescription
GetComponent<Renderer>()Getting by type argument
GetComponent(typeof(Renderer))Getting by type value
GetComponentInChildren<Renderer>()Getting from children
GetComponentInParent<Renderer>()Getting from parents
GetComponentInChildren<Renderer>(true)Getting including inactive objects
GetComponents<Renderer>()Getting all of the same type
GetComponentsInChildren<Renderer>()Getting all from children
target.GetComponent<Renderer>()Getting from another object
transform.Find("Lamp")Getting a child by name
GetComponent<ComponentsLamp>()Getting by a type you wrote
GetComponents<ComponentsSpeaker>()Getting all of a type you wrote
GetComponent<TsukimiBehaviour>()Getting by a base type
GetComponent<UdonBehaviour>()Getting by the runtime’s type
SendCustomEvent(nameof(Ping))Calling yourself
other.SendCustomEvent("Ping")Calling another program
SendCustomEventDelayedSeconds(nameof(Ping), 1.5f)Calling after a delay in seconds
SendCustomEventDelayedFrames(nameof(Ping), 10)Calling after a delay in frames
SendCustomEventDelayedSeconds(nameof(Ping), 1f, EventTiming.LateUpdate)Choosing when the call happens
[NetworkCallable] public void Hit(int damage)Registering a method with arguments
door.Open()Calling a method
counter.Add(5)Calling with arguments
board.Score()Calling with a return value
tank.levelReading a field
dial.position = 3Writing to a field
public ComponentsLight[] lights;Referencing with an array
public IComponentsSwitch target;Referencing by interface
public UdonBehaviour other;Referencing by the runtime’s type
(int)other.GetProgramVariable("count")Reading a value
other.SetProgramVariable("count", 5)Writing a value
GetProgramVariable(nameof(count))Reading and writing your own variable
SyntaxDescriptionErrorReasonInstead
SendCustomEvent("Ping")You have no method named PingTUKI0001by designFix the name. Writing it with nameof avoids spelling mistakes
SendCustomEvent(nameof(Ping))Ping is privateTUKI0001by designMake it public
SendCustomEvent(nameof(Hit))Hit takes an argumentTUKI0001by designAdd [NetworkCallable] to Hit
[NetworkCallable] private void Ping()The attribute is on a method that isn’t publicTUKI0001runtimeMake it public
[NetworkCallable] public static void Ping()The attribute is on a static methodTUKI0001runtimeRemove static
[NetworkCallable] public int Ping()The attribute is on a method with a return valueTUKI0001runtimeMake the return type void
[NetworkCallable] public void Ping(a, ..., i)The attribute is on a method with 9 argumentsTUKI0001runtimeKeep the arguments to 8 or fewer
GetComponents<UdonBehaviour>()Use UdonBehaviour as the type argument to get all of themTUKI0001not yetWrite GetComponents(typeof(UdonBehaviour)) instead
sink.Take(p)Passing a struct you wrote to the other behaviourTUKI0101undecidedPass it broken up into its parts
sink.Take(ref n)Passing it to the other behaviour with refTUKI0001not yetGet it back through a return value
Gate.Ping()Calling a static method on the other behaviour’s typeTUKI0001undecidedRemove static and call it as an instance method on the other behaviour
holder.__stateTouching a field on the other behaviour that starts with __TUKI0001by designRemove the __ from the front of the name
Call by nameCall with the type
How you hold the other behaviourTsukimiBehaviour or UdonBehaviourThe other behaviour’s type
Syntaxother.SendCustomEvent("Ping")door.Open()
Methods you can callpublic ones (methods with arguments need [NetworkCallable])public and not static
ArgumentsSendCustomEvent can’t pass any (the form that sends across the network is sync)Can pass (a single value, or an array of it)
Return valueCan’t receive oneCan receive one
Name errorsThe compiler checks this only when you send to yourselfThe compiler checks this
The other behaviour’s fileNot neededNeeded

Calling with the type is converted into calling by name before it runs. Arguments are first written into the other behaviour’s variable, then passed, so types that can’t be stored in a variable can’t be passed. This is why you can’t pass a struct you wrote (see the table above).

GetComponent returns null if that type isn’t attached. transform.Find also returns null if there’s no child with that name. Using the returned null as is stops execution (Udon’s execution model).

The compiler checks names you send to yourself. If you write SendCustomEvent(nameof(Ping)) and Ping doesn’t exist on yourself, compilation stops.

The compiler doesn’t check names you send to another behaviour. The other program is in a different file, and the compiler doesn’t look at that file. Compilation succeeds even if you send a name the other behaviour doesn’t have. GetProgramVariable and SetProgramVariable don’t check the names you pass them, for the same reason.

To keep the examples on this page self-contained in a single file, the other behaviour is written as just an abstract shape. When you actually write this, put the other behaviour in a separate file as a concrete behaviour and inherit from it, or write that type directly as the field’s type.

  • Calling by name makes the other behaviour run that method
  • Sending a name the other behaviour doesn’t have does nothing. It isn’t an error
  • Reading and writing by name reaches the other behaviour’s private fields too. Visibility is a C# rule, and it doesn’t carry over to the runtime side

Components attached to the same GameObject are obtained with GetComponent. If none is found, null is returned, so check before using it.

Write the type you want as the type argument. Returns one component attached to the same GameObject.

using UnityEngine;
using Tsukimi;
public class ComponentsGetComponent : TsukimiBehaviour
{
void Start()
{
Renderer r = GetComponent<Renderer>();
Debug.Log(r == null);
}
}

You can also pass a type value instead of a type argument. The return value is Component, so cast it to the type you use.

using UnityEngine;
using Tsukimi;
public class ComponentsGetComponentTypeof : TsukimiBehaviour
{
void Start()
{
Renderer r = (Renderer)GetComponent(typeof(Renderer));
Debug.Log(r == null);
}
}

Searches yourself and the objects hanging below you.

using UnityEngine;
using Tsukimi;
public class ComponentsGetComponentInChildren : TsukimiBehaviour
{
void Start()
{
Renderer r = GetComponentInChildren<Renderer>();
Debug.Log(r == null);
}
}

Searches yourself and the objects above you.

using UnityEngine;
using Tsukimi;
public class ComponentsGetComponentInParent : TsukimiBehaviour
{
void Start()
{
Renderer r = GetComponentInParent<Renderer>();
Debug.Log(r == null);
}
}

Passing true also searches inactive objects. If you don’t pass it, only active ones are searched.

using UnityEngine;
using Tsukimi;
public class ComponentsGetComponentInactive : TsukimiBehaviour
{
void Start()
{
Renderer r = GetComponentInChildren<Renderer>(true);
Debug.Log(r == null);
}
}

When multiple components of the same type are attached, get all of them as an array.

using UnityEngine;
using Tsukimi;
public class ComponentsGetComponents : TsukimiBehaviour
{
void Start()
{
Renderer[] all = GetComponents<Renderer>();
Debug.Log(all.Length);
}
}

Get all of them as an array, including the ones on objects hanging below.

using UnityEngine;
using Tsukimi;
public class ComponentsGetComponentsInChildren : TsukimiBehaviour
{
void Start()
{
Renderer[] all = GetComponentsInChildren<Renderer>();
Debug.Log(all.Length);
}
}

If you hold a GameObject, you can get the components attached to that object.

using UnityEngine;
using Tsukimi;
public class ComponentsGetComponentOfObject : TsukimiBehaviour
{
public GameObject target;
void Start()
{
Renderer r = target.GetComponent<Renderer>();
Debug.Log(r == null);
}
}

Searches for a child object by name. Returns null if it isn’t found.

using UnityEngine;
using Tsukimi;
public class ComponentsFindChild : TsukimiBehaviour
{
void Start()
{
Transform child = transform.Find("Lamp");
Debug.Log(child == null);
}
}

You can also get by a behaviour type you wrote. You can call methods directly on the returned value.

using UnityEngine;
using Tsukimi;
public abstract class ComponentsLamp : TsukimiBehaviour
{
public abstract void TurnOn();
}
public class ComponentsGetComponentOwnType : TsukimiBehaviour
{
void Start()
{
ComponentsLamp lamp = GetComponent<ComponentsLamp>();
if (lamp != null)
{
lamp.TurnOn();
}
}
}

You can also get all of a behaviour type you wrote as an array.

using UnityEngine;
using Tsukimi;
public abstract class ComponentsSpeaker : TsukimiBehaviour
{
public abstract void Play();
}
public class ComponentsGetComponentsOwnType : TsukimiBehaviour
{
void Start()
{
ComponentsSpeaker[] all = GetComponents<ComponentsSpeaker>();
Debug.Log(all.Length);
}
}

You can also get by a base type. This is the form for when you want to hold something you’ll only call by name, without knowing the other behaviour’s type.

using UnityEngine;
using Tsukimi;
public class ComponentsGetComponentBehaviour : TsukimiBehaviour
{
void Start()
{
TsukimiBehaviour b = GetComponent<TsukimiBehaviour>();
Debug.Log(b == null); // => the runtime's value
}
}

You can also get by the UdonBehaviour type that the runtime has.

using UnityEngine;
using Tsukimi;
using VRC.Udon;
public class ComponentsGetComponentUdonBehaviour : TsukimiBehaviour
{
void Start()
{
UdonBehaviour b = GetComponent<UdonBehaviour>();
Debug.Log(b == null);
}
}
using UnityEngine;
using Tsukimi;
using VRC.Udon;
public class R_components_get_components_udon : TsukimiBehaviour
{
void Start()
{
UdonBehaviour[] all = GetComponents<UdonBehaviour>();
Debug.Log(all.Length);
}
}

Passing a method’s name as a string calls that method. Only public methods can be called by name. The name can be written with nameof or as a string literal.

Calls your own method by name. Writing a name that doesn’t exist on yourself stops compilation.

using UnityEngine;
using Tsukimi;
public class ComponentsSendCustomEvent : TsukimiBehaviour
{
public void Ping()
{
Debug.Log(1);
}
void Start()
{
SendCustomEvent(nameof(Ping));
}
}
using UnityEngine;
using Tsukimi;
public class R_components_unknown_name : TsukimiBehaviour
{
void Start()
{
SendCustomEvent("Ping");
}
}

If you hold the other behaviour as TsukimiBehaviour, you can call it by name without knowing its type. The compiler doesn’t check names in this form (Scope of name checking).

using UnityEngine;
using Tsukimi;
public class ComponentsSendCustomEventOther : TsukimiBehaviour
{
public TsukimiBehaviour other;
void Start()
{
other.SendCustomEvent("Ping");
}
}
using UnityEngine;
using Tsukimi;
public class ComponentsSendDelayedSeconds : TsukimiBehaviour
{
public void Ping()
{
Debug.Log(1);
}
void Start()
{
SendCustomEventDelayedSeconds(nameof(Ping), 1.5f);
}
}
using UnityEngine;
using Tsukimi;
public class ComponentsSendDelayedFrames : TsukimiBehaviour
{
public void Ping()
{
Debug.Log(1);
}
void Start()
{
SendCustomEventDelayedFrames(nameof(Ping), 10);
}
}

The third argument chooses where in the frame the call happens. If you don’t write it, it defaults to Update.

using UnityEngine;
using Tsukimi;
using VRC.Udon.Common.Enums;
public class ComponentsSendDelayedTiming : TsukimiBehaviour
{
public void Ping()
{
Debug.Log(1);
}
void Start()
{
SendCustomEventDelayedSeconds(nameof(Ping), 1f, EventTiming.LateUpdate);
}
}

A method with arguments can’t be called by its plain name. Adding [NetworkCallable] makes it callable by its plain name. SendCustomEvent has no way to pass arguments.

using UnityEngine;
using Tsukimi;
using VRC.SDK3.UdonNetworkCalling;
public class ComponentsSendCustomEventWithArguments : TsukimiBehaviour
{
[NetworkCallable]
public void Hit(int damage)
{
Debug.Log(damage);
}
void Start()
{
SendCustomEvent(nameof(Hit));
}
}
using UnityEngine;
using Tsukimi;
public class R_components_send_with_args : TsukimiBehaviour
{
public void Hit(int damage) { Debug.Log(damage); }
void Start()
{
SendCustomEvent(nameof(Hit));
}
}

Holding the other behaviour’s type lets you call its methods directly. The compiler checks arguments and return values, so mistakes surface earlier than with calling by name. To keep the examples below self-contained in a single file, the other behaviour is written as just an abstract shape (What can be written in a single file).

using UnityEngine;
using Tsukimi;
public abstract class ComponentsDoor : TsukimiBehaviour
{
public abstract void Open();
}
public class ComponentsCallAcross : TsukimiBehaviour
{
public ComponentsDoor door;
void Start()
{
door.Open();
}
}

You can pass arguments. You can pass a single value, or an array of it. You can’t pass a struct you wrote.

using UnityEngine;
using Tsukimi;
public abstract class ComponentsCounter : TsukimiBehaviour
{
public abstract void Add(int n);
}
public class ComponentsCallAcrossArgument : TsukimiBehaviour
{
public ComponentsCounter counter;
void Start()
{
counter.Add(5);
}
}
using UnityEngine;
using Tsukimi;
public struct R_components_pair
{
public int a;
public int b;
}
public abstract class R_components_sink : TsukimiBehaviour
{
public abstract void Take(R_components_pair p);
}
public class R_components_cross_struct : TsukimiBehaviour
{
public R_components_sink sink;
void Start()
{
R_components_pair p;
p.a = 1;
p.b = 2;
sink.Take(p);
}
}
using UnityEngine;
using Tsukimi;
public abstract class ComponentsScoreBoard : TsukimiBehaviour
{
public abstract int Score();
}
public class ComponentsCallAcrossReturn : TsukimiBehaviour
{
public ComponentsScoreBoard board;
void Start()
{
Debug.Log(board.Score());
}
}
using UnityEngine;
using Tsukimi;
public abstract class ComponentsTank : TsukimiBehaviour
{
public int level;
}
public class ComponentsFieldAcrossRead : TsukimiBehaviour
{
public ComponentsTank tank;
void Start()
{
Debug.Log(tank.level);
}
}
using UnityEngine;
using Tsukimi;
public abstract class ComponentsDial : TsukimiBehaviour
{
public int position;
}
public class ComponentsFieldAcrossWrite : TsukimiBehaviour
{
public ComponentsDial dial;
void Start()
{
dial.position = 3;
Debug.Log(dial.position);
}
}

You can hold other behaviours of the same type in an array. Assign them from the inspector and loop over them in a single loop.

using UnityEngine;
using Tsukimi;
public abstract class ComponentsLight : TsukimiBehaviour
{
public abstract void TurnOn();
}
public class ComponentsReferenceArray : TsukimiBehaviour
{
public ComponentsLight[] lights;
void Start()
{
for (int i = 0; i < lights.Length; i++)
{
lights[i].TurnOn();
}
}
}

You can also hold a behaviour by an interface type attached to it (interfaces).

using UnityEngine;
using Tsukimi;
public interface IComponentsSwitch
{
void Toggle();
}
public class ComponentsReferenceInterface : TsukimiBehaviour
{
public IComponentsSwitch target;
void Start()
{
if (target != null)
{
target.Toggle();
}
}
}

Holding it as UdonBehaviour lets you call by name and read and write variables by name. You can’t call methods directly.

using UnityEngine;
using Tsukimi;
using VRC.Udon;
public class ComponentsReferenceUdonBehaviour : TsukimiBehaviour
{
public UdonBehaviour other;
void Start()
{
other.SendCustomEvent("Ping");
other.SetProgramVariable("count", 3);
Debug.Log((int)other.GetProgramVariable("count"));
}
}

You can also read and write the other behaviour’s variables by name. This works even without holding its type.

The return value is object, so cast it to the type you use. If the cast type doesn’t match the contents, the value won’t be what you wrote.

using UnityEngine;
using Tsukimi;
public class ComponentsGetProgramVariable : TsukimiBehaviour
{
public TsukimiBehaviour other;
void Start()
{
int count = (int)other.GetProgramVariable("count");
Debug.Log(count);
}
}
using UnityEngine;
using Tsukimi;
public class R_components_progvar_no_cast : TsukimiBehaviour
{
public TsukimiBehaviour other;
void Start()
{
int count = other.GetProgramVariable("count");
Debug.Log(count);
}
}

The second argument takes object, so you can pass a value of any type.

using UnityEngine;
using Tsukimi;
public class ComponentsSetProgramVariable : TsukimiBehaviour
{
public TsukimiBehaviour other;
void Start()
{
other.SetProgramVariable("count", 5);
}
}

This also works on yourself. Writing the name with nameof makes it change together when you rename the field.

using UnityEngine;
using Tsukimi;
public class ComponentsProgramVariableSelf : TsukimiBehaviour
{
private int count = 3;
void Start()
{
SetProgramVariable(nameof(count), 9);
Debug.Log((int)GetProgramVariable(nameof(count)));
}
}