简介
本文以你的第一个 2D 游戏 — Godot Engine (4.5) 简体中文文档为蓝本撰写,godot-rust提供了该教程的一个rust的实现dodge-the-creeps,与本文方法会稍有不同。如果你对rust、godot等内容比较生疏或感到疑惑,可以先阅读godot-rust入门文档。
本文搭建在godot-rust(gdext)创建项目的基础之上,如果你对该部分内容感到生疏,可以先阅读这部分内容,为接下来的操作做好准备。
注意,本文使用的godot版本为4.5.1,godot版本的变化可能会导致一些内容失效。
本文操作均在Windows上执行。
正文
现在,我们已经完成了游戏的所有功能。以下是一些剩余的步骤,为游戏加点“料”,改善游戏体验。 随意用你自己的想法扩展游戏玩法。
背景
向Main添加子节点ColorRect,在场景面板中,再用鼠标左键拖动到Main下的第一个节点,这样这个节点就会绘制在其他节点之后。
看到godot编辑器右边的检查器,在ColorRect下只有一个属性Color,选择你喜欢的颜色,原游戏的背景使用的是3a6d70,这也是本文选择的颜色。
再到工具栏的锚点预设中设置为整个矩形
如果你有背景图片,你也可以通过使用
TextureRect节点来添加它。
音效
声音和音乐可能是增强游戏吸引力的最有效方法。在游戏 art 文件夹中,有两个声音文件:“House in a Forest Loop.ogg”用于背景音乐,而“gameover.wav”用于当玩家失败时。
在Main节点下新增两个子节点AudioStreamPlayer,一个命名为Music,另一个为DeathSound。
通过检查器->AudioStreamPlayer->Stream来加载音频
Music:使用House in a Forest Loop.ogg文件DeathSound:使用gameover.wav文件
背景音乐Music需要循环播放,但这个时候你会发现检查器的AudioStreamPlayer里Loop选项是灰色的。点击Stream的向下箭头,点击唯一化,这时可以选择循环了。
我们需要在rust里可控制音乐的播放,回到rust的Main中:
fn game_over(&mut self) {
......
self.base().get_node_as::<AudioStreamPlayer>("Music").stop();
self.base()
.get_node_as::<AudioStreamPlayer>("DeathSound")
.play();
}
fn new_game(&mut self) {
......
self.base().get_node_as::<AudioStreamPlayer>("Music").play();
}
快捷键
快捷键可以提升游戏体验,比如便捷地开始游戏。
回忆一下我们添加键盘输入来控制Player移动,同样,我们添加一个输入start_game,映射键盘的Enter,来开始游戏,项目->项目设置->输入映射:
如果你有一个手柄,现在可以添加一个手柄支持。连接上你的手柄,然后在每一个你想添加手柄支持的输入动作下,点击 "+" 按钮然后按下该输入动作对应的按钮,方向键或者摇杆。
接下来切换到hud场景,选中StartButton节点,在检查器->BaseButton->Shortcut,新建一个Shortcut,点解你新建的Shortcut点击添加元素,添加一个InputEventAction,再点击InputEventAction打开次级面板,选择Action为你刚创建的start_game。你可以使用回车键开始游戏了:
记得保存godot编辑器里你所有的场景,将rust编译
cargo build
就这样,你在 Godot 中完成了你的第一个 2D 游戏。
分享游戏
如果你希望别人无需安装 Godot 就能试玩你的游戏,你需要将项目导出为目标操作系统可玩的格式。有关详细说明,请参见 导出项目。
导出项目后,将导出的可执行文件和 PCK 文件(不是原始项目文件)压缩为 ZIP 文件,然后将此 ZIP 文件上传到文件共享网站。
更多
细心的你会发现,当在调试完成的游戏时,你会发现godot提示了一些错误:
经过反复调整代码和测试,我们尝试找到了一些端倪。
在Hud中在MessageTimertimeout时会调用方法on_message_timer_timeout():
self.base()
.get_node_as::<Timer>("MessageTimer")
.signals()
.timeout()
.connect_other(self, Self::on_message_timer_timeout);
注意这个绑定将self传了进去,也就是hud自己。
回忆一下当游戏结束时Main中的方法:
fn game_over(&mut self) {
......
let mut hud = self.base().get_node_as::<Hud>("Hud");
spawn(async move {
hud.bind_mut().show_game_over().await;
});
......
}
这时hud执行了一次bind()用以调用show_game_over(),由于rust特性,hud实例将会锁定
进入到show_game_over()
pub async fn show_game_over(&mut self) {
self.show_message("Game Over");
self.base()
.get_node_as::<Timer>("MessageTimer")
.signals()
.timeout()
.to_future()
.await;
// 报错发生在此处
let mut message = self.base().get_node_as::<Label>("Message");
message.set_text("Dodge the Creeps!");
message.show(); // 本行甚至可以删除
self.base()
.get_tree()
.unwrap()
.create_timer(1.0)
.unwrap()
.signals()
.timeout()
.to_future()
.await;
self.base().get_node_as::<Button>("StartButton").show();
}
提示Game Over之后,我们等待MessageTimer走完,这时MessageTimer会自动调用前面提到的绑定的hud的on_message_timer_timeout()方法,但此时show_game_over()并未完成,也就是说hud.bind_mut().show_game_over().await这行代码并未释放bind_mut(),而MessageTimer在尝试调用hud。
但是报错并不影响我们游戏正确运行,因为紧接着我们立马又使用了message来显示"Dodge the Creeps!"提示,on_message_timer_timeout()是否被调用成功,即message是否被隐藏对此处逻辑无关痛痒,甚至可以利用此处报错逻辑,将message.show();此行代码删除。
我们并不知道按照原教程中GDScript对应的调用是否会报错,也可能是由于rust严格的借用规则造成了此处问题。严格意义上来说,此处报错意味着在godot-rust中使用future要更为谨慎。按照godot-rust中提供的demo演示,优化你的代码是更好的选择。
这里是本文的最终代码,如果需要运行请编译rust,注意本文使用的环境为win11。