从零开始独立游戏开发学习笔记(六十五)--Godot 学习笔记(一)--入门-结束

因为 unity 不行了,所以换 godot 了。这时候学 Godot 和 blender 具有同样光明的未来。
0. 寻找教程
第一步的学习比较重要,不然差教程养成的坏习惯会伴随终身(不至于,但比较难改)。
先从官方教程找起:

很幸运,有 Getting Startted,一个有 Getting Started 的软件是具有光明前途的软件。
简单翻一下看看,文档用的是最新的 4.1 版本,正是时下的最新版,更新很快,一个文档更新很快的软件是具有光明前途的软件(至少短时间内是)。
我直接下载最新版本的 Godot,没有选 .NET 支持,看了下别的地方的说辞,说是在网上搜索 Godot 问题,大部分给的都是 GDScript 的回答,也就是说选 C# 短时间内没有前途,所以直接 GDScript 走起,说是很像 python,那正好上手简单。
开学
1. 介绍
1.1 介绍 Godot
Godot 是一款能做 2D 和 3D 游戏应用的引擎,也能做主机游戏。不过因为许可证问题,Godot 不能原生支持主机游戏。不过没关系,主机移植放哪里都是难事,Unity 也不简单,不是 Godot 的错。
1.1.1 Godot 能做啥
原来 Godot 桑不是一开始就是开源的,是 2014 年才开始开源的,也就是这个时候软件完全被重写并提高的。 然后展示了一堆 Godot 开发的游戏,全是 2D 的。
1.1.2 Godot 看起来咋样
自己下载看不就知道了,设计软件还能咋样,不都一样吗。

当然 Godot 也可以连外部软件,比如用 blender 画 3D,或者 vscode,vs 来写代码。
1.1.3 学 Godot 前需要知道的事
你最好会代码,Godot 没有那么对代码小白友好。
1.2 Godot 关键词一览
在 Godot 里,游戏场景 Scene 就是一棵有很多节点的树,树上有许多节点,你将把这些节点连在一起,让他们之间发出和接受信号来互相连接。
1.2.1 场景 Scene
在 Godot 里,Scene 场景和别的游戏引擎里有稍微一点点的区别。Godot 里的引擎可以是一个大场景,也可以是一个房子,一块石头,一个角色,甚至是一个 UI 界面。也就是说,Godot 里的 Scene,同时兼具其他引擎里 Scene 和 Prefabs 的功能,是一个很大的概念。
1.2.2 节点 Nodes
节点是树上的最小单位。
实际上,节点和场景(树)其实很像,一颗树本身也可以嵌套进另一颗树上,这颗树本身就成为了节点。
1.2.3 场景树 Scene tree
所有的场景会汇总在一棵场景树上。场景这个时候又可以看作是节点了。所以其实可以混用。不过最好还是用场景这个词来描述比较好,因为之前说过了,场景可以是任何东西。最好还是说你的游戏由场景构成。
1.2.4 信号 Signal
节点可以发出信号,借此可以让节点间传递信息,这一特性可以让你少编程一点。提供了一些灵活性。
(说白了,其实就是观察者模型在 Gogot 里的应用罢了)
举个例子,按下按钮触发一个 Signal,然后用来执行一段代码或者某个其他事件之类的。
包括还提供了一些类似于物体碰撞的时候发出的信号,这种常用功能。
1.2.5 总结
场景,节点,场景树,信号是 Godot 的 4 个核心概念。
1.3 稍微看下 Godot 的界面
Godot 真的很轻量,官网直接下载一个压缩包,解压直接用就行。
建完项目看了下,其实就一很标准的设计工具。左上边是场景树,左下是资产。右边是当前物体的属性等。下面是各种调试程序动画面板之类的东西。
中间的主屏幕有 4 个分类,2D,3D,Script,AssetLib。 2D 3D 就不说了,开发哪个就用哪个。Script 是 Godot 内置的代码编辑器,带有基础的功能。AssetLib 就是免费素材商店罢了。
Godot 还内置了 Class Reference 的文档, 按 F1 呼出,或者在 Script 里按住啊 Ctrl + 点击。便可方便地看文档。有一种吊打 Unity 的美。

