Quick answer: Implement INotifyBindablePropertyChanged. Match binding-path to a public property. Fire propertyChanged from setters.

Health bar bound via UXML. Health field decreases. UI doesn’t. Binding has no signal that the source value changed.

The Symptom

Initial value displays correctly. Subsequent updates don’t reflect. Inspector edits propagate (because of SerializedObject); runtime changes don’t.

The Fix

public class PlayerVM : INotifyBindablePropertyChanged {
    int _hp;

    [CreateProperty]
    public int Health {
        get => _hp;
        set {
            if (_hp == value) return;
            _hp = value;
            propertyChanged?.Invoke(this,
                new BindablePropertyChangedEventArgs(nameof(Health)));
        }
    }

    public event EventHandler<BindablePropertyChangedEventArgs> propertyChanged;
}

UXML side:

<ProgressBar binding-path="Health" low-value="0" high-value="100"/>

Bind once at runtime:

root.dataSource = playerVM;
root.SetBinding("value", new DataBinding { dataSourcePath = new("Health") });

Now playerVM.Health-- updates the bar without manual repaint calls.

SerializedObject Path

For inspector data: rootVisualElement.Bind(serializedObject). Then bindings auto-poll. Outside of Editor, use the runtime data binding API above.

Verifying

Decrement Health from script. Bar shrinks immediately. Console-log propertyChanged firing per setter call.

“Notify on set. Path matches. UI listens.”

Related Issues

For Cinemachine impulse, see impulse. For input system composite, see composite.

Notify on set. UI follows.