Godot学习笔记

记录Godot游戏引擎的学习

本文章长期更新(最后更新于2026.4.28)

前言

古人有云,温故而知新,学习做笔记

考虑到应该学习一点其他东西了,所以打算学习Godot引擎做出全宇宙爆款的胎死腹中音乐游戏,这个是古话了

其实在选择学什么之前,也在考虑是选择Godot还是市场占有率更高的Unity. 但是Godot有以下吸引我的点

  • 看着性能占用不高

Unity给我的感觉是很重型。假设我要做一个项目,我要启动Unity Hub,再启动Unity,然后启动Visual Studio. 要打开的东西很多,无法保证我的便携电脑能带的动。而Godot是一个绿色软件,看着很轻量化。

  • GDScript看着更容易上手

因为目前所规划的想做的游戏基本都是2D游戏,虽然说还有像Cocos2D这样的专业选手,但是我的C++水平基本等于没有,Java目前是起步阶段,C#的学习看着也比较曲折。

Godot的原生编程语言GDScript以近似Python一样的简便而闻名。实际体验也确实容易理解。没准GDScript学会了还能点下Python技能树。

  • Unity经常做妖

我懒得对这一条进行解释。

其他的暂时没想出来,我还没有达到从技术方面对他们进行剖析的水平。如果未来水平足够或者需求提高,再考虑学习其他引擎

本文基于Clear Code的《Godot4终极入门教程》,在这里附上B站链接

关键概念

节点

节点 (Node) 是Godot的一个重要的概念。场景是一个节点,玩家是一个节点,很多东西都是节点。整个Godot就是一个节点大世界。

在Godot中基本要做的就是制作各种节点然后将他们联系在一块,个人感觉可以将它们视作多个对象。

主页面

图为Godot的主界面,按下Ctrl + A来增加新的节点。

节点使用帕斯卡命名法,即每个单词首字母大写。而其他内容使用蛇形命名,即所有字母小写,用下划线链接。Godot会自动帮你做好这些内容。

节点具有图层优先级,类似于Ps,越往下面图层就会越优先。

父节点与子节点

节点具有包含关系。可以将一个节点B归属到另一个节点A. 此时A为B的父节点,反之为子节点

父节点中对属性的改变都会对子节点造成影响,而反之不然。

可以右键选择父节点增加子节点。如果要把已有的节点作为子节点,按下Ctrl + Shift + A或者点击“实例化子节点”选项,将会列出所有可以作为子节点的其他节点

语言

Godot的原生语言为GDScript,虽然宣传说的是类似Python,但准确来讲,他就是Python. 除了部分关键词有区别外,其他的基本如出一辙。

游戏中的所有属性都可以在属性面板中找到,将鼠标选在其上方将会自动显示对应的变量名称。

由于Markdown没有原生GDScript支持,所以代码块将会使用Python.

语言特点

  • 数据类型:常见的数据类型都有覆盖,数组覆盖了元组和列表。

  • 两种变量:普通变量常量

    普通变量格式:

    1
    2
    var name = 200 #直接赋值的一般格式
    var names: int = 114 #指定数据类型的格式

    常量格式:

    1
    const value = 200 #直接赋值的一般格式
  • 函数:用来实现功能

    Python应该用的是define作为函数关键词,但GDScript中使用func作为关键词。格式如下所示

    1
    2
    func name_here(vari a: int) -> bool #括号里透数据类型,箭头指定返回类型
    return true #首行缩进说明其仍然属于函数中
  • 流:包括if-else,for-while循环,continue-break,数学运算,数值比较等

  • :和我的想法差不多,有功能的节点可视为一个类

内置函数

游戏默认有两个内置函数,且都以下划线开头。两个函数如下所示

1
2
3
4
5
func _ready() -> void:
$logo.rotation_degrees = 90

func _process(delta: float) -> void:
pass

_ready,我打算叫他预备函数,在节点准备好后就会开始运行

_process,我打算叫运行函数,游戏每帧都会执行

在父节点中可能会更改子节点的属性。有两种方式可以调用

1
2
get_node("node path")
$node path #更加常用的方式

输入后,自动补全会列出所有可用的子节点。

关于GDScript更详细的学习内容,可以查看我发布的另一篇基于Brackeys的快速入门GDScript课程的笔记

增量时间(Delta)