1.4 新 Features
其实就是教你咋学 Godot,没啥,给了一堆搜索技巧,一些编程学习网站。然后再给一些论坛,基操了。
看了下给的几个论坛:
Reddit 很活跃,但基本都是吹水 “我花了 XX 小时做出的场景” 这种。以及一些 Feature Request。感觉比较适合有一定了解的人进来玩玩看。
Godot 论坛,难以置信居然还挺活跃,但也没那么活跃吧,只能说和其他比起来算活跃的。
Discord, 毋庸置疑最活跃的地方,适合新人。
看到一个 qq 群链接,死活打不开。不过反正 qq 群正是技术漩涡,能不进就别进吧。
1.5 Godot 的设计
原文是设计哲学,算对哲学这个词的滥用吧。
1.5.1 面向对象设计
Godot 的核心是面向对象设计,这个看之前树节点啥的就能感觉到。Godot 想营造出一种不同于传统游戏编程的模式,更加直观。
Godot 里你可以构建和聚合场景,并嵌套。比如你可以创造一个灯场景,再创造一个路灯场景包含灯场景。再建一个道路场景上面有路灯场景,这个时候改变灯的颜色,所有路灯的颜色都会改变。
如果你对 OOP 编程熟悉的话,其实所谓的 Scene 就是 Class。
请注意,虽然听起来像是 Prefabs 的定义,但 Scene 不是 Prefabs,因为你可以自由扩展 class。修改基底 class 会同时更改高级 class。让在 Godot 里的游戏设计和 oop 息息相关。
1.5.2 nodes
Godot 提供了一系列的节点用作各种目的。节点是树的一部分并由母节点继承而来。Godot 里大多数节点是独立存在的,除了某些像是碰撞节点是属于物理实体的这种设定。

以上图为例,一个 Sprite2D 同时既是 Sprite2D,也是 Node2D,还是 CanvasItem,还是个 Node,它继承母节点的所有特性。
1.5.3 Godot 的目标
Godot 意图提供所有制作游戏的工具,不过显然现在还做不到,所以提供了许多使用外部工具的途径。
顺带一提,Godot 本身这个软件就是一个使用 Godot 引擎制作的游戏。正如 Git 的代码目前是用 Git 在管理一样,所有成功的开源软件都这样。这也是为什么这款引擎对编程具有一定的要求。
2. 一步一步做游戏
2.1 节点
之前只是粗略地说了节点,让我们实际上引擎来看一下。
节点是执行功能的最小模块,Godot 内置了许多节点,执行各种类似摄像机,UI,等各种功能。
一般来说,节点具有以下属性:
名字。
可以更改的属性。
每帧都有的 callback 函数。
可以扩展更多的功能。
可以作为其他节点的子节点。
2.2 场景
当把节点组成一棵树的时候,这就是一个场景。场景本身又可以作为一个节点。
Godot 就是一个场景编辑器,你可以做很多场景,Godot 只需要一个场景作为主场景,用于第一次加载。
场景有以下的特性:
有一个根节点。
可以被储存在硬盘上以供以后加载。
场景可以被实例化,比如有一个人物场景,然后根据此实例化十几个人物。
实际操作
不难,加了个 Label ,很容易就 Hello world 了。

