Generate R3 Observables from Godot signals via Chickensoft.GodotNodeInterfaces
We want:
- ✅ To program to interfaces (via Chickensoft.GodotNodeInterfaces)
- ✅ To use ReactiveX-style observables (via R3)
- ❌ But we don't want to manually wire every Godot signal ourselves
This library gives us declarative, strongly-typed observables for signals across interfaces, Godot built-ins, and [Signal]
delegates — all auto-generated.
public partial class MyMenu : Control, IControl {
private IBaseButton button;
public void OnReady() {
button.Pressed += HandlePressed;
}
public void OnExitTree() {
button.Pressed -= HandlePressed;
}
private void HandlePressed() => GD.Print("Pressed!");
}
- ❌ Must manage subscribe/unsubscribe manually
- ❌ Can’t compose with R3
Observable.FromEvent<PressedEventHandler, Unit>(
h => () => h(Unit.Default),
h => button.Pressed += h,
h => button.Pressed -= h
)
.Subscribe(_ => GD.Print("Pressed"))
.AddTo(this);
- ✅ Works
- ❌ Verbose
- ❌ Easy to miswire
button.OnPressedAsObservable()
.Subscribe(_ => GD.Print("Pressed"))
.AddTo(this);
- ✅ Clean
- ✅ Reactive
- ✅ Auto cleanup
- ✅ Interface- and class-based support
- ✅ Works for custom
[Signal]
s and built-ins
This project provides Roslyn source generators that create R3 observables for:
- All
event
s defined inChickensoft.GodotNodeInterfaces
interfaces. - All signals in built-in Godot classes (e.g.,
Button
,Node2D
) if you define apartial
class in your project. - Any user-defined
[Signal]
delegate in your own Godot partial classes.
These generators output strongly-typed, composable observables for signals using idiomatic R3 patterns.
- ✅ Program to interfaces with
Chickensoft.GodotNodeInterfaces
- ✅ Use ReactiveX (
R3
) for signal handling - ✅ Avoid writing manual signal-to-observable plumbing
- ✅ Get compile-time safety and auto-cleanup via
.AddTo(this)
public partial class MyMenu : Control, IControl {
private IBaseButton doSomethingButton;
public override void _Ready() {
doSomethingButton.OnPressedAsObservable()
.Subscribe(_ => GD.Print("Pressed!"))
.AddTo(this);
}
}
If you define your own [Signal]
-annotated delegates:
[Signal] private delegate void ToggledEventHandler(bool toggled);
public partial class ToggleThing : Node {
private Subject<bool> _onToggled = new();
public Observable<bool> OnToggled => _onToggled ??= ConnectToggled();
}
And for built-in classes:
public partial class Button : Godot.Button { }
Triggers generation of:
private Subject<Unit>? _onPressed;
public Observable<Unit> OnPressed => _onPressed ??= ConnectPressed();
- 🔧 Zero config — just mark your classes
partial
and you're done - ⚙️ Supports 0–5 signal parameters
- 📡 Built-in Godot signal support via known interface mappings
- 🧠 Handles custom signal delegates with or without constructors
- 📎 Avoids duplicating built-in signal wiring
- 🧪 Thoroughly tested with diagnostic output
Install via NuGet:
dotnet add package LokiCat.GodotNodeInterfaces.Observables
Covers:
- Signals with 0–5 parameters
- Interfaces with custom and standard delegate types
- Edge cases like non-
partial
classes or invalid delegate wiring
dotnet test
-
Scans
Chickensoft.GodotNodeInterfaces
for allinterface
s withevent
s. -
For each:
- Emits extension method
On[EventName]AsObservable()
- Emits extension method
-
Separately, scans all
partial class
es in your project:- If it inherits a known Godot class (e.g.,
Button
), emits_onX
/OnX
fields + observable logic for signals likepressed
,toggled
, etc. - If it defines a
[Signal]
-annotated delegate, generates a subject-backed observable for it.
- If it inherits a known Godot class (e.g.,
-
Supports 0–5 parameters; skips and warns if more.
- Chickensoft for
GodotNodeInterfaces
- Cysharp for
R3
- Godot C# community for enabling typed signals in C#
MIT