使用Godot和CSharp开发桌面应用软件

原文链接

欢迎大家对于本站的访问 - AsterCasc

前言

本来我的想法是使用Kotlin Multiplatform来做为安卓端和桌面端应用软件跨平台的解决方案,参考本站前文构建跨平台的客户端界面。但是在撰写和尝试更完整的跨平台应用的时候发现,目前使用Kotlin Multiplatform社区组件选择以及部分组件的兼容性还有编辑器的显示支持上都不够优秀,简单来说遇到了不少的坑,虽然基本都能解决,但是实在不能作为一个具有长期借鉴意义的文章发出(比如可能会介绍蛮多坑,但是这些坑会在之后版本中被修复)。另外由于目前他们还在积极的更新和迭代中,我仍然觉得这是一个对于跨平台应用开发非常值得的选择,在之后他们各个功能都比较完善了,本站会出一个包含源码完整的跨平台应用的实现

想要写跨平台应用的原因也是想给astercasc.com出一个移动&桌面版本的应用程序,一方面看视频听音乐也比较方便(😄),另一方面能相对比较简单地观察到后端服务的状态。个人开发,不考虑其他人的维护性的话,肯定是跨平台的项目性价比更高的,而且也更具有学习价值和有趣。所以既然目前Kotlin Multiplatform不是特别好用的话,由于这个想法也没有特别着急,所以这个东西可以暂时搁置。肯定是不会考虑写两份代码的,比如写一套Kotlin Android写一套Qt,这种就没意义,纯纯在上班,也出不了文章

这个时候就可以考虑一些可能可以实现跨平台,但是即使没实现也有意思的东西了。于是我查了一下清单,发现之前有写过不少做游戏的点子,按照目前的我的时间,做游戏肯定是没空,但是游戏引擎对于跨平台的支持导出也是一种可以尝试的方案,即使最后效果不理想,也可以了解一下游戏开发

游戏开发引擎的选择,我们这里选择Godot,没有选择虚幻的原因是虚幻太大了,这个《大》更多的是对于游戏各种功能的支持,这里我们更希望有个轻量级的工具可以处理,不选择Unity的原因,大伙都知道,就属于懂的都懂了

Which platforms are supported by Godot?

For the editor:

  • Windows
  • macOS
  • Linux, *BSD
  • Android (experimental)
  • Web (experimental)

For exporting your games:

  • Windows
  • macOS
  • Linux, *BSD
  • Android
  • iOS
  • Web

CSharp环境构建

对于我们jetbrains全家桶来说,肯定是使用Rider进行开发的,下载Rider然后根据提示安装环境即可,目前的默认支持为:

.NET VersionRider VersionSupport
.NET SDK 8Rider 2023.3Full support
.NET SDK 7Rider 2022.3Full support
.NET SDK 6Rider 2021.3Full support
.NET SDK 5Rider 2020.3Full support

Godot配置

Godot官网下载所需版本,这里我们选择Godot Engine-.Net版本,然后解压,运行即可

这里有个小坑,Godot获取dotnet不是通过环境变量获取的,也就是说,我们在使用Rider安装运行环境之后,即使加入环境变量中,Godot也无法检测到,即使你的命令行可以调通dotnet --versionGodot获取dotnetWindows下是直接在Program Files里面拿的,所以我们需要将Rider下载的.dotnet复制成Program Files下的dotnet才能让Godot正常工作。当然你如果使用的是Visual Studio就不用这么麻烦了,直接在Visual Studio里面安装就可以直接使用

项目构建

对于Godot而言,虽然官网的介绍比较详细,但是我这里以前端对应来简单再介绍下。整个Godot项目为树状结构,整个树状结构为场景树The scene tree,对应了前端的html标签。在树上场景Scene就像是可复用的前端组件,级联选择器,分页表格等,游戏场景不执着于场景,可以是人物,装饰,武器等。而节点Node就像是我们的基础标签,buttoninput等。最后信号Signal就对应了前端信号,将事件传递给各个组件

