前言
(本文使用版本为 Godot 3.5.x)
首先说一个观点,我们自学 Godot 主要是看自己的需求(想学的一般是想做独立游戏的吧,想吃饭的我建议你还是去学 Unity),不能说是这个引擎能干的事情我们都去学(除非你的目标是成为什么 Godot 砖家)。
基于这样的观点,本文并不是传统意义的什么 Godot 教程(比如什么如何用 Godot 制作 xxx,这样的教程我相信太多了),而是如何自学 Godot 的教程。换句话说,本文仅仅是给你自学指个路,毕竟这软件门槛确实有那么一点高。
官方文档——最好的参考资料
温馨提示:接下来我会贴很多链接,但你没有必要每读到一个链接就去把这个链接的内容看完(可以先收藏),除非你看到了新的温馨提示,那里我会建议你先回来看看这些链接。
首先在此附上官方文档链接:https://docs.godotengine.org/en/stable/
我这里贴的是英文版,有能力阅读英文的尽量直接看英文版,因为别的语言都是基于英文版翻译而来的,至少中文版确实存在错误和漏翻的现象。
实在看不动英文也不要勉强,中文也不是不行,左下角就可以切换语言。
入门级教程
从官方文档的 Getting Start 部分开始可以帮助你快速熟悉软件界面,我建议阅读前两节,后面那两个 Your First 2D/3D Game 不算特别需要,直接跳过也无妨。
编程语言基础教程
直接附上链接:https://docs.godotengine.org/en/stable/tutorials/scripting/index.html#programming-languages
一般来说只看 GDScript 就可以,这门语言特别简单(特别像 Python),你但凡稍微有点基础就能够快速入手。
这里的基础指的是程序设计基础,一般来说大学的 C/C++/Python 通识课程够用(前提是你好好学了而不是水过去的),中学的 VB/Python 课程因地区而异,我不好说。
如果你没啥基础的话,那我也不能保证你看这个够不够用,大概率是不够用的,毕竟程序设计的基本思想不是一个讲语法的教程能够教会的。对于这种情况,我建议你还是直接先去学我上面提到的课程再说(上面提到了 GDScript 特别像 Python,所以你直接去学习 Python 程序设计基础就可以)。当然,你也可以先不管,说不定你的需求特别简单,不需要太多基础也能做;你也可以选择多看看 Godot 自带示例项目以及别的开源项目,学习别人的写法,如果真没基础的话这样会比较难,但相对比较快。
顺带一提,官网也推荐了一个零基础教程:https://gdquest.github.io/learn-gdscript/?ref=godot-docs
不过没有中文版,我也不知道效果好不好,感兴趣可以试试。
以及,尽管 Godot 支持 C#,而且就算你恰好也会 C#,我还是建议你简单学学 GDScript(我相信你有这么好的基础的话肯定学的很快),因为在 Godot 里很多事情用后者处理会方便得多。
程序设计进阶教程
(没基础的话可以先跳过)
首先是最佳实践:https://docs.godotengine.org/en/stable/tutorials/best_practices/index.html
这里面讲了一些比较基本的面向对象思想,并且对于如何组织项目等问题都给出了建议,非常值得参考。
其次是优化:https://docs.godotengine.org/en/stable/tutorials/performance/index.html
这里面给出了如何进行调试分析以及优化的基本建议,推荐有一定开发经验的人员阅读,还算是比较实用;没什么开发经验的话就不建议看得太深入,比如假设你根本不知道什么时候该用线程,就别想着用线程优化,因为你很有可能搞成负优化。
功能实现类教程
官方文档的 Tutorials 部分主要是关于引擎对于特定功能(往往都是基础功能)如何实现的具体说明,这个就看自己的需求去阅读了。
比如假设我想做一个 2D Platform,那么我就需要去看 2D 以及 Physics 部分。
API 文档
这部分内容与在引擎内使用 F1
的效果完全相同,而且引擎内的版本一方面是离线版(网页版加载有时候会很慢),另一方面也有非常方便的检索机制,因此我推荐在引擎内阅读这一部分,而将网页端作为一个备选项。
如果你不知道 API 是啥,你可以先大致理解为这部分内容是对引擎里面的各种东西具体能做什么的一个说明。总之,API 文档 放在任何一个游戏引擎都是非常重要的内容,但从学习的角度来说我们对于这部分内容一般都是即查即用(然后你会自然地记住特别常用的内容),之后对于特别常用的内容我会贴链接以及作部分讲解说明。
官方 Q&A 平台
还是直接附上链接:https://godotengine.org/qa/
遇到问题可以首先在这里搜索(需要使用英文),很多时候你遇到的问题都是别人遇到过的。
此外,官方论坛的性质也类似:https://godotforums.org/
不过论坛上肯定不是纯粹的 Q&A,也有很多其他有用的资源,自己探索吧。
使用搜索引擎
使用 Google/Bing 等搜索引擎直接搜索问题一般也能找到一些资料。(最好用英文)
当然你很有可能搜到的就是刚才提到的 Q&A 平台上的问题,不过这也无妨。
其他资源
官方资源网站:https://godotengine.org/asset-library/asset
这里面资源不少,还能找到很多实用的插件(Addon),需要的功能引擎不直接提供的话可以来翻翻看。
Shader 资源网站:https://godotshaders.com/
里面有很多开源的 Shader 可以直接使用和学习。
一个相对火的教程网站:https://godottutorials.com/
这个并不是官方教程,我自己也没看过,不过既然比较火还是在这里贴一下,可以参考。
至于别的非官方教程资源我就更没有看过了,自己取舍吧,说不定能找到适合你的。
Godot 程序基础
温馨提示 2:阅读本节之前,你最好先至少看完了官方文档的 Getting Start 部分的前两节,且保证自己有一定的程序设计基础。
前言
这一节是为了解决官方文档缺少的一个重要内容:Godot 程序是怎么跑起来的?
(也不能这么说,因为在 API 文档里面有)
我个人认为足够了解自己的程序的运作机制是十分必要的,否则做程序设计时总会感觉不少地方有些含糊不清(比如不知道代码的运行顺序等等)。
主循环(Mainloop)
游戏程序本质上是一个 while 循环,游戏引擎提供了一个框架,你要做的就是往这个循环里面填东西,这一小节我们简单介绍 Godot 的主循环。
官方文档参考
在 API 文档中可以找到这部分内容:https://docs.godotengine.org/en/stable/classes/class_mainloop.html
你不看也没关系因为我接下来就是要讲这个。
process 与 physics_process
Godot 提供了本小节标题所述的两类主循环,它们是独立运行的,下面我们作简单介绍:
physics_process:大部分游戏引擎都提供的一类主循环,且部分游戏引擎(如 Gamemaker)只提供了这一类。
具体来说,这个循环是根据你预先设定的一个帧率(默认60)稳定运作的。当然,如果性能承受不住,那就会出现所谓的“掉帧”现象。
process:这个循环的速度取决于屏幕刷新率,你也可以简单理解为要多快有多快。
对于一些需要高刷的游戏(如音游),或者说对于特定的功能实现(如高精度计时器)来说很有用。
delta 参数:将行为与帧率独立
当你看到我对 process 的介绍时,你可能会疑惑:既然是要多快有多快,那我怎么保证我能做出我需要的速度呢?
事实上,Godot 的这两类主循环都会提供一个 delta 参数:从上一帧到这一帧所经过的真实时间,单位为秒。
因此,利用这样一个参数,一方面可以在 process 中有效地控制速度,另一方面对于 physics_process 来说,即使掉帧也不会出现“变速”现象。
当然,这要求你使用 px/s、px/s^2 (px 是像素)作为速度和加速度的单位,如果你以前使用的都是 px/frame 的体系,需要稍微适应一下。
(或者你继续用 px/frame 也行,只是掉帧就会变速)
SceneTree:默认的主循环结构
SceneTree 是 Godot 程序默认采用的主循环结构(这意味着你也可以自己做主循环结构,虽然大部分情况下这不太必要),也算是非常核心的一环。
官方文档参考
API 文档中同样对 SceneTree 有介绍:https://docs.godotengine.org/en/stable/classes/class_scenetree.html
我们可以看到 SceneTree 继承于 Mainloop,它就是默认的主循环。
(如果你不知道“继承”是什么,说明你对 Class 以及面向对象的概念不熟悉,可以参考官方文档中的最佳实践部分,也可以看一些讲面向对象的教程。当然,也可以暂时先不管。)
不过我不会讲这里面的太多内容,这一节我的目标是讲清楚 SceneTree 的基本组织方式,至于其他的功能,感兴趣的话自己在上面提到的文档中查阅。
节点树
SceneTree 管理的是节点(Node),组织节点的结构为树状结构。我相信你已经在 Getting Start 部分对于节点以及这样的树状结构的概念有了一定认识,接下来我们将会深入其实质。
受“管控”的函数:
节点由树状进行组织,统一由 SceneTree 进行管理,以下是一些由 SceneTree 调用的属于节点的虚函数:
(虚函数是指函数内容默认为空,你有需要的话就往里面加东西,专业术语叫作 Override。)
_process(delta)
:由 SceneTree 按照树的结构从上到下由外到内依次调用,调用频率与主循环 process 相同。注:如果节点为暂停状态,则不会被调用,亦可手动用相关函数(
set_process(enable: bool)
)直接禁用_process()
。_physics_process(delta)
:同理,只是对应的主循环为 physics_process。_enter_tree()
:节点被加入 SceneTree 时调用。对于同时加入的情形,依照同样的从上到下由外到内的顺序。_ready()
:优先级晚于_enter_tree()
,且顺序与之不相同。具体来说,整体上仍然按照从上到下的顺序,但是如果一个节点 A 有子节点,则首先“尝试”调用其子节点的_ready()
(这里说“尝试”是因为子节点可能也有子节点,这是一个递归过程),当节点 A 的全体子节点 ready 完毕后,再调用节点 A 的_ready()
。简单来说,这是从上到下但是由内到外的顺序。_exit_tree()
:与_enter_tree()
类似,节点被移出 SceneTree 时调用,但是顺序与_ready()
同理。
_enter_tree() 还是 _ready()?
相信一些读者可以注意到,本小节标题提到的这两个函数似乎都是用作“初始化”的,考虑到它们被调用的顺序不同,用处也自然不相同。事实上很多情况下两者并无太大差别,它们可以做相同的事情,只是实现方式有所差别。
不过考虑到官方开发了一个 onready
语法糖(参考官方文档编程语言基础教程部分),我们往往都首选 _ready()
。
此外,还有一个关于初始化的虚函数 _init()
,刚才没提是因为这个函数不归 SceneTree 管。事实上,这个函数是 Class 的构造函数,它比 _enter_tree()
的优先级还要更早,可以简单理解为载入内存的时候就被调用了。需要注意的是,当一个节点的 _init()
被调用时,它的子节点甚至都还没被载入内存,因此你不可能在这里调用到一个节点的子节点。
树函数
Godot 游戏运作的核心就是对节点树的操作,下面介绍一些常用的关于树的函数。
通用函数:
get_node(path :NodePath)
:获取指定路径的节点,路径与文件路径相似,"." 代表 Node 本身,"root" 代表根节点。举例:
get_node(".")
:这是 Selfget_node("..")
:这是父节点get_node("root/Test")
:获取根节点名为 Test 的子节点get_node("./Sprite")
:获取名为 Sprite 的子节点(可以省略“./”)
注 1:如果 Script 的主语为 Node,则可使用语法糖
$path
代替get_node(path)
,如$Sprite
。注 2:"xxx" 实际上为 String,严格的 NodePath 应该为 @"xxx",只是这个函数会自动转换 String 为 NodePath。
is_instance_valid(object: Object)
:检查某个对象是否有效,往往用于检查某个变量所指向的节点是不是被删除了。
关于父级的函数:
get_parent()
:获取父节点,等价于get_node("..")
get_tree()
:获取 SceneTree 对象
关于子级的函数:
get_child(index: int)
:获取第 index 个子节点(从 0 开始)get_child_count()
:返回有多少个子节点get_children()
:将全体子节点作为 Array 返回add_child(node: Node, goodname = false)
:将 node 作为子节点加入,若 goodname 为 true,给这个 node 起一个人性化的名字而不是一些乱七八糟的字符(除非这个 node 本来就有名字)move_child(node: Node, index: int)
:将指定的子节点移到第 index 个位置remove_child(node :Node)
:将指定的子节点移除(这并不会删除该子节点)
关于删除的函数:
queue_free()
:将节点加入到“待删除队列”中,每帧结束的时候统一删除,这将把节点的子节点一并删除。注:关于删除操作还有另一个函数
free()
,这是 Object 意义上的基础删除,尽管可以立刻删除,但它作为 Object 基础功能并不会删除子节点,并且会带来很多问题(因为它并不是为 Node 量身定做的函数)。
对于初学者来说,只记住 queue_free()
就行了,另一个函数可以当它不存在。但要记住“待删除队列”的概念,有时候你会很自然地认为一个节点删了之后,一些代码就不再会运作了,但实际上删除的时机是每一帧结束的时候,所以可能会跟你想的不太一样。
场景节点(Scene)
关于 SceneTree 的另一个重要概念当然是 Scene。简单来说,Scene 就是你设计节点树的载体,Godot 引擎会要求你给 SceneTree 提供一个 Main Scene(或者你可以理解为 First Scene),然后在程序开始运行时,Main Scene 就会被加入 SceneTree。
PackedScene
你在编辑界面编辑和保存的 tscn/scn 文件都属于 PackedScene,字面意义就是被“打包”的场景节点。PackedScene 属于资源,与图片资源、音频资源等类似,它并不是节点,但它可以变成节点:
PackedScene 有一个 instance()
函数,调用该函数将返回一个由该 PackedScene 打包好的节点,你可以将这个新节点加到 Main Scene 里面去(通过将其作为其中某些节点的 child 加入)。此外,你甚至可以将其作为根节点加入到另一个 tscn 文件中,相当于新建了一个继承场景。
此外,你也可以直接在 Scene 的编辑界面中加入 PackedScene,换句话说,你可以把 PackedScene 看作一个节点直接放到其他的节点树中。
(这应该是官方文档 Getting Start 部分介绍过的内容,只是没有提到这些具体概念,我相信你现在已经有了更好的理解)
切换场景
SceneTree 提供了切换 Main Scene 的函数 change_scene_to(scene: PackedScene)
,这将允许你进行场景切换,但有时候我们也许不希望直接换掉 Main Scene(比如分屏类游戏),我们也可以选择手动删除既有节点以及从特定的 PackedScene 调用 instance()
并将返回的新节点加入到 SceneTree 中。
结语
这一节的内容并不多,因为都是 Godot 程序的基础内容,理解这些内容能够更好地帮助你理解和使用 Godot 引擎。(当然,不理解也不影响使用,很多例子已经证明了这一点)
我编写这一节仅仅是因为官方文档没有在 Tutorial 栏目中专门讲解这些内容,而是将它们分散在 API 文档的各个板块,属实是给我个人自学带来了诸多不便。
温馨提示 3:如果读到这里,你还没有阅读之前提到的官方文档中最佳实践的内容,那么强烈建议现在去看看。
尾声
距离我上一次更新这个网站都不知道过了多久了,不过我确实也一直懒得更新(
这篇文章主要是给我的一些朋友参考的,所以普适性可能不是那么强,但如果能帮到一些陌生人,我也会很开心的(
总之,欢迎留言评论(