核心概念
QfStudio.Godette.ReactiveUI 是什么
ReactiveUI 是一个可组合、跨平台的 .NET MVVM(Model-View-ViewModel)框架。它使用 Rx(Reactive Extensions)将 UI 元素绑定到 ViewModel 的属性和命令,使视图与业务逻辑保持清晰的分离。
QfStudio.Godette.ReactiveUI 提供让 ReactiveUI 在 Godot Engine 上运行所需的平台服务 —— 调度器、视图激活、属性变更通知以及命令绑定。如果你曾在 Avalonia 或 WPF 上使用过 ReactiveUI,那么 this.Bind / this.BindCommand / WhenActivated 的用法完全一样,只是底层对接的是 Godot 的节点与信号。
激活语义
当视图对应的 Godot Node 位于场景树中 且 IsNodeReady() 返回 true 时,视图被激活(true)。在 TreeExited 时失活(false)。其语义等价于 Avalonia 的 AttachedToVisualTree / DetachedFromVisualTree。
激活是如何检测的
GodotActivationFetcher 从以下三条路径发出 true:
Ready信号(首次进入,所有子节点已初始化);TreeEntered+IsNodeReady()(节点已就绪后的重新进入);- 订阅时的初始检查
IsInsideTree() && IsNodeReady()。
在 TreeExited 时发出 false。
注意:C# 虚方法 _Ready 在 Ready 信号发出 之前 执行,因此在 _Ready 中赋值的 ViewModel 在 WhenActivated 触发时已经就绪。
usings
本指南中的所有示例均假定以下 using 已在作用域内:
using QfStudio.Godette.ReactiveUI;
using ReactiveUI;
using System.Reactive.Disposables; // 用于贯穿全文的 DisposeWith(d)[GodotViewFor<T>] 以及生成的 ViewModel 属性由本库自带的源生成器生成,位于 QfStudio.Godette.ReactiveUI 命名空间下 —— 无需额外的 using 或包引用。
基本设置
ViewModel 实现 IActivatableViewModel。View 使用 [GodotViewFor<T>] 源生成器特性来实现 IViewFor<T>。在构造函数中通过 WhenActivated 声明绑定:
// ViewModel
public partial class MyViewModel : ReactiveObject, IActivatableViewModel
{
public ViewModelActivator Activator { get; } = new();
[Reactive] public partial string Name { get; set; } = "";
}
// View(.tscn 根脚本)
[GodotViewFor<MyViewModel>]
public partial class MyScene : Control
{
public MyScene()
{
this.WhenActivated(d =>
{
this.Bind(ViewModel, vm => vm.Name, v => v.NameEdit.Text)
.DisposeWith(d);
});
}
public override void _Ready()
{
ViewModel = new MyViewModel();
}
}为什么在 _Ready 中赋值 ViewModel?
Godot 中 没有内置的 UI/路由框架 来替你创建视图并注入其 ViewModel。不同于 Avalonia + ReactiveUI —— 在那里 RoutingState 与平台的 IViewLocator 通常会在导航期间构造 View 并为你设置 ViewModel —— 在 Godot 中,每个场景的根脚本必须在某个时机自行实例化自己的 ViewModel。推荐的位置是 _Ready,原因如下:
- Godot 保证
_Ready在所有子节点初始化之后才被调用,因此此处[SceneTree]生成的节点属性(例如NameEdit)非空; - Godot 的 C# 虚方法
_Ready在Ready信号发出 之前 执行,而WhenActivated通过GodotActivationFetcher订阅的是Ready信号,因此_Ready中的ViewModel = new MyViewModel();会在WhenActivated回调触发之前完成。
如果你自行实现导航(参见 Routing),RoutedViewController 会在解析出视图后设置 view.ViewModel = viewModel,因此对于路由的视图无需在 _Ready 中赋值 —— 仅顶层/根场景需要。