Reference

Every binding, what it raises, and what it will not do.

Updated 2026-09-08

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 Start that runs twice leaves one.
  • A null control or observable is ignored rather than halting the behaviour.
  • Handlers already added stay when you Watch again or Unbind — 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.

Something went wrong Reload