上文提到了运行函数是每帧执行的操作,但这也带来一个问题。

假设一个物体每帧移动10个像素,在30帧/秒内将会移动30 * 10 = 300像素。但如果我用的是神威太湖之光来打游戏,帧数可能会到60帧,120帧甚至3000帧/秒,那么这个物体将会移动600个像素甚至更高,而过高的移动速度会让游戏根本没法玩。

我刚好遇到一个例子,就是在我的电脑上玩DJMAX TECHNIKA 2时,倒计时会数的很快。查了贴吧之后才知道这是因为该游戏原先为街机开发,用的是一块60Hz的触摸屏。而我的电脑屏幕刷新率很高,从而导致运行速度过快

因此,为了解决在不同帧率的机器上有一致的运行速度,我们需要一个统一的时间间隔,而这就是增量时间

增量时间为创建一帧所需要的时间。例如30帧的增量时间就是1s / 30f ≈ 0.0333

具体的使用如表格所示

速度 (每帧像素) FPS 间隔时间 (秒) 原运动 (每秒) 间隔运动 (每秒)
10 30 1 / 30 = 0.033 10 * 30 = 300 10 * 30 * 0.033 = 10
10 60 1 / 60 = 0.017 10 * 60 = 600 10 * 60 * 0.017 = 10
10 120 1 / 120 = 0.008 10 * 120 = 1200 10 * 120 * 0.008 = 10

可以看到,增量时间就是与原来的运动速度相乘,从而抵消以实现原来的运动速度

在具体的代码中,将需要的量与delta相乘

1
2
pos.x += speed * delta
$logo.rotation_degrees += 45 * delta

后续的很多函数中会在构建中自动运用增量式案的运动,所以不用一直考虑增量时间,不过,这仍然是一个重要的概念

获取输入

这一部分在GDScript笔记中亦有记载,这里复制粘贴一下

要获取输入有两个步骤:

  1. 创建映射事件

首先打开项目 - 项目设置 - 输入映射

建立新的映射之后按下加号,绑定键位。这里采用视频所给出的案例:创建一个名为my_action的事件,内容为按下空格键就会改变Label的颜色

  1. 脚本调用映射事件

用于按键绑定的系统函数**_input**函数格式如下

1
2
3
4
5
6
func _input(event):
if is_action_pressed(my_action) # 按住按键
$Label.modulate = Color.RED

if is_action_released(my_action) # 释放按键
$Label.modulate = Color.GREEN

值得一提的是,按下有两个函数,is_action_pressedis_action_just_pressed. 核心区别就在于前者会一直检测按下的状态,而后者只检测第一次按下

有一个函数在搞2D游戏时用起来挺不错,即Input.get_vector,参数顺序通常为(negative_x, positive_x, negative_y, positive_y),在Godot体系中是左右上下。每次执行都会在对应的参数上+1

可以参考以下代码

1
2
3
4
# 设置direction变量,调用函数
var direction = Input.get_vector("left", "right", "up", "down")
# 通过乘以运动速度和增量时间,让精灵在每一帧都会运动
position = direction * 200 * delta

更多节点该怎么办

唯一名称访问

假设出现了复杂的场景,节点也将层层嵌套。如果直接将这个节点拖进编辑器,路径会很长。

如果这个节点很独特,可以右键点击选中“作为唯一名称访问”

节点之间互相访问

在Godot中,最重要的就是节点之间能调用属性,因此需要掌握他们链接的方法

(待完善)

物理

前面的环节都在讲移动一个精灵,但是在游戏中,精灵和精灵之间肯定会有碰撞,还会有专门的墙体限制精灵活动,这就需要用到物理属性了

物理是游戏运动的基础,图像本身没有碰撞属性,必须使用碰撞体和物理体节点(如Area2D、PhysicsBody2D)来处理物理交互。

物理属性

CollisionObjectPhysicsBody是物理属性中的基础。在PhysicsBody中包含了三个属性。其作用如下表所示

名字 定义 作用
StaticBody 静态碰撞体 不能自己移动,但其它物体可与其碰撞
CharacterBody 代码操控实体 由代码驱动的玩家或敌人,通过内置方法实现复杂移动
RigidBody 物理实体 仅通过物理力量移动(如手榴弹等),给定初速度即可移动
Area 检测区域 检测其他物体是否进入
0条搜索结果。