Godot 游戏客户端 Godot Linux 的 C# 配置 MeteorCat 2026-01-26 2026-01-26 因为目前主要系统迁移到 Ubuntu/Debian 上, 基于这种情况就全面转移到 Linux 平台做相关游戏业务开发
这里主要是针对 Ubuntu 的 Godot C# 版本, 需要配置 dotnet 环境相关和 JetBrains Rider 的开发环境, 首先需要安装对应组件依赖:
这里官方文档都有脚本安装和 apt 安装配置; 注意不要安装 snap 版本, 这个版本会让 Rider 无法自动扫描到环境变量配置
dotnet 安装: https://learn.microsoft.com/zh-cn/dotnet/core/install
新版本 Rider 现在对于个人已经是免费提供, 所以直接去官网下载安装配置就行了; 只要你提前安装好 dotnet, Rider 就会自动扫描工具链
工具链配置在 Settings → Build,Execution,Deployment → Toolset And Build 内部, 确认 CLI 和 MSBuild 已经自动扫描到
之后就是 Godot 版本, 目前 Godot 官网默认下载不带 C#, 需要去对应页面选择下载
GodotC# Linux 下载: https://godotengine.org/download/linux
这里必须前置安装好 dotnet-sdk-8.0 以上, 剩下就是一些细节配置和功能相关, 启动 GodotC# 二进制首先配置好代码风格:
这里选择项目目录代码风格, 各自分割选项如下:
kebab-case: 中划线风格, 单词之间采用 “-” 划分, 比如 “godot-project”
snake_case: 下划线风格, 单词之间采用 “_” 划分, 比如 “godot_project”
camelCase: 小驼峰风格, 首字母必须采用小写, 之后单词采用大写划分, 如 “godotProject”
PascalCase: 大驼峰风格, 首字母和单词之间必须采用大写划分, 如 “GodotProject”
Title Case: 自然风格, 单词之间采用空格划分的自动声明, 如 “Godot Project”
这几种风格首先不推荐 Title Case, 因为目录空格处理可能因为某些情况不好区分几个空格导致错误.
剩下这几种对于默认版本 Godot 其实没什么差别, 但是如果采用 GodotC# 版本最好和 Unity3D 尽可能匹配, 所以采用 PascalCase 风格
C# 官方编码规范(Microsoft 推荐)明确以下代码规范:
这样方便后续切换到 Unity3D 等相关 C# 的平台
另外需要注意, 如果你采用官方的脚本安装 dotnet, 启动 GodotC# 会出现以下异常:
1 2 3 4 Unable to load .NET runtime, specifically hostfxr. Attempting to create/edit a project will lead to a crash. Please install the .NET SDK 8.0 or later from https://get.dot.net and restart Godot.
这代表没有扫描到系统 dotnet 的配置, Linux 下开发 Godot C#, dotnet 环境必须是系统全局可访问的
所以这部分开发最好采用 apt 安装方式来安装, 确保删除脚本安装的 dotnet 并执行以下命令:
1 2 3 4 5 6 7 8 9 10 11 12 13 # 首先删除掉内部脚本安装的 .dotnet 目录 # 之后删除 ~/.bashrc 当中的 DOTNET_ROOT 和 PATH=$PATH :$DOTNET_ROOT 配置 # 最后更新管理员环境变量 source ~/.bashrc # 最后采用 apt 安装配置 dotnet 环境, 目前官方 LTS 版本为 10.0 # hostfxr 是运行时宿主库 # sdk 是开发库 sudo apt install dotnet-hostfxr-10.0 dotnet-sdk-10.0 apt-transport-https ca-certificates # 系统安装完成之后查看具体版本信息 # 确保命令有输出内容 dotnet --info|grep DOTNET_ROOT
这样重新安装之后启动 GodotC# 就不会出现启动找不到 dotnet 的问题
IDE 配置
这里随便建立项目方便对开发环境做进一步配置, 如果是在比较新 Ubuntu 版本就会发现输入法冲突异常
因为最新版本主流 Linux 环境强推 Wayland, 而默认 Godot 启动的是 x11 桌面环境, 建议首选 Wayland 环境启动
在 编辑器配置(Editor Settings) → 运行(Run) → 平台(Platforms) 下勾选首选 Wayland 并重启:
这种就不会出现输出法卡死的问题, 之后就是启用默认开发工具为 Rider
在 编辑器设置(Editor Settings) → 勾选高级设置(Advanced Settings) → 找到 Dotnet → Editor 配置:
这样默认的启动 IDE 就是 JetBrains Rider, 现在可以开始 GodotC# 的具体开发
示例: 聊天室
这里采用官方的 WebSocket GDScript 例子来开发 C# 版本的简单聊天室功能, 这里简单布局如下:
如果创建之后发现界面元素错位, 需要检查是否 Container 节点元素的 Expand 已经启用
之后创建 ChatSession.cs 脚本,双击之后就会启动 Rider 来运行 GodotC# 项目代码, 这里简单挂载代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 using System;using System.Net.Sockets;using System.Text;using Godot;using Environment = System.Environment;namespace ChatRoom ;public partial class ChatSession : Control { private StreamPeerTcp _client; private bool _connected; #region 连接服务器信息 [Export ] public LineEdit ConnectHostnameNode { get ; set ; } [Export ] public SpinBox ConnectPortNode { get ; set ; } [Export ] public Button ConnectButtonNode { get ; set ; } #endregion #region 推送给消息功能 [Export ] public LineEdit SendMessageNode { get ; set ; } [Export ] public Button SendButtonNode { get ; set ; } #endregion #region 响应服务端消息 [Export ] public RichTextLabel ReceiveMessageNode { get ; set ; } #endregion public override void _Ready() { _client = new StreamPeerTcp(); if (ConnectButtonNode != null ) ConnectButtonNode.Pressed += OnConnectButtonPressed; if (SendButtonNode != null ) SendButtonNode.Pressed += OnSendButtonPressed; } private void OnConnectButtonPressed () { var hostname = ConnectHostnameNode.Text.Trim(); var port = (int )ConnectPortNode.Value; if (hostname.Length == 0 ) return ; GD.Print($"Connecting to {hostname} :{port} " ); try { ConnectToServer(hostname, port); SendButtonNode.Disabled = false ; } catch (Exception exception) { SendButtonNode.Disabled = true ; OS.Alert(exception.Message, "Connect Error" ); } } private void ConnectToServer (string hostname, int port, bool disableNagle = true ) { var err = _client.ConnectToHost(hostname, port); if (Error.Ok != err) { _connected = false ; throw new SocketException(Convert.ToInt32(err)); } _client.SetNoDelay(disableNagle); _connected = true ; } private void OnSendButtonPressed () { if (!_connected || _client.GetStatus() != StreamPeerTcp.Status.Connected) { GD.PrintErr("未连接到服务器, 无法发送消息" ); return ; } var message = SendMessageNode.Text.Trim(); if (message.Length == 0 ) return ; var data = Encoding.UTF8.GetBytes(message); var err = _client.PutData(data); if (Error.Ok != err) { GD.PrintErr($"发送失败: {message} " ); } else { GD.Print($"发送成功: {message} " ); if (ReceiveMessageNode != null ) { ReceiveMessageNode.Text += $"[REQUEST] : {message} {Environment.NewLine} " ; } } } public override void _Process(double delta) { if (!_connected) return ; _client.Poll(); var status = _client.GetStatus(); switch (status) { case StreamPeerTcp.Status.Connected: OnReceiveData(); break ; case StreamPeerTcp.Status.Error: case StreamPeerTcp.Status.None: OS.Alert("Connect Error" ); _connected = false ; SendButtonNode.Disabled = true ; break ; case StreamPeerTcp.Status.Connecting: break ; } } private void OnReceiveData () { var sz = _client.GetAvailableBytes(); if (sz <= 0 ) return ; var data = _client.GetData(sz); OnMessageReceived((byte [])data[1 ]); } private void OnMessageReceived (byte [] message ) { GD.Print($"Message received: {message} " ); if (ReceiveMessageNode != null ) { ReceiveMessageNode.Text += $"[RESPONSE] : {Encoding.UTF8.GetString(message)} {Environment.NewLine} " ; } } public override void _ExitTree() { if (_client != null ) { _client.DisconnectFromHost(); _client.Dispose(); } } }
需要处理下 RichTextLabel 节点, 因为之前没考虑到容器扩展问题, 会导致服务端输出内容没办法 “撑开高度”, 这里修改节点位置:
需要吐槽: GodotC# 内部功能返回的 Array 对象是没办法直接转化成 byte[], 只能依靠丑陋的 (byte[])data[1] 强制转化 byte[]
这里因为懒得编写 tcp-echo 服务器, 所以安装工具来简单启动 echo 服务, 输入以下命令行来安装并启动服务:
1 2 3 4 5 # 安装简单网络工具, 用于搭建 echo 服务端, 内置 nc 服务功能有欠缺没办法处理 sudo apt install ncat # 启动监听本地 8090 端口, 处理接收到的数据原样返回服务 ncat -l -p 8090 -k -c cat
最后的实现效果如下:
这里就简单实现基于 GodotC# 的 TCP 连接客户端服务, 其他后续比较深入内容最好查询官方文档来处理
Godot C# 文档: https://docs.godotengine.org/zh-cn/4.x/tutorials/scripting/c_sharp/index.html
需要说明: 目前官方 GodotC# 4.x 版本还不支持 Web 模板导出; 也就是编写 H5 游戏要么降级 Godot, 要么采用 GDScript
上面例子采用 Godot 的 TCP 相关功能, 但 GodotC# 当中更加推荐采用 dotnet 的 TCP/UDP
GDScript 底层的网络 PackedByteArray 的存在严重拷贝问题, 所有网络数据必须通过 Godot 的 Variant 系统且没办法提取 byte[]
后记说明
目前 C# 版本并不是 Godot 官方支持的 “一等公民(专门做优化的开发脚本语言)”, 所以官方内部接口支持很混乱导致问题都需要自己处理
这里的转换 byte[] 方式还只是小问题, 因为 Godot 底层采用 C++ 开发导致涉及到 C# 和 C++ 底层对接的时候可能说不定出现异常
官方目前的主要开发语言是 GDScript, 但是我个人很不喜欢 GDScript 来维护项目, 无论是语法严谨程度和社区第三方库支持差距都很大
GDScript 社区 Protobuf 支持太差了, 因为本身 Godot 内部 API 都是破坏性更新, 导致社区支持方案不同版本都会出现不同问题
所以真的很希望 Godot 后续将主要脚本语言重心转移到 C# 之中, 不止能够享受 dotnet 社区支持, 还能方便 Unity3D 平滑转入技术栈
按照目前来看估计还是遥遥无期, 甚至 GodotC# 目前的 Web 支持都还不稳定, 很多组件都需要迭代更新