使用 Godot 进行简单的横板平台游戏开发
游戏引擎有很多,适用于独立开发者进行 2d 游戏开发的也不少。Godot Engine 是一个关注已久的游戏引擎项目,有着非常宽松的授权许可(MIT)和看上去还不错的 2d 支持。一直以来想在有机会时做点东西出来,但又感觉真的想做时任何引擎都不熟悉也不是件好事,于是就打算试着使用 Godot 来开发一个简单的横板平台游戏原型来熟悉这个引擎。恰巧也有其它群友对 Godot 感兴趣,于是尽管我的了解程度还不多,也仍打算记录下一些研究过程和结果,以备后续自己查阅和它人参考。
下述的内容此处也附带了代码仓库链接,这里建议自己拉去仓库并打开,把玩之后再按照你在 Godot 中所看到的项目结构和写法来对照下方的文章,于是我也可以省点截图(
既然要使用引擎,那么自然希望引擎能够附带一些常用的功能而不再需要玩家自行实现或寻找解决方案。对于这次尝试的需求而言,Godot 有下面这些特性可用:
- 对 2d 像素游戏相对友好,支持直接的 pixel prefect 相机设置和资源导入。
- 内置 TileMap 以及 Autotile 支持。
- 相对很友好的 vcs 支持。
剩下的自然是一些几乎所有引擎都一定会提供的常用功能了。当然由于这篇文章并不是“教程”性质的文章,所以并不打算把文章提到的内容都详细讲清楚,如果你未曾用过 Godot,这里可以给你一些概览,具体的内容自然还是建议你优先考虑查阅文档。
Godot Engine 使用树形结构来描述场景内对象的层级关系,可以在 Scene 面板查看当前场景内的对象层级关系(场景树),选中其中的节点后也可以在 Inspector 和 Node 面板查看其对象属性和信号与组关系。在必要时,可以将一组节点作为一个新的场景保存,并在其它场景中实例化自己所保存的场景以便复用。
每个场景对象可以对应一个 tscn 文件,记录场景的结构和一些未单独保存的场景文件中的子节点数据。一个 Godot 工程对应一个 godot 扩展名的工程文件,记录了项目的各种参数配置,键位配置,默认场景等信息。由于 tscn 文件、godot 文件等涉及到的引擎相关大都是文本文件,于是相应的对 vcs 也很友好。
每个 Godot 项目对应一个目录(godot 文件所在的目录)作为项目目录,当把所支持的资源文件放置到项目目录下时,资源会被自动导入到项目中以供使用。已经导入的资源即可在 FileSystem 面板看到,并在代码中通过 res:// 协议的 url 进行访问,在 .import 目录也会有对应的数据生成(不需要 check-in 到 vcs 中)。导入会使用默认配置,当默认配置不符合需求时,则可以在文件视图中选取对应的文件,并在 Import 面板中调整设置并进行重新导入。
![]()
我们的目标游戏原型是像素游戏,默认的图片素材导入选项并不适合,故对于我们的像素游戏素材,则需要进行这样的操作,在把素材放进去后,选中并直接使用 2D Pixel 预设重新导入即可。
我们使用 KinematicBody2D 节点来表示玩家对象,此节点下放置了 Sprite 节点来放置 Sprite,以及 CollitionShape2D 节点标记碰撞盒,然后将这个 KinematicBody2D 玩家节点保存为独立的场景,以便后续导入关卡场景使用。我们的关卡场景则是 Node2D 下只放置了一个 TileMap 节点,然后链接的形式把单独存储的玩家节点也实例化在了测试关卡场景中。TileMap 这里随便用 GIMP 瞎画了一个,然后使用 TileMap 的 TileSet 功能选择我们的文件并配置 Autotile,设置恰当的 Collition 和 BitMask,完毕之后就可以直接在 Godot 内绘制关卡的结构原型了。

Godot 使用类似 python 风格的 GDScript 脚本语言来使开发人员编程控制游戏逻辑(也可选择 Godot VSL 或 C#)。对于每个节点,我们都可以在 Inspector 面板进行选择来对其附加脚本。对于直接附加在节点上的脚本,可以认为是继承了对应节点类型的对象的脚本,例如附加在 KinematicBody2D 对象上的脚本即扩展(extends)了 KinematicBody2D。当然,在需要时,我们也可以在代码中通过 preload() 函数来载入我们自己的脚本。
Godot 引擎的执行逻辑也和场景树相关,详细的执行顺序逻辑可以在 Node 文档中看到,下面是我们所关心的最常见的几个函数:
_process(delta): 每个帧调用,与 fps 相关。_physics_process(delta): 每个物理帧调用。_ready(): 游戏实例化场景树时,节点首次载入场景树中被调用。
熟悉其它引擎或者自己写过类似的东西的话,自己猜也猜得到这些都是干嘛的了…当然实际我们这次只用到了 _physics_process()。
这次打算实现的东西也非常基础,只有这些:
- Variable Jump:按住以跳的更高。
- Coyote Time:在玩家移动掉平台的一小段时间内按跳跃键仍然允许跳跃。
- Buffer Jump:未落地但即将落地时按下跳跃,在着地瞬间起跳。
…然而并不打算在这篇文章中讲这些基础操作应该怎样实现,如果你实在不知道,请参考项目的 Player.gd 文件。
对于这些功能,实际我们只要在逻辑理清楚的情况下,按照 Godot Script 的语法编写即可。其中一些玩家状态的检测我们自然使用了引擎所提供的功能,例如玩家是否在“地面”上,我们即直接使用了 is_on_floor() 实际行为则只需要我们之前正确配置了恰当的 Collition 就可以了。对于处理输入,我们使用 Input.is_action_pressed(name), Input.is_action_just_released() 和 Input.is_action_just_released(name) 等获取按键的按下状态。当然,对于手柄摇杆等输入方式存在轻推等按下程度相关的情况,我们用 Input.get_action_strength(name) 即可。我们这次的实际场景即跳跃键使用前者,方向输入使用后者。最后我们知道可以用 move_and_slide() 移动 KinematicBody2D 玩家,就可以实现基本的玩家操作逻辑了。
当然,我们显然也会有在代码中访问当前节点之外的节点的需求,比如根据方向改变玩家 Sprite 的朝向等。我们即可使用 $ 来访问场景树中的其它节点,例如可使用 $Sprite 访问相对此节点而言,此节点下的 Sprite 节点,也可使用 $"/root/World/TileMap" 来访问根节点下,/World/TileMap 路径所对应的节点。这种需求通常也伴随着需要我们所用来对应节点的变量在当前节点载入时即可用,我们即可以使用 onready 关键字标记变量何时就绪。
哦对了,最基础的一个东西,print() 是你要找的用来打 log 的函数。
以上就是基本的概览了,上面大致涵盖了基础的功能介绍和一些最常见的 GDScript 相关内容,如果你更习惯看一个不复杂的项目的文件并配合一些解释性的文字来熟悉这个引擎的工作流程来作为快速上手的方式的话,我应该提到了我觉得比较重要的大部分内容了。
这篇文章涵盖的内容自然也很少,就横板平台游戏而言,这里也有大量的很基础的内容没有涵盖,例如相机,Sprite 动画,例子效果,场景切换,UI 等内容,如果你感兴趣,可以考虑把玩一下,并在有兴趣和精力的情况也分享给其它人。尽管我非常清楚这篇文章压根没特别清楚的讲述任何地方的细节,但如果你对于此文章所涉及内容有任何疑问,也可考虑在 讨论版 中发帖讨论。
我其实目前有两次使用 Godot 的尝试了,另一次是在 这篇文章中 提到过的临时用的 mapeditor。委实说,两次而言 Godot 所给我的体验是还算可以但不是特别出彩。就此例而言,Autotile 的配置编辑过程相比很繁琐,编辑碰撞区域是非常无聊的重复工作,对于所设 bitmask 未存在对应的 tile 时,绘制关卡时选取的填补块也是完全存粹的随机块。另一次体验则发现 GridMap 组件对于稍复杂的需求即不太能应付的过来,glTF 的支持程度也比较一般。不过 Godot 发展的时间并不长,加之非常宽松的许可协议以及开放源代码,以上问题对于确实想解决的情况下,即便官方不处理,也是可以自己上手改的。个人也留意了一段时间 Godot 社区的发展情况,目前看来也比较健康,于是相信目前存在的一些体验问题可以在后续的演进过程中解决吧。
最后祝大家,身体健康,再见。
以上内容可以在遵循 CC BY-SA 4.0 共享协议的条件下随意使用。上述源码仓库内的源码不包含在此协议范围内,在对应源码仓库明确给出许可前,请遵循您所在地的法规限定的合理使用范畴下利用。