Avalonia和Godot混合开发
007 使用 Estragonia 将 Avalonia 勉强嵌入 Godot 中
本文所用 Avalonia 和 Godot 版本
Avalonia 11.0.0Godot 4.2
警告:这个东西现在并不成熟,还存在一些bug,比如一些控件无法及时绘制出来,比如对作为Avalonia类库的资源的使用等等。
0. 资料
1. 前言
居然能够在 Godot 里面嵌套 Avalonia,我本来两个都要研究,但是没有想到还有这种高手,在 Godot 里面套 Avalonia,太牛了。
项目来自 https://github.com/MrJul/Estragonia 。
2. 先有鸡还是现有蛋?
在开始项目之前,我们是先创建 Avalonia 还是先创建 Godot 是一个值得思考的问题。
我们看项目的 Readme 能够看到大概分为以下的步骤:
- 在 Godot 项目中使用 NuGet 包
JLeb.Estragonia; - 在 Avalonia 中的启动项配置中写上代码
UseGodot().SetupWithoutStarting(); - 在 Godot 中在你的场景中添加一个
Control节点来承载 Avalonia 内容,这个Control将会继承自JLeb.Estragonia.AvaloniaControl;
文档中也提供了更详细的步骤,一共有小二十步呢,文档来自此处:https://github.com/MrJul/Estragonia/blob/main/docs/setup.md
我们也来挨个翻译。
3. 看看示例的结构
你可以先看看一个简单的 Avalonia 嵌入 Godot 的示例 https://github.com/MrJul/Estragonia/tree/main/samples/HelloWorld。
这个项目有一些额外的 UI 元素和对应的 xaml 文件,它们现在还和 Godot 与 Estragonia 都没什么关系,我们将会在稍后的一节专门介绍如何将 Avalonia 进行针对 Godot 的初始化。
当然,你的项目也可以简单一些。我这里选择直接开一个 Avalonia 项目,不做任何的改动。
4. Godot 配置
- 我们的宿主是 Godot,你总得有 Godot 吧,我们需要的是 Godot 4 的 .NET 版本。
- 创建或者打开一个现成的 Godot 项目,而且渲染必须是 Forward+ 或者是 Mobile 渲染器。
- 在 Godot 场景中添加一个 Control 节点,你可以简单去个名字比如叫做
UserInterface。 - 在属性查看器的地方为新建的 Control 节点,在 Focus 分组的下面,将 Mode 设置为 All,如果说你没有这步的话,Godot 不会将键盘输入消息传到嵌套层的里面。
- 为 Control 节点附加上 C# 脚本,因为控件叫做
UserInterface,习惯上这个脚本也应该取名UserInterface.cs。
5. 建立 Avalonia 和 Godot 的桥梁
我们在做将 Avalonia 嵌入 Godot 的最后准备,而这个阶段将会非常冗长。
- 总之用你的 IDE 先打开 Godot 的工程。
- 在你的 Godot 工程中安装 JLeb.Estragonia 这个 NuGet 包。
- 在你的 Godot 工程中安装 Avalonia NuGet 主题包,这里比较偏好 official fluent theme。 如果没有主题的话,Avalonia 的内容将无法被看到。
- 打开
UserInterface.cs,将 UserInterface 这个类的继承从Godot.Control改为JLeb.Estragonia.AvaloniaControl。 - 继续在
UserInterface.cs中编辑,删掉默认的_Ready()和_Process()方法的覆写实现,这样JLeb.Estragonia.AvaloniaControl的实现就可以发挥出自己的作用了。 - 我们转移会一开始的 Avalonia 项目中,Estragonia 的工作流程中需要一个基于
Avalonia.Application的类型(当然,派生类型也完全可以),创建App类型以及确保它是FluentTheme样式(这一步比较难懂,会做进一步图示,来介绍何为 创建App类型)。 - 使用
AppBuilder.Configure<App>().UseGodot().SetupWithoutStarting()来初始化 Avalonia 项目。
当我们在 Godot 中以这种方式调用 Avalonia 的时候,实际上这只在维护对应的这个 UserInterface Control,这一个对象,而且在使用前必须用这种方式初始化它。
如果你希望你的 Avalonia 控件多开,我们比较推荐使用 Godot 的 autoload script,者在 C# 的层面去维护一个单例对象,去干在示例里面本来由AvaloniaLoader类负责的事(注意,这个类型类名是写死的,就只能叫做 AvaloniaLoader)。 - 创建一个 Avalonia 视图,比较推荐使用 UserControl 来创建,举个例子比如叫做
HelloWorldView.axaml。 - 在你的新视图稍微写点东西,比如
<TextBlock Text="Welcome to Avalonia in Godot!" /> - 在 Godot 的
UserInterface.cs的地方,在_Ready()的地方加上一句Control = new HelloWorldView(),别忘了后面也得写base._Ready()。
6. 本人的具体实操
上面多少是我的粗暴翻译,有些未必能翻译的对,为此自己要来实际操作一遍才好。
1. 新建 Avalonia
我的选择是先开一个 Avalonia 项目。
然后就让他放在这里了,我也不去动它。默认情况下就是一个 Hello Avalonia 的正文内容嘛,我懂。

