Reference
Every binding, what it raises, and what it will not do.
Everything lives in Udonite.Binding.
Every binding shares the same shape:
| Member | What it does |
|---|---|
Watch(control) |
starts watching; raises the events and nothing else |
Bind(control, observable) |
the same, and keeps an observable in step as well |
Poll() |
reads the control; call it from Update |
Unbind() |
stops watching and lets go of the observable |
Value |
what the control holds now, whether or not an observable is bound |
And the same rules:
- The control is written at bind time, so it starts showing the value rather than whatever the designer left in it.
- Binding again replaces the previous binding rather than adding to it, so a
Startthat runs twice leaves one. - A null control or observable is ignored rather than halting the behaviour.
- Handlers already added stay when you
Watchagain orUnbind— they belong to whoever added them, not to the control. Clear the event field to drop them. - Only the player's changes raise an event. Moving a control by assigning its observable does not.
Watched controls
These need Poll() in your Update.
SliderBinding
| Member | |
|---|---|
Action<float> OnChanged |
raised when the player has moved the handle |
Bind(Slider, Observable<float>) |
both directions |
float Value |
where the handle is |
ToggleBinding
| Member | |
|---|---|
Action<bool> OnChanged |
raised when the player has clicked the box |
Bind(Toggle, Observable<bool>) |
both directions |
bool Value |
whether it is ticked |
InputBinding
| Member | |
|---|---|
Action<string> OnChanged |
every keystroke |
Action<string> OnEndEdit |
the player left the field having changed it |
Action OnFocused |
the player started editing |
Bind(TMP_InputField, Observable<string>) |
both directions |
string Value |
what is in the field |
OnChanged reports every keystroke, which is what a field bound to a live label should do. Read Value once on a button press instead if that is not what you want.
Writing the observable while the player is typing moves their caret to the end. The binding does not do this to itself — a poll pushes their text in and the write back is the same string — but code that assigns during editing will.
Driven controls
No events, no Poll(). An observable is how they are fed.
TextBinding<T>
| Member | |
|---|---|
Bind(TextMeshProUGUI, Observable<T>) |
shows the value |
Bind(TextMeshProUGUI, Observable<T>, prefix, suffix) |
with text either side |
Bind(label, score, "Score: ", "") and Bind(label, health, "", " HP"). A null value shows as an empty string rather than faulting.
ActiveBinding
| Member | |
|---|---|
Bind(GameObject, Observable<bool>) |
shown while true |
BindInverted(GameObject, Observable<bool>) |
shown while false |
Two bindings on one observable make a matched pair of signs.
A hidden object's behaviours stop running. Hiding the object a binding lives on stops its Update, and a Poll() on that object stops with it. Keep polling bindings on something that stays active.
FillBinding
| Member | |
|---|---|
Bind(Image, Observable<float>) |
a value already between 0 and 1 |
Bind(Image, Observable<float>, min, max) |
a value over a range you name |
The range is part of the binding because fillAmount is 0 to 1 and almost nothing a world counts is. Values outside it are clamped, and an empty or backwards range leaves the image empty rather than dividing by zero.
The Image needs its type set to Filled for fillAmount to do anything.
What this does not do, and why
Buttons. A click is an event rather than a value. Slider.value, Toggle.isOn and TMP_InputField.text can be read at any moment; "was clicked since last frame" cannot, so there is nothing to poll. A button still has to be wired in the inspector to a public method.
URL fields. A VRCUrlInputField accepts selection and never focuses — measured by calling ActivateInputField() on one directly, which leaves isFocused false. VRChat routes text into it through its own keyboard, and it cannot be exercised in the Editor at all. Use its OnEndEdit in the inspector, which is the route VRChat documents.
Lists of rows. Driving a set of rows from an ObservableList<T> means creating and destroying objects, which in a world means VRCObjectPool and decisions about ownership. That is a different package.
If a handler throws
An exception in Udon halts the behaviour for the rest of the session, and there is no catching one handler's and carrying on. A handler that throws takes down the rest of the notification and everything that behaviour would have done afterwards, so keep handlers to reading the value and updating something.