Scene 在实例化之后,如果改变原始 Scene,所有的 instance 都会改变。当然也可以直接改变某个 instance 的属性,这个属性会与原始 Scene 失去联系,从此改变原始 Scene 的这个属性,再也不会影响这个 instance,除非点击属性旁边的还原按钮。
整个 Godot 都是以 Scene 和实例化来建立的。当使用 Godot 创作游戏的时候,不需要考虑什么设计模式。直接从玩家看到的对象开始设计和想象。
2.3 GDScript
脚本要挂在 node 上,是 node 的扩展和延伸。也就是说脚本拥有这个 node 的所有属性。脚本用于 Godot 本身无法完成的功能。
Godot 本身支持多语言,甚至可以在一个项目里使用多种语言,比如用 GDScript 写简单的 gameplay,用 C# 写复杂的算法来优化性能。
GDSCript 是 Godot 自家的语言,当然和引擎本身具有最紧密的联系。
2.4 创建第一个脚本
每个 GDScript 脚本本身就是一个 class,比如在 Godot 里添加脚本自带的 extends Node,意思就是母节点是 Node。
在 Inspector 里看到的所有属性都是以空格形式隔开,在代码里这些属性就是全小写并把空格换成下划线即可得出,鼠标移动到属性上可以看到详情。
_init() 函数是初始化的时候执行,_process(delta)则是每帧执行,其中 delta 是上一帧到现在的时间。
所有内置方法都是以 _ 开头以区分。
最后写了这样的代码来控制物体旋转:
js
func _process(delta): rotation += delta * angular_speed var velocity = Vector2.UP.rotated(rotation) * speed position += velocity * delta
解读:每帧执行,改变旋转角度。Vector2.Up的目的是创造角度,先创造一个向上的方向(也就是 0 度),再旋转 rotation 大小的角度。所以综上就是创造一个跟随者旋转角度的速度向量赋予给 position,所以整体物体就会绕着中心旋转。
2.5 监听用户输入
Godot 里有两种方式监听用户输入:
和 _process 类似,Godot 也提供了 _unhandled_input 函数来处理键盘输入,这个函数会在每次用户按键的时候触发。
Input Singleton。Singleton 是一个全局变量,可以在任何地方访问,Godot 提供了很多 Singleton 来做各种事情。
这里我们使用第二种方法:
js
func _process(delta): var direction = 0 if Input.is_action_pressed("ui_left"): direction = -1 elif Input.is_action_pressed("ui_right"): direction = 1 rotation += delta * angular_speed * direction var velocity = Vector2.ZERO if Input.is_action_pressed("ui_up"): velocity = Vector2.UP.rotated(rotation) * speed position += velocity * delta
2.6 使用 Signal
Signal 是 node 之间传递信息的方式,可以让某个事件触发 signal,也能让 signal 触发某个事件。
Signal 是 Godot 实现的委托机制。可以让两个对象在不引用对方的情况下做出反应。
比如说生命值条会在玩家受伤后缩短。
信号其实就是 Godot 的观察者模式的实现。
2.6.1 在编辑器里使用信号
比如我们想做一个按钮,按钮按下触发一个信号来触发某段代码执行。想要信号起作用,信号要连接到一个具有脚本的节点,并链接一个接受函数,信号触发后就会执行这个函数,正好我们之前写了一个脚本。按钮信号正好可以传到这个节点上并执行其脚本里的接受函数。
当然,信号也可以连接到内置函数上,这算高级内容了。
使用 Godot 内置信号功能后,会在接受节点的脚本上创建对应的接受函数。
2.6.2 在代码里使用信号
虽然编辑器里也可以使用,但是如果节点是在游戏过程中生成的,那就没办法用了。
在代码里操作也不难,找到节点再与对应的信号connect就行了。注意是与信号connect而不是节点。
2.6.3 自定义信号
当然,开发者也可以自定义信号。
文档居然提到了命名规则,信号最好用动词的过去式来命名。
定义好信号后,使用 emit 来触发信号。其他的和普通信号一样。会去执行连接的脚本。
3. 第一个 2D 游戏
Godot 官方上来就是直接教做手游,光明的未来(
3.1 基础项目设置
因为是手游,所以对宽高比要有不一样的设置,去往项目设置里设置为 480 x 720。
不过这样不够自适应,所以我们还要再拉伸力设置模式为 canvas_item 并让 aspect 设置为 Keep。自适应设置参考这里。
3.2 玩家 Scene
先从创建玩家 Scene 开始,根节点选择 Area2D,这么选的原因是玩家需要判断碰撞,哪些东西进入了玩家区域。这是“玩家”的基本功能,而一个规则就是,一个 Scene 的根节点应该要反应这个 Scene 的基本功能特性,然后再加各种子节点来增加功能。
3.2.1 编组防止手贱
在添加子节点之前,为了确保我们不手贱拖拽了子节点或者变大缩小。将其编组:

3.2.2 命名规范
Class 采用 PaselCase,函数变量都用 snake_case,常量全大写。(还说自己不是python
3.2.3 2D 动画
创建一个子节点-》AnimatedSprite2D。有一个 waning,告诉我们缺少 Frame,毕竟是动画。正好素材里有,我们直接加。
选择创建 SpriteFrame,点击后下面出现面板。给左边的 Default 重命名为自己想要的。按照教程添加好两个动画。
3.2.4 碰撞
添加一个 collisionShape2D,shape里创建一个形状。
3.3 Player 代码
@export 可以让参数暴露在 inspector 里。
3.3.1 输入映射
输入配置在项目设置里,配置好四个方向的移动后,代码里就可以用 Input.is_action_pressed 来获取刚刚配置的输入了。
代码如下:
js
extends Area2D@export var speed = 1600var screen_size# Called when the node enters the scene tree for the first time.func _ready(): screen_size = get_viewport_rect().size# Called every frame. 'delta' is the elapsed time since the previous frame.func _process(delta): var velocity = Vector2.ZERO if Input.is_action_pressed("move_left"): velocity.x -= 1 if Input.is_action_pressed("move_right"): velocity.x += 1 if Input.is_action_pressed("move_up"): velocity.y -= 1 if Input.is_action_pressed("move_down"): velocity.y += 1 if velocity.length() != 0: velocity = velocity.normalized() $AnimatedSprite2D.play() else: $AnimatedSprite2D.stop() position += velocity * speed * delta position = position.clamp(Vector2.ZERO, screen_size)
其中,$ 代表 get_node 的简写,路径为相对当前节点。所以能获取子节点。
Clamp则为限制数值不能超过的最小值最大值,这里设置为不能超过 screen size。
3.3.2 碰撞
碰撞使用 Area2D 的 bodyenter 信号。信号里写上如下代码:
js
func _on_body_entered(body): hide() $CollisionShape2D.set_deferred("disabled", true)
使用 set_deffered 而不是只直接$CollisionShape2D.disabled= true,因为直接设置的话,如果这帧还在继续,那么会出现错误。要等这一帧结束再设置才比较好。
3.4 敌人
教程这里就很粗糙了,完全都不解释就直接让你加一堆节点。
不过看到最后还是能理解的,整体来讲没什么新东西,新的只有节点的作用,但基础操作方法和 player 没有变化。
3.5 主要场景
玩家和敌人创建好后,接下来就是创建主要游戏场景了。
根节点用 Node(不是 Node2D)因为主要场景的功能并非2d特有。接下来我们实例化玩家和敌人。
3.5.1 path
path节点可以用来设置敌人安放的路径。 创建好 path 后,添加一个 pathfollow2d 节点,这个节点会在 path 上巡逻,然后我们再在这个节点上放置敌人,这样敌人就会在 path 上安放(所以刚才的path最好不要放在屏幕中间,不然敌人会凭空出现)。
3.5.5 实例化敌人
我们在 script 里 export 一个 PackedScene 格式的变量,然后把敌人scene赋值给它。这里教程真的很烂,就一股脑告诉你咋做咋做,也不告诉你为啥要这么做,总之就是很烂。虽然确实是该教的都教了,但就是很烂。
3.6 UI
主要场景能玩后做 UI。
选用 CanvasLayer 作为节点,这个节点会出现在其他节点上面,正是 HUD 所需。 剩下的按照教程来就行了,基本逻辑都不变,节点,场景,信号基本特性熟了后,剩下的只不过是一些 best practice 的熟悉罢了。况且这教程代码看着一点也不 best。所以就不讲了。
3.7 额外润色
Input Mapping 里添加的按键还可以用于创建快捷键。比如我们创建一个叫做 start_game的input。然后在按钮上绑定,就可以让按键操作按钮了。
4. 成果
一个小垃圾游戏。不过 Godot 确实好学,方便简单,喜欢。