从Unity到Godot
前言
本文主要记录了本人刚刚从Unity开发转到Godot开发以来的一些经验和教训。
用了近两年的Unity,最近决定转Godot,原因除了广为人知的Unity收费风波还有以后发展方向的原因。无论如何个人认为Godot跟Unity的相似程度还是挺高的,很多内容都能快速适应,当然也可能是单单游戏引擎都有的共性和功能。
虽然但是遇到的坑还是不少的。而且也看到了Godot和Unity设计理念上的许多异同。
其实我也是看着Godot的官方文档进行学习的,最重要最全面的东西其实都在文档里了。
手感
目前而言,Godot的体验还是非常不错的,至少比Unity好,也可能是因为学过Unity的原因吧。Godot比Unity更轻量,所以写起来也更清凉一点。
我觉得很大原因会归功于Godot的设计理念,文档中自己说了不鼓励编程架构模式:
“我们建议在使用 Godot 制作游戏时忽略架构代码模式,例如模型-视图-控制器 (MVC) 或
实体关系图。相反,你可以从想象玩家将在游戏中看到的元素开始,并围绕它们构建代码。”
也就是鼓励我们从自然的游戏设计角度出发,去构建我们的游戏。在设计时先不考虑代码方面的架构。大概就是这个意思吧。
还有话说Godot的很多理念跟C#不谋而合,比如信号体现的观察者模式,和C#可以轻松融入,直接当作C#事件处理了。有个缺点,一定要记得Build!一定要记得Build!一定要记得Build!
(指Build C# 代码,不Build的话就等着被Godot偷偷删东西吧)。
所以可能写起来会舒服很多吧,以前用惯Unity了,过多考虑了框架,架构什么的,就会感叹自己到底是在做游戏还是写代码。所以开发道路上切忌舍本逐末,游戏是目的,代码是过程。
当然我并没有鼓励完全放弃设计模式哈,很多游戏设计模式是无关引擎,无关语言的。都应当予以重视。
另提一嘴,Godot中组件(就是那些节点)的易用性真的比Unity好了不止一点半点(上限应该不如),最简单的说,我在使用一个Unity内置组件的时候,有很多需求是需要去阅读文档才能知道怎么实现的,但是在Godot中,它们要么一览无余的暴露在Inspector中,要么在信号窗口中,知道点开一看就知道有什么用,鼠标悬停后还会弹出很详细的提示。
当然也可能是因为Unity比较重量级一点才不得不把很多内容扔到文档中。无论如何开发效率和舒适度上来看,个人认为Godot肯定更舒服。非要说什么上限的话,我目前还没有游戏能离谱完全榨干一个引擎的“上限”。
还有最后一点,我非常喜欢Godot的文件夹管理,比Unity的不知道什么就多出来一个固定名称的文件夹好多了。(现在Godot4.3能把文件管理窗口放在屏幕正下方了哦,显得比较高级)
物理检测
对Godot物理检测部分进行试探的时候,出现了怎么设置都没有触发的情况:用一个RigidBody2D去碰一个Area2D,发现怎么接触都没用,什么碰撞设置,碰撞掩码之类都确认过了。后来才发现是因为节点排列顺序的问题,我把Sprite2D当作RigidBody2D的父节点,最主要的问题是我把脚本写在了Sprite2D上,所以我一直移动的其实是那个图像。
而且还自信的以为整个物体已经被移动了,结果RigidBody2D一直在往下掉(因为重力)。所以死活动不了。然后把RigidBody2D作为整个物体的父节点就修改好了。从这里也可以看出,Godot开发中的节点排列顺序是很重要的。
最好设计场景,设计"Prefab"时要策划一下它们所需要的节点应该怎么分布合理。其实这个问题可能跟Unity的操作习惯有关,在Unity中都是很多脚本塞到一个物体上的,鲜有考虑它们之间的顺序问题。但是我们的Godot每个节点只能挂一个脚本,这样就不得不规划好职能了。
关于物理检测,还有一个和Unity一样的,或者说游戏引擎都需要注意的情况,就是单帧位移,在单个帧内直接改变物体位置坐标。这个问题就是不得不考虑物理引擎能否检测到物体的位置变化,比如上个例子中的Area2D有个BodyEntered信号,这玩意只检测边缘的接触,换句话说当物体瞬间进入Area中,是不会被它检测到的,信号无法触发,但是控制RigidBody2D移动到Area中就可以被检测到。
所以目前我猜测Godot现在的物理引擎应该挺鸡肋的。
关于节点
节点应该是Godot世界里最最最重要的东西了,它直接或间接参与了构成整个世界。我们在Godot里所指的什么场景,什么预制体其实都是一堆节点。Unity中相当于把这些概念细分,特化职能,场景就是场景,预制体就是预制体。所以用Godot开发总会有一种无拘无束的感觉。
节点通过父子关系约束构成节点树,往大点说就变成了场景树,虽然至今我也没搞清楚场景树是什么,只知道它有个根节点作为视口。总之这些什么七七八八的树状结构就构成了Godot处理游戏世界的一般逻辑,许许多多的回调函数按照既定的顺序(可能自顶向下,可能自底向上)在树状结构中执行。
关于信号
我原本是这样认为的,Godot的节点-信号模型,为游戏开发提供了天然的解耦模型,让各个节点之间不必因为依赖关系的丢失而丧失作用。一想到Unity一整个系统可能含有大量的依赖交织,一个Prefab脱离了某个场景就完全无法工作的情况比比皆是,当然也有可能是我太菜了的原因。无论如何,在Unity解决上述问题的方式基本上都是抽象出一套框架用来解耦,比如比较国内热门的QFramework,采取MVC框架进行系统间的解耦。
而Godot相当于把一整个ECS内置到了设计理念中,还有观察者模式等等。
“信号是 Godot 内置的委派机制,允许一个游戏对象对另一个游戏对象的变化做出反应,而无需相互引用。使用信号可以限制耦合,并保持代码的灵活性。”
所来惭愧,我在Unity很少这样去考虑解耦,说白了就是没有这个想法,既然Godot都鼓励我们去这样干,那为何不试试呢。况且前面提到了,信号跟C#天生一对。自定义信号的定制方式也和C#事件本身的处理方式雷同。当然也可以采用那种直接的复古的直接获取对象引用然后读写数据之类的野蛮操作进行依赖注入。
结语
未完待续......