2. 新建 Godot
总之建立了一个 Godot 项目,在其中加入了一个 Control 类型的节点然后取名为 UserInterface。

并且设置了焦点模式是 All。

然后添加了一个脚本名为 UserInterface.cs。


3. 在 Godot 中安装 JLeb.Estragonia 和 Avalonia 主题
在 Godot 项目中打开 NuGet,安装 JLeb.Estragonia

我发现我的 Avalonia 项目其实是有主题库的,我选择暂时不装。
4. 将 Avalonia 项目引用到 Godot 项目中
我发现我默认创建出来的 Godot 是 .NET 6.0 而 Avalonia 的版本是 .NET 7.0 我将 Godot 的项目进行升级。
你可以将你的 Godot 项目的 csproj 的这行
<TargetFramework>net6.0</TargetFramework>
改成
<TargetFramework>net7.0</TargetFramework>
简单而言就像这样,将你的 Avalonia 核心的那个库引用到 Godot 项目中。

5. 修改 UserInterface 类的继承,并调整部分默认代码
修改 UserInterface 脚本中对应类的继承,并且写成差不多这个样子。
public partial class UserInterface : JLeb.Estragonia.AvaloniaControl
{
// Called when the node enters the scene tree for the first time.
public override void _Ready()
{
base._Ready();
}
}
6. App 相关?
接下来要处理一下 App 相关的内容了。
文中所谓 App 我在我自己的 Avalonia 项目中也有看到,似乎不用自己创建,但是 App.axaml.cs 的内部实现比示例代码要多很多,我将它们先注释掉看看情况。
using Avalonia;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Data.Core.Plugins;
using Avalonia.Markup.Xaml;
using AvaTest.ViewModels;
using AvaTest.Views;
namespace AvaTest;
public partial class App : Application
{
public override void Initialize()
{
AvaloniaXamlLoader.Load(this);
}
/*
public override void OnFrameworkInitializationCompleted()
{
// Line below is needed to remove Avalonia data validation.
// Without this line you will get duplicate validations from both Avalonia and CT
BindingPlugins.DataValidators.RemoveAt(0);
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
{
desktop.MainWindow = new MainWindow
{
DataContext = new MainViewModel()
};
}
else if (ApplicationLifetime is ISingleViewApplicationLifetime singleViewPlatform)
{
singleViewPlatform.MainView = new MainView
{
DataContext = new MainViewModel()
};
}
base.OnFrameworkInitializationCompleted();
}
*/
}
7. 开始 Avalonia 在 Godot 的初始化吧
继续补充脚本之后,它会是这个样子,我们的 Avalonia 里面自然有带一个 MainView.axaml,为此我们来使用它。
public partial class UserInterface : JLeb.Estragonia.AvaloniaControl
{
// Called when the node enters the scene tree for the first time.
public override void _Ready()
{
AppBuilder.Configure<App>().UseGodot().SetupWithoutStarting();
Control = new AvaTest.Views.MainView();
base._Ready();
}
}
8. 似乎大功告成了?跑一跑?
我进行了编译和运行,但是一片空白

我尝试编译了示例项目,发现一件很严肃的事情,AvaloniaLoader 这个东西虽然没有任何的引用,在 Godot 工程中也没有作为节点出现,但是这是极为重要的一个类。
为此我也向示例一样尝试加入它,但是未果。
原来是 AvaloniaLoader 被 AutoLoad 了,你可以在这里发现它。

为此我也将我的 AvaloniaLoader 加入到我的 AutoLoad 里面。
但是还是没有结果。
难道就是无法获得来自类库的 View 吗?我这样好奇。
于是我自己在 Godot 项目里面加了一个 UserControl
<UserControl x:Class="GodotTest.Views.TestUserControl"
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
d:DesignHeight="450"
d:DesignWidth="800"
mc:Ignorable="d">
Welcome to Avalonia!
</UserControl>
并且在 UserInterface 的地方使用它
public partial class UserInterface : JLeb.Estragonia.AvaloniaControl
{
// Called when the node enters the scene tree for the first time.
public override void _Ready()
{
Control = new GodotTest.Views.TestUserControl();
base._Ready();
}
}
然后我尝试跑它。
发现仍然是一片空白……

直到我无意点了一下空白处。

出现了!
但是为啥我需要重新点一下?
我是不是可以做一个延迟手动刷新的代码?
但是作罢了。
我在想,好像示例代码里面没有这么复杂啊。我对比一下我缺失了什么。
我将示例代码放到了我那里,发现除了上面有动画的这部分能出现,其他都需要稍微交互才行。
交互前:

交互后:

7. 令人失望的结论
看起来 Avalonia 和 Godot 的混合开发还有很长的一段路要走,总之在现在,这个状况是仍然有不少要解决的严重问题的,现阶段不建议大家研究。

浙公网安备 33010602011771号