Skip to main content
Unity 2022.3+ · Source Generators · MIT

MVVM for Unity
without the boilerplate

A field becomes a notifying property. A method becomes a command. Bindings compile to direct calls: zero reflection, zero string lookups.

Get StartedTutorialsopenupm add tech.aspid.mvvm
CounterViewModel.cs
[ViewModel]
public sealed partial class CounterViewModel
{
[Bind] private int _count;

[RelayCommand]
private void Increment() => Count++;
}
CounterView.cs
[View]
public sealed partial class CounterView : MonoView
{
[RequireBinder(typeof(int))]
[SerializeField] private MonoBinder[] _count;

[RequireBinder(typeof(IRelayCommand))]
[SerializeField] private MonoBinder[] _incrementCommand;
}
0
reflection in bindings
190+
converters in StarterKit
600+
ready-made binders
4
binding modes
Before / After

Twenty-six lines become seven.

The generator writes the property, the change notification and the command. You keep the field and the method.

Hand-written INotifyPropertyChanged
public class CounterViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private int _count;

public int Count
{
get => _count;
set
{
if (_count == value) return;
_count = value;
PropertyChanged?.Invoke(this,
new PropertyChangedEventArgs(nameof(Count)));
}
}

public ICommand IncrementCommand { get; }

public CounterViewModel()
{
IncrementCommand = new RelayCommand(() => Count++);
}
}
With Aspid.MVVM
[ViewModel]
public sealed partial class CounterViewModel
{
[Bind] private int _count;

[RelayCommand]
private void Increment() => Count++;
}
[ViewModel][Bind][RelayCommand][View][RequireBinder]
How it flows

View, Binder, ViewModel. Nothing in between.

A binder is a MonoBehaviour on the UI element. The generated ViewModel hands it a typed member: no dictionary, no reflection, no string keys at runtime.

ViewMonoView · Unity sceneBinderTextBinder · SliderBinder · …ViewModel[ViewModel] partial classSetValue(T)ValueChangedIBinder<T>OneWayToSource
OneWayTwoWayOneTimeOneWayToSourceThe ViewModel sets the upper bound. Every binder picks its own mode in the Inspector.
In the Editor

Wire it up in the Inspector, not in code.

Drop a binder on any UI element, pick the ViewModel member and the mode. Converters, formats and fallbacks are serialized fields. Designers rebind without touching C#.

  • Hundreds of binders. uGUI, TextMeshPro, Transform, Animator, AudioSource, Rigidbody and more.
  • Converters as slots. Chain a format, a clamp or a lookup, or write your own in a dozen lines.
  • Analyzer-checked. A missing partial or a wrong CanExecute signature is a compile-time diagnostic with a code fix.
Why Aspid

Built for the frame budget.

Every design decision starts from the same question: what does this cost at runtime?

Source Generator, not reflection

Bindings are emitted at compile time as direct calls. No reflection, no boxing, no string lookups per frame.

Attributes instead of boilerplate

A field with [Bind] becomes a notifying property. A method with [RelayCommand] becomes a command. Nothing else to write.

Four binding modes

OneWay, TwoWay, OneTime and OneWayToSource. The ViewModel sets the upper bound, every binder picks its own mode in the Inspector.

StarterKit out of the box

Hundreds of binders for uGUI, TextMeshPro, Transform, Animator, AudioSource and more, plus a catalogue of 190+ converters.

Observable collections

ObservableList, FilteredList and CreateSync keep model and ViewModel lists in step; virtualized lists render only what is visible.

Analyzers that catch mistakes early

Roslyn analyzers flag a missing partial, a wrong CanExecute signature or a field used where the property was meant, with code fixes.

Ship your first bound screen in ten minutes.

Install from OpenUPM or the Asset Store, open the Counter sample, press Play.