Avalonia和Godot混合开发

007 使用 Estragonia 将 Avalonia 勉强嵌入 Godot 中

本文所用 Avalonia 和 Godot 版本
Avalonia 11.0.0

Godot 4.2

警告:这个东西现在并不成熟,还存在一些bug,比如一些控件无法及时绘制出来,比如对作为Avalonia类库的资源的使用等等。

0. 资料

  1. https://github.com/MrJul/Estragonia

1. 前言

居然能够在 Godot 里面嵌套 Avalonia,我本来两个都要研究,但是没有想到还有这种高手,在 Godot 里面套 Avalonia,太牛了。
项目来自 https://github.com/MrJul/Estragonia

2. 先有鸡还是现有蛋?

在开始项目之前,我们是先创建 Avalonia 还是先创建 Godot 是一个值得思考的问题。
我们看项目的 Readme 能够看到大概分为以下的步骤:

  1. 在 Godot 项目中使用 NuGet 包 JLeb.Estragonia
  2. 在 Avalonia 中的启动项配置中写上代码 UseGodot().SetupWithoutStarting()
  3. 在 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 配置

  1. 我们的宿主是 Godot,你总得有 Godot 吧,我们需要的是 Godot 4 的 .NET 版本。
  2. 创建或者打开一个现成的 Godot 项目,而且渲染必须是 Forward+ 或者是 Mobile 渲染器。
  3. 在 Godot 场景中添加一个 Control 节点,你可以简单去个名字比如叫做 UserInterface
  4. 在属性查看器的地方为新建的 Control 节点,在 Focus 分组的下面,将 Mode 设置为 All,如果说你没有这步的话,Godot 不会将键盘输入消息传到嵌套层的里面。
  5. 为 Control 节点附加上 C# 脚本,因为控件叫做 UserInterface,习惯上这个脚本也应该取名 UserInterface.cs

5. 建立 Avalonia 和 Godot 的桥梁

我们在做将 Avalonia 嵌入 Godot 的最后准备,而这个阶段将会非常冗长。

  1. 总之用你的 IDE 先打开 Godot 的工程。
  2. 在你的 Godot 工程中安装 JLeb.Estragonia 这个 NuGet 包。
  3. 在你的 Godot 工程中安装 Avalonia NuGet 主题包,这里比较偏好 official fluent theme。 如果没有主题的话,Avalonia 的内容将无法被看到。
  4. 打开 UserInterface.cs,将 UserInterface 这个类的继承从 Godot.Control 改为 JLeb.Estragonia.AvaloniaControl
  5. 继续在 UserInterface.cs 中编辑,删掉默认的 _Ready()_Process() 方法的覆写实现,这样 JLeb.Estragonia.AvaloniaControl 的实现就可以发挥出自己的作用了。
  6. 我们转移会一开始的 Avalonia 项目中,Estragonia 的工作流程中需要一个基于 Avalonia.Application 的类型(当然,派生类型也完全可以),创建 App 类型以及确保它是 FluentTheme 样式(这一步比较难懂,会做进一步图示,来介绍何为 创建 App 类型)。
  7. 使用 AppBuilder.Configure<App>().UseGodot().SetupWithoutStarting() 来初始化 Avalonia 项目。
    当我们在 Godot 中以这种方式调用 Avalonia 的时候,实际上这只在维护对应的这个 UserInterface Control,这一个对象,而且在使用前必须用这种方式初始化它。
    如果你希望你的 Avalonia 控件多开,我们比较推荐使用 Godot 的 autoload script,者在 C# 的层面去维护一个单例对象,去干在示例里面本来由 AvaloniaLoader 类负责的事(注意,这个类型类名是写死的,就只能叫做 AvaloniaLoader)。
  8. 创建一个 Avalonia 视图,比较推荐使用 UserControl 来创建,举个例子比如叫做 HelloWorldView.axaml
  9. 在你的新视图稍微写点东西,比如 <TextBlock Text="Welcome to Avalonia in Godot!" />
  10. 在 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 的混合开发还有很长的一段路要走,总之在现在,这个状况是仍然有不少要解决的严重问题的,现阶段不建议大家研究。

posted @ 2024-05-16 16:32  fanbal  阅读(728)  评论(0)    收藏  举报