How components interact
Forms that compile (30)
Section titled “Forms that compile (30)”| Syntax | Description |
|---|---|
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.level | Reading a field |
dial.position = 3 | Writing 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 |
Forms that don’t compile (12)
Section titled “Forms that don’t compile (12)”| Syntax | Description | Error | Reason | Instead |
|---|---|---|---|---|
SendCustomEvent("Ping") | You have no method named Ping | TUKI0001 | by design | Fix the name. Writing it with nameof avoids spelling mistakes |
SendCustomEvent(nameof(Ping)) | Ping is private | TUKI0001 | by design | Make it public |
SendCustomEvent(nameof(Hit)) | Hit takes an argument | TUKI0001 | by design | Add [NetworkCallable] to Hit |
[NetworkCallable] private void Ping() | The attribute is on a method that isn’t public | TUKI0001 | runtime | Make it public |
[NetworkCallable] public static void Ping() | The attribute is on a static method | TUKI0001 | runtime | Remove static |
[NetworkCallable] public int Ping() | The attribute is on a method with a return value | TUKI0001 | runtime | Make the return type void |
[NetworkCallable] public void Ping(a, ..., i) | The attribute is on a method with 9 arguments | TUKI0001 | runtime | Keep the arguments to 8 or fewer |
GetComponents<UdonBehaviour>() | Use UdonBehaviour as the type argument to get all of them | TUKI0001 | not yet | Write GetComponents(typeof(UdonBehaviour)) instead |
sink.Take(p) | Passing a struct you wrote to the other behaviour | TUKI0101 | undecided | Pass it broken up into its parts |
sink.Take(ref n) | Passing it to the other behaviour with ref | TUKI0001 | not yet | Get it back through a return value |
Gate.Ping() | Calling a static method on the other behaviour’s type | TUKI0001 | undecided | Remove static and call it as an instance method on the other behaviour |
holder.__state | Touching a field on the other behaviour that starts with __ | TUKI0001 | by design | Remove the __ from the front of the name |
Two ways to call
Section titled “Two ways to call”| Call by name | Call with the type | |
|---|---|---|
| How you hold the other behaviour | TsukimiBehaviour or UdonBehaviour | The other behaviour’s type |
| Syntax | other.SendCustomEvent("Ping") | door.Open() |
| Methods you can call | public ones (methods with arguments need [NetworkCallable]) | public and not static |
| Arguments | SendCustomEvent can’t pass any (the form that sends across the network is sync) | Can pass (a single value, or an array of it) |
| Return value | Can’t receive one | Can receive one |
| Name errors | The compiler checks this only when you send to yourself | The compiler checks this |
| The other behaviour’s file | Not needed | Needed |
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).
When getting fails
Section titled “When getting fails”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).
Scope of name checking
Section titled “Scope of name checking”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.
What can be written in a single file
Section titled “What can be written in a single file”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.
What happens when you call by name
Section titled “What happens when you call by name”- 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
privatefields too. Visibility is a C# rule, and it doesn’t carry over to the runtime side
Getting components
Section titled “Getting components”Components attached to the same GameObject are obtained with GetComponent.
If none is found, null is returned, so check before using it.
Getting by type argument
Section titled “Getting by type argument”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); }}Getting by type value
Section titled “Getting by type value”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); }}Getting from children
Section titled “Getting from children”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); }}Getting from parents
Section titled “Getting from parents”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); }}Getting including inactive objects
Section titled “Getting including inactive objects”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); }}Getting all of the same type
Section titled “Getting all of the same type”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); }}Getting all from children
Section titled “Getting all from children”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); }}Getting from another object
Section titled “Getting from another object”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); }}Getting a child by name
Section titled “Getting a child by name”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); }}Getting by a type you wrote
Section titled “Getting by a type you wrote”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(); } }}Getting all of a type you wrote
Section titled “Getting all of a type you wrote”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); }}Getting by a base type
Section titled “Getting by a base type”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 }}Getting by the runtime’s type
Section titled “Getting by the runtime’s type”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); }}Calling by name
Section titled “Calling by name”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.
Calling yourself
Section titled “Calling yourself”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"); }}Calling another program
Section titled “Calling another program”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"); }}Calling after a delay in seconds
Section titled “Calling after a delay in seconds”using UnityEngine;using Tsukimi;
public class ComponentsSendDelayedSeconds : TsukimiBehaviour{ public void Ping() { Debug.Log(1); }
void Start() { SendCustomEventDelayedSeconds(nameof(Ping), 1.5f); }}Calling after a delay in frames
Section titled “Calling after a delay in frames”using UnityEngine;using Tsukimi;
public class ComponentsSendDelayedFrames : TsukimiBehaviour{ public void Ping() { Debug.Log(1); }
void Start() { SendCustomEventDelayedFrames(nameof(Ping), 10); }}Choosing when the call happens
Section titled “Choosing when the call happens”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); }}Registering a method with arguments
Section titled “Registering a method with arguments”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)); }}Typed references
Section titled “Typed references”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).
Calling a method
Section titled “Calling a method”using UnityEngine;using Tsukimi;
public abstract class ComponentsDoor : TsukimiBehaviour{ public abstract void Open();}
public class ComponentsCallAcross : TsukimiBehaviour{ public ComponentsDoor door;
void Start() { door.Open(); }}Calling with arguments
Section titled “Calling with arguments”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); }}Calling with a return value
Section titled “Calling with a return value”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()); }}Reading a field
Section titled “Reading a field”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); }}Writing to a field
Section titled “Writing to a field”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); }}Referencing with an array
Section titled “Referencing with an array”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(); } }}Referencing by interface
Section titled “Referencing by interface”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(); } }}Referencing by the runtime’s type
Section titled “Referencing by the runtime’s type”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")); }}Reading and writing variables by name
Section titled “Reading and writing variables by name”You can also read and write the other behaviour’s variables by name. This works even without holding its type.
Reading a value
Section titled “Reading a value”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); }}Writing a value
Section titled “Writing a value”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); }}Reading and writing your own variable
Section titled “Reading and writing your own variable”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))); }}