这里就是Godot几个核心概念:场景树,场景,节点以及信号。官网有简单游戏的创建流程Your first 2D game,非常详细,我们这里就不再重复了,我们这里以网站首页举例,看看构建一个网站页面需要的工作量,下面的内容以您已完成官网的最佳实践为前提或者对Godot有初步了解

在项目设置中调整基础页面参数,比如初始窗口的宽高,主场景,自定义字体,如果需要使用快捷键也在这里配置,基本配置完成后,我们从基本组件开始,首先是卡片

由于我们这里并不是真的要用这个实现,做个简单的即可,调整后基本代码如下

[gd_scene load_steps=9 format=3 uid="uid://0sffbl8ylhnw"]

[ext_resource type="StyleBox" uid="uid://bawggbd2nu5ye" path="res://style/articlesimplebk.tres" id="1_s1tlr"]
[ext_resource type="StyleBox" uid="uid://bqeox8tc7ff3u" path="res://style/articleinbtn.tres" id="2_qhtdo"]
[ext_resource type="StyleBox" uid="uid://cao5ggt1fjedp" path="res://style/articleinbtn-hover.tres" id="3_4eymw"]

[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_fb4l4"]

[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_j0kbe"]
bg_color = Color(0.223529, 0.223529, 0.247059, 1)
corner_radius_top_left = 25
corner_radius_top_right = 25
corner_radius_bottom_right = 25
corner_radius_bottom_left = 25

[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_a8ibj"]
content_margin_left = 20.0
content_margin_right = 20.0

[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_rjk3e"]
content_margin_left = 20.0
content_margin_top = 60.0
content_margin_right = 20.0
content_margin_bottom = 20.0

[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_1c2hf"]
draw_center = false

[node name="CardOut" type="PanelContainer"]
custom_minimum_size = Vector2(450, 300)
offset_right = 450.0
offset_bottom = 300.0
theme_override_styles/panel = SubResource("StyleBoxEmpty_fb4l4")

[node name="Card" type="PanelContainer" parent="."]
layout_mode = 2
size_flags_vertical = 0
theme_override_styles/panel = ExtResource("1_s1tlr")

[node name="CardContainer" type="MarginContainer" parent="Card"]
custom_minimum_size = Vector2(0, 40)
layout_mode = 2
size_flags_horizontal = 0
size_flags_vertical = 0
theme_override_constants/margin_left = -25
theme_override_constants/margin_top = -25

[node name="CardBgContainer" type="PanelContainer" parent="Card/CardContainer"]
layout_mode = 2
theme_override_styles/panel = SubResource("StyleBoxFlat_j0kbe")

[node name="Title" type="Label" parent="Card/CardContainer/CardBgContainer"]
layout_mode = 2
theme_override_font_sizes/font_size = 25
theme_override_styles/normal = SubResource("StyleBoxEmpty_a8ibj")
text = "this is  a article title"

[node name="CardContentContainer" type="VBoxContainer" parent="Card"]
layout_mode = 2

[node name="CardContent" type="Label" parent="Card/CardContentContainer"]
custom_minimum_size = Vector2(100, 100)
layout_mode = 2
size_flags_vertical = 0
theme_override_colors/font_color = Color(0, 0, 0, 1)
theme_override_font_sizes/font_size = 20
theme_override_styles/normal = SubResource("StyleBoxEmpty_rjk3e")
text = "this is content this is content this is content this is content this is content
this is contentthis is contentthis is content"
autowrap_mode = 3

[node name="Go" type="Button" parent="Card/CardContentContainer"]
layout_mode = 2
size_flags_horizontal = 8
size_flags_vertical = 8
theme_override_colors/font_color = Color(0.972549, 0.972549, 0.972549, 1)
theme_override_font_sizes/font_size = 20
theme_override_styles/normal = ExtResource("2_qhtdo")
theme_override_styles/hover = ExtResource("3_4eymw")
theme_override_styles/pressed = ExtResource("3_4eymw")
theme_override_styles/focus = SubResource("StyleBoxFlat_1c2hf")
text = "更多内容"

这里我们在创建的时候为了复用使用自定义的tres,可以认为是css文件,可以直接导入需要的标签复用,以articlesimplebk.tres为例:

[gd_resource type="StyleBoxFlat" format=3 uid="uid://bawggbd2nu5ye"]

[resource]
content_margin_left = 0.0
content_margin_top = 0.0
content_margin_right = 40.0
content_margin_bottom = 35.0
bg_color = Color(1, 1, 1, 0.784314)
corner_radius_top_left = 25
corner_radius_top_right = 25
corner_radius_bottom_right = 25
corner_radius_bottom_left = 25
shadow_color = Color(1, 1, 1, 0.0784314)
shadow_size = 20

这样一个我们一个基础组件(场景)就制作好了

现在我们写个主场景树:

[gd_scene load_steps=7 format=3 uid="uid://cr14hf5hn4tp5"]

[ext_resource type="Texture2D" uid="uid://bqe782vjeix25" path="res://img/backgroud.png" id="1_51gst"]
[ext_resource type="Script" path="res://YunoDesktop.cs" id="1_ogbat"]
[ext_resource type="StyleBox" uid="uid://cfh1uqw6lpksw" path="res://style/transbk.tres" id="2_gsi10"]
[ext_resource type="Theme" uid="uid://c8f4q4asmagvt" path="res://style/titlelabel.tres" id="3_6cb30"]
[ext_resource type="Script" path="res://HomeProject.cs" id="3_gbua5"]

[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_gnpks"]
bg_color = Color(1, 1, 1, 1)

[node name="YunoDesktop" type="Node"]
script = ExtResource("1_ogbat")

[node name="HomeScroll" type="ScrollContainer" parent="."]
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2

[node name="HomeContainer" type="PanelContainer" parent="HomeScroll"]
layout_mode = 2
size_flags_horizontal = 3
theme_override_styles/panel = SubResource("StyleBoxFlat_gnpks")

[node name="Home" type="VBoxContainer" parent="HomeScroll/HomeContainer"]
layout_mode = 2
size_flags_horizontal = 3
theme_override_constants/separation = 0

[node name="HomeBkColor" type="ColorRect" parent="HomeScroll/HomeContainer/Home"]
layout_mode = 2

[node name="HomeBkImg" type="TextureRect" parent="HomeScroll/HomeContainer/Home"]
layout_mode = 2
texture = ExtResource("1_51gst")
expand_mode = 5

[node name="HomeMain" type="MarginContainer" parent="HomeScroll/HomeContainer/Home"]
layout_mode = 2
theme_override_constants/margin_left = 50
theme_override_constants/margin_top = -200
theme_override_constants/margin_right = 50
theme_override_constants/margin_bottom = 100

[node name="HomeStyle" type="PanelContainer" parent="HomeScroll/HomeContainer/Home/HomeMain"]
layout_mode = 2
theme_override_styles/panel = ExtResource("2_gsi10")

[node name="HomeProject" type="VBoxContainer" parent="HomeScroll/HomeContainer/Home/HomeMain/HomeStyle"]
layout_mode = 2
script = ExtResource("3_gbua5")

[node name="HomeArticle" type="Label" parent="HomeScroll/HomeContainer/Home/HomeMain/HomeStyle/HomeProject"]
layout_mode = 2
theme = ExtResource("3_6cb30")
text = "技术备录"

[node name="HomeArticleContainer" type="HFlowContainer" parent="HomeScroll/HomeContainer/Home/HomeMain/HomeStyle/HomeProject"]
layout_mode = 2
theme_override_constants/h_separation = 100
theme_override_constants/v_separation = 100

[node name="HomeLife" type="Label" parent="HomeScroll/HomeContainer/Home/HomeMain/HomeStyle/HomeProject"]
layout_mode = 2
theme = ExtResource("3_6cb30")
text = "生活题记"

[node name="HomeLifeContainer" type="HFlowContainer" parent="HomeScroll/HomeContainer/Home/HomeMain/HomeStyle/HomeProject"]
layout_mode = 2
theme_override_constants/h_separation = 100
theme_override_constants/v_separation = 100

[node name="ArticleRequest" type="HTTPRequest" parent="HomeScroll/HomeContainer/Home/HomeMain/HomeStyle/HomeProject"]

[node name="LifeRequest" type="HTTPRequest" parent="HomeScroll/HomeContainer/Home/HomeMain/HomeStyle/HomeProject"]

我们需要根据后端请求更新在该场景树上的卡片场景:

using Godot;
using System;
using System.Text;
using System.Text.Encodings.Web;

public partial class HomeProject : VBoxContainer
{
	public override void _Ready()
	{
		HttpRequest articleRequest = GetNode<HttpRequest>("ArticleRequest");
		articleRequest.RequestCompleted += OnArticleReqCompleted;
		articleRequest.Request(ServerInfo.ServerBase.KotomiAddress + "/article/list?articleType=1&offset=0&limit=10");

		HttpRequest lifeRequest = GetNode<HttpRequest>("LifeRequest");
		lifeRequest.RequestCompleted += OnLiftReqCompleted;
		lifeRequest.Request(ServerInfo.ServerBase.KotomiAddress + "/article/list?articleType=2&offset=0&limit=10");
	}

	private void OnArticleReqCompleted(long result, long responseCode, string[] headers, byte[] body)
	{
		rentCard(body, "HomeArticleContainer");
	}

	private void OnLiftReqCompleted(long result, long responseCode, string[] headers, byte[] body)
	{
		rentCard(body, "HomeLifeContainer");
	}

	private void rentCard(byte[] body, string containerName)
	{
		Godot.Collections.Dictionary json = Json.ParseString(Encoding.UTF8.GetString(body)).AsGodotDictionary();
		var articleList = json["data"].AsGodotArray();
		if (articleList.Count > 0)
		{
			var articleContainer = GetNode<HFlowContainer>(containerName);
			var scene = GD.Load<PackedScene>("res://ArticleSimple.tscn");
			for (int count = 0; count < articleList.Count; count++)
			{
				var objDic = articleList[count].AsGodotDictionary();
				var instance = scene.Instantiate();
				var thisLabel = instance.GetNode<Label>("Card/CardContainer/CardBgContainer/Title");
				thisLabel.Text = objDic["articleTitle"].AsString();
				var thisContent = instance.GetNode<Label>("Card/CardContentContainer/CardContent");
				thisContent.Text = objDic["articleBrief"].AsString().Replace("\n", "") + "...\n";
				var thisBtn = instance.GetNode<Button>("Card/CardContentContainer/Go");
				thisBtn.Pressed += () => onPressToReadMore(objDic["id"].AsString());
				articleContainer.AddChild(instance);
			}
		}
	}

	private void onPressToReadMore(string articleId)
	{
		GD.Print(articleId);
		OS.Alert("不支持当前功能,请移步astercasc.com体验完整功能");
	}
}

最后我们运行主场景可以得到:

项目发布

这里我们可以选择发安卓端以及桌面端,桌面端基本直接导出就可以了,安卓端需要填写密钥库,创建后即可发布。经过测试,桌面端基本没有问题,安卓端部分功能在转换之后还是有点问题,但是如果是做游戏应该不影响,因为做游戏一般也用不到侧边栏滑动

最后

全部代码可以在yuno-door-godot找到。可能是我不是很熟练的原因,用godot来模拟web端逻辑上没有什么问题,但有两个痛点,第一个是很多web以及android比较方便就可以实现的功能,由于游戏里面不存在,在转换脚本里面没有处理(比如侧边栏滑动)需要单独处理。第二个是写样式太痛苦了,同样的样式可能web或者android只需要一行,在godot需要迭代两到三层

参考

godot

godotgithub

chickensoft

原文链接

欢迎大家对于本站的访问 - AsterCasc

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值