Telepath.Core
0.46.0
dotnet add package Telepath.Core --version 0.46.0
NuGet\Install-Package Telepath.Core -Version 0.46.0
<PackageReference Include="Telepath.Core" Version="0.46.0" />
<PackageVersion Include="Telepath.Core" Version="0.46.0" />
<PackageReference Include="Telepath.Core" />
paket add Telepath.Core --version 0.46.0
#r "nuget: Telepath.Core, 0.46.0"
#:package Telepath.Core@0.46.0
#addin nuget:?package=Telepath.Core&version=0.46.0
#tool nuget:?package=Telepath.Core&version=0.46.0
Telepath
基于 R3 的 MVVM UI 框架,面向 Godot 4.4+ / .NET 8
Feature
- 平台无关的 Core:
Telepath.Core不依赖 Godot,同一套 ViewModel 可复用到其他 R3 宿主 - 声明式绑定:绑定信息可存在于场景节点的.tscn文件中,由编辑器扩展可视化编辑,无需重新编译即可对绑定进行调整
- 源生成器:使用源生成器减少了大量样板代码
- 内嵌 R3 胶水:帧/时间时钟、Godot 信号转 Observable,无需再装官方
R3.Godotaddon
Requirements
- Godot 4.4+(.NET)
- .NET 8 SDK
Installation
NuGet
dotnet add package Telepath.Godot
Telepath.Godot 会带上 Telepath.Core 以及源生成器。
然后按下方 Addon 一节把编辑器插件接入工程。
From Source
- Clone 或 submodule 本仓库
- 在宿主工程(Godot.NET.Sdk)里加两个 ProjectReference:
<ProjectReference Include="path/to/Telepath/src/Telepath.Godot/Telepath.Godot.csproj" />
<ProjectReference Include="path/to/Telepath/src/Telepath.SourceGenerator/Telepath.SourceGenerator.csproj"
OutputItemType="Analyzer"
ReferenceOutputAssembly="false" />
Telepath.Godot 会附带 Telepath.Core
- 按下方 Addon 一节把编辑器插件拷进工程。
Addon
无论 NuGet 还是源码引用,都需要把 addon 复制(或符号链接)进宿主工程:
| 来源 | 目标 |
|---|---|
src/Telepath.Godot/Addon/(含 Editor/ Binding Dock) |
res://addons/Telepath/ |
构建 Godot 项目后然后在 项目设置 → 插件 里启用
插件会把 GDScript 外壳 FrameProviderDispatcher.gd 注册成 Autoload,运行时驱动 R3 时钟;导出的工程同样依赖这个 Autoload,请确认 project.godot 里有 [autoload] 条目
注意:
- Windows 上 git 符号链接需要
core.symlinks = true,否则直接复制 - 不要装官方
R3.Godotaddon:运行时子集已内嵌(namespace R3),同名类型会冲突 - addon 里的 GDScript 外壳与 Dock 由宿主主程序集编译,不会进入
Telepath.Godot.dll - 因此 addon 脚本用到的库类型必须是 public;不要用
InternalsVisibleTo("Telepath.Showcase")掩盖真实宿主的可见性问题
Quick Start
一个最简单的计数器:ViewModel → View → 场景绑定
1. ViewModel —— 只写字段与方法,生成器产出 R3 属性与命令:
using R3;
using Telepath.Core;
public sealed partial class CounterViewModel : ViewModel
{
[Bindable]
private int _count;
[Command(CanExecute = nameof(CanIncrement))]
private void OnIncrement() => Count.Value++;
private Observable<bool> CanIncrement() => Count.Select(c => c < 10);
}
[Bindable] private int _count 生成 BindableReactiveProperty<int> Count;[Command] OnIncrement() 生成 ReactiveCommand IncrementCommand(CanExecute 关联按钮 Disabled),两者都登记进 VM 的 DisposableBag
2. View —— 轻量化对象,标记与通知转发:
using Godot;
using Telepath.Godot;
[TelepathView<CounterViewModel>]
public partial class CounterView : Control
{
public override partial void _Notification(int what);
private CounterViewModel CreateViewModel() => new();
}
3. 场景 —— 一个 Label(%CountLabel)和一个 Button(%IncrementButton),使用UI绑定侧栏(在Inspector旁边),调整绑定关系
场景文件中将会附带这样的 metadata/telepath_bindings
metadata/telepath_bindings = [{
"kind": "Text",
"member": "Count",
"path": "%CountLabel"
}, {
"kind": "Command",
"member": "IncrementCommand",
"path": "%IncrementButton"
}]
运行时,编辑器扩展会解析这些绑定信息,并自动生成对应的代码
导航模型
Telepath 采用 ViewModel优先的状态驱动导航,不提供 URL / Route 路由器
页面状态存在于 ViewModel 层,Godot View 只负责把当前状态投影到场景
ShellViewModel
├─ Conductor
│ ├─ BackStack: [Page A, Page B]
│ └─ ActiveItem: Page C
└─ OverlayHost
├─ Popup: [Panel]
├─ Modal: [Dialog]
└─ Toast: [Toast]
主页面管理:Conductor
Conductor 是一个单内容槽的回退栈:
ActiveItem是当前页面,View 只需观察它Navigate(viewModel)停用当前页面并压入回退栈,再激活新页面Back()销毁离开的页面 ViewModel,并恢复上一页Close(viewModel)可以关闭当前页或回退栈中的指定页面CanGoBack和BackCommand可直接用于 UI 绑定
初始: Active=A, Stack=[]
Navigate(B): Active=B, Stack=[A]
Navigate(C): Active=C, Stack=[A, B]
Back(): Active=B, Stack=[A] // C Dispose
子页面通常通过构造函数被注入更小的 INavigator 接口,只发出导航请求,不依赖具体宿主:
public sealed class DirectoryViewModel(INavigator navigator) : ViewModel
{
private void OnOpenCounter() =>
navigator.Navigate(new CounterViewModel());
}
Conductor 保留回退栈中的 ViewModel,但不缓存 Godot View。ActiveItem 变化时,ContentPresenter 会释放旧 View;返回时再根据同一个 ViewModel 创建新 View。因此需要跨页面保留的状态应放在 ViewModel 或独立领域模型中,不要依赖 Godot 节点实例一直存活。
覆盖层宿主:OverlayHost
覆盖层不进入主页面回退栈。OverlayHost 管理多个具名 Band,每个 Band 都是独立的栈;Order 决定 Band 之间的视觉层级。
| 内建 Band | Order | 处理被覆盖的ViewModel | 默认 Cover 模式 | 阻断下层输入 |
|---|---|---|---|---|
Popup |
0 | 是 | Pause |
是 |
Modal |
100 | 是 | Pause |
是 |
Toast |
200 | 否 | Continue |
否 |
CoverMode.Pause对被覆盖项调用IActivatable.Deactivate(),关闭后重新激活CoverMode.Continue让被覆盖项继续运行- 被覆盖的页面和 Overlay View 会保留在场景树中并继续绑定,只有出栈项会被释放
BlocksPassThrough控制 GUI 点击与焦点隔离,与CoverMode是两个独立概念
Pause 表示 ViewModel 展示生命周期的停用,不会自动设置 SceneTree.Paused,也不会停止 Node._Process() / _PhysicsProcess()。游戏宿主需在 IActivatable 或自己的会话服务中显式暂停玩法和输入。
组合返回策略
返回优先级由 Shell 决定,而不是框架全局硬编码。常见策略是先关闭最高的可回退 Overlay,再返回主页面:
public sealed class ShellViewModel : Conductor
{
public OverlayHost Overlay { get; }
public ShellViewModel()
{
Overlay = Track(new OverlayHost(() => ActiveItem.Value));
Track(Overlay.HasBackableOverlay.Subscribe(_ => UpdateCanGoBack()));
}
public override bool Back() => Overlay.Back() || base.Back();
public override void Navigate(IViewModel viewModel)
{
// 避免“清 Overlay → 旧页面短暂激活 → 立即换页”。
Overlay.Clear(resumeCovered: false);
base.Navigate(viewModel);
}
protected override bool ComputeCanGoBack() =>
Overlay.HasBackableOverlay.Value || base.ComputeCanGoBack();
}
Navigate(viewModel) / Overlay.Push(viewModel) 都会将实例所有权交给对应容器,页面出栈或容器销毁时由 Telepath Dispose。不要再让 DI 容器或其他所有者管理同一个页面实例。
推荐的 UI 节点树
Shell 是应用的稳定宿主;Content 是主页面的单槽目标,Overlay 是所有覆盖层的根节点。两者建议是全屏兄弟 Control,并让 Overlay 处于更高的绘制顺序。
ShellView : Control [TelepathView<ShellViewModel>]
├─ Content : Control %Content
│ └─ <当前 PageView> 由 ContentPresenter 动态放入
└─ Overlay : Control %Overlay
├─ Popup : Control 由 OverlayHostPresenter 动态创建
├─ Modal : Control
└─ Toast : Control
Content 和 Overlay 必须共享同一个 PresentedViews,才能让 Overlay 对当前页面正确播放覆盖动画、隔离焦点并在关闭后恢复焦点。
承载 SubViewportContainer 游戏内容
默认 ViewRegistry 把注册场景实例化为 Control。以 SubViewportContainer 承载 2D / 3D 世界时,可以把整个游戏会话作为一个 Gameplay View:
ShellView : Control
├─ Content : Control
│ └─ GameplayView : Control [TelepathView<GameplayViewModel>]
│ └─ AspectRatioContainer
│ └─ GameViewportContainer : SubViewportContainer
│ └─ GameViewport : SubViewport
│ └─ World : Node2D / Node3D
└─ Overlay : Control
├─ Pause
├─ Inventory
├─ Settings
└─ Toast
推荐的职责边界:
Conductor管理“是否处于游戏会话”,例如MainMenu → Gameplay → Result- Gameplay View 内部的世界宿主管理玩家、关卡和场景加载
- 暂停、背包、设置和确认框使用 Overlay,使 Gameplay View 继续存在
- 离开 Gameplay 页面会释放整棵 View 子树,包括
SubViewport内的世界节点;只有在结束或卸载游戏会话时才应该这样导航 - 如果需要返回 Gameplay 后恢复世界,将状态放在 ViewModel / GameSession / 存档快照中,不要只放在 Godot 节点字段中
Current Supported Bindings
| 控件 | 生成 | 方向 |
|---|---|---|
Label / RichTextLabel |
Bind(..., .Text()) |
单向 |
LineEdit / TextEdit |
Bind(..., .Text()) |
BindableReactiveProperty<string> 双向,否则单向 |
CheckBox / CheckButton |
Bind(..., .Toggle()) |
双向 bool |
其他 BaseButton |
BindCommand |
... |
Range(Slider / SpinBox / 进度条) |
Bind(..., .Value()) |
BindableReactiveProperty<double> 双向 |
ItemList |
BindItems(..., .Items()) |
集合绑定 |
OptionButton |
Bind(..., .Selected()) |
双向 long |
带 [TelepathView] 的 Control |
BindView(..., .View()) |
... |
普通 Control |
报错 | 必须显式指定绑定类型 |
Directory Structure
文件夹只用来分职责,不等于命名空间(库代码仍是 Telepath.Core / Telepath.Godot)。
src/Telepath.Core/ 平台无关
ViewModel/
Binding/ Attributes / Collection / Converters
Presentation/ Activation / Conductor / Overlay / Interaction / NestedView
src/Telepath.Godot/ Godot 层
View/ 资源寿命
Binding/ Attributes / Scene / Targets / Collection
Presentation/ Hosting / Conductor / Overlay / NestedView / Transition / Focus
Addon/ 完整插件树(排除出库编译)
Editor/ Binding Dock 与编辑器插件
R3/ 内嵌 R3 Godot 胶水
src/Telepath.SourceGenerator/ Roslyn 生成器
samples/Showcase/ 演示 Godot 工程(addons/Telepath → Addon/)
tests/ 单元测试
Showcase
如果你想深入了解,请使用 Godot 打开 samples/Showcase
包含详尽的示例与注释帮你理解这个框架
依赖注入
Telepath 不提供 DI 容器。
页面 ViewModel 由 Conductor / OverlayHost 持有:进栈接管、出栈 Dispose。若容器再管理同一实例,会双重释放或泄漏。Core 也不绑定某一套容器,宿主自己选。
主线 Showcase 用构造函数传入 INavigator / IOverlayHost / IInteraction,导航时手写 Navigate(new FooViewModel(...))。
若要按类型导航,实现 IViewModelActivator 并赋给 Conductor 与 Overlay 的 ViewModelActivator,即可 Navigate<T>() / Push<T>()。激活器只负责创建,所有权仍归 Telepath。
接入现成容器的示例(实验分支,不进主线):
| 分支 | 容器 |
|---|---|
experiment/showcase-msdi |
Microsoft.Extensions.DependencyInjection |
experiment/showcase-qframework |
QFramework |
Build & Test
dotnet build Telepath.sln
dotnet test Telepath.sln
License
MIT
See LICENSE
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net8.0
- ObservableCollections (>= 3.3.4)
- R3 (>= 1.3.1)
NuGet packages (1)
Showing the top 1 NuGet packages that depend on Telepath.Core:
| Package | Downloads |
|---|---|
|
Telepath.Godot
Godot integration layer for Telepath. |
GitHub repositories
This package is not used by any popular GitHub repositories.