文章

GDScript基础语法

虽说Godot支持C#和C++等其它语言,但目前阶段GDScript更能发挥出Godot的特性与优势,其开发社区的资料文档也更为全面完整,此博客记录了入门所需的基础GDScript语法

GDScript基础语法

一、脚本结构

1.1 关于GDScript

  • GDScript是专门为了Godot进行游戏开发使用的面向对象的编程语言
  • GDScript和Python的语法很相似

1.2 GDScript的脚本结构

  • 我们在新建项目中创建一个名为MainNode类型节点后,为这个节点添加GDS脚本xxx.gd,其会被初始化为这样,其中_ready()函数是在该节点被初始化后立刻执行的语句,类似于Unity中的Start()或者Awake()函数(具体区别我暂时不懂),而_process(delta)类似于Unity中的Update函数
1
2
3
4
5
6
7
8
9
extends Node

# Called when the node enters the scene tree for the first time.
func _ready():
	pass # Replace with function body.

# Called every frame. 'delta' is the elapsed time since the previous frame.
func _process(delta):
	pass
  • 函数_ready()_process(delta)均为Godot的内置函数

二、变量与常量

2.1 变量的定义与修改

  • 通过var关键字定义变量
1
2
3
4
5
6
7
8
9
extends Node

# var 变量名称 = 默认值
var health = 100

# 游戏开始时在控制台处打印生命值
func _ready():
	print(health)
	# 打印结果为100
  • 通过赋值修改变量值;赋值也可以用+=-=*=/=
1
2
3
4
5
6
7
8
9
extends Node

var health = 100

func _ready():
	health = 20 + 20
	health += 10
	print(health)
	# 打印结果为50

2.2 量的作用域

  • 例如if语句内或者函数内部声明一个新变量,则只能在该结构内部使用
  • 若想在全局都能使用,则将变量定义在外部

2.3 数据类型

2.3.1 动态类型

  • GDScript中定义变量无需写出数据类型
  • 甚至可以像python一样更改同一变量名的数据类型
1
2
3
4
5
6
# bool值
var my_bool = true
# 数字
var my_int = 100
# 更改数据类型
var my_int = false
  • 数据类型转换,int转成string
1
2
3
4
var num = 5
var text = "A band usually has " + str(num) + " members"
print(text)
# A band usually has 5 members
  • 数据类型转换,float转成int,会仅仅去掉小数位(并无四舍五入)
1
2
3
var decimal = 3.94
print(int(decimal))
# 3

2.3.2 设置静态类型

  • 我们也可以使得一个变量的类型不变
1
2
# 设置为整型,该变量名后续无法被改变数据类型
var damage: int = 15
  • 也可以使用推断类型,让系统自己判断数据类型
1
2
# 系统会自动判断为整型,该变量名后续同样无法被改变数据类型
var damage := 15
  • 通过添加@export前缀来使得我们可以在节点的检查器Inspector中设置该变量的值,这样的话引擎只认你在检查器中设置的值,而在脚本中等号后赋予的值将只是一个默认值而已(类似Unity中脚本附着的GameObject上的脚本组件处可以编辑公共变量的值或者[serializefield]前缀的私有变量)
1
2
3
4
@export var damage := 15
# 假设我们在Inspector中设置damage值为100
print(damage)
# 100

GodotInspectorSetValue.png

2.3.3 向量

  • 常用二维向量Vector2和三维向量Vector3
1
2
3
4
# 二维向量由两个浮点数组成
var vec2 = Vector2(0.0, 0.0)
# 三维向量由三个浮点数组成
var vec3 = Vector3(0.0, 0.0, 0.0)
  • 修改向量的项
1
2
3
4
5
var position = Vector3(1, 2, 3)
# 横坐标加2
position.x += 2
print(position)
# (3, 2, 3)

2.3.4 字符串

  • 我们可以通过my_str.length()获取字符串my_str的字符长度整数

2.4 常量的定义

  • 使用关键字const来定义常量
1
const GRAVITY = -9.81

三、输入与输出

3.1 信息输出

  • 运行程序后在底部控制台显示信息
1
print("It's MyGO!!!!!")

3.2 按键输入

  • Project Settings面板中的Input Map处设置按键映射,我们新建一个例如名为my_action的动作,并将空格键Space绑定到该动作下

GodotInputMap.png

  • 接下来我们在父节点Main的脚本下引出_input(event)函数用于处理按键输入,这个函数是Godot内置的函数
1
2
3
4
5
6
7
8
9
10
extends Node

func _ready():
	pass

func _process(delta):
	pass

func _input(event):
	pass
  • 函数_input(event)在接收到任意输入的时候才会运行;其中event指代触发输入的事件
1
2
3
4
5
6
func _input(event):
	# 括号内引用我们设置好的按键映射名称,此处是"my_action",即Space键
	if event.is_action_pressed("my_action")
		# 游戏运行时,按下空格键则会执行此处语句
	if event.is_action_released("my_action")
		# 松开空格键会执行的语句

四、数据容器

4.1 数组

4.1.1 数组的创建

  • 创建空数组
1
var items = []
  • 在GDScript中可以在数组中混合数据类型,我们也可以限定数组元素为特定数据类型
1
2
3
4
# 普通数组,不限制元素的数据类型
var items = [3.14, "KON", 114514]
# 限定为字符串类型数组
var girls: Array[String] = ["Nina", "Bocchi"]

4.1.2 数组的读写

  • 通过索引来对数组元素进行读取或修改
1
2
3
4
5
6
# 读值
print(girls[1])
# Bocchi

# 写值
girls[0] = "Anon"
  • 删除和添加数组元素
1
2
3
4
5
6
7
var crychic: Array[String] = ["Tomori", "Sakiko", "Muzimi", "Taki", "Soyo"]

# 删除第二个元素"Sakiko"
crychic.remove_at(1)

# 添加新元素"Anon"到数组末尾
crychic.append("Anon")

4.2 字典

4.2.1 字典的创建

  • 字典中的每个元素为一对key: value
1
2
3
4
5
var instruments = {
	"Guitar": 2,
	"Bass": 1,
	"Keyboard": 0
}
  • 在GDScript的字典中,可以有多种类型的键值对
  • 你甚至可以在字典中嵌套另一个数组或者字典,此时我们可以通过比如两个键来访问
1
2
3
4
5
6
var players = {
	"Steve": {"Health": 100, "Level": 1}.
	"Alice": {"Health": 80, "Level": 3}
}
print(players["Steve"]["Level"])
# 1

4.2.2 字典的读写

  • 通过字典的key索引来读出值的大小或者为某键重定义一个新的值
1
2
3
4
5
6
# 读值
print(instruments["Bass"])
# 1

# 写值
instruments["Bass"] = 0
  • 为字典添加新条目
1
instruments["Violin"] = 1

4.2.3 数组的循环遍历

  • forin关键字获取字典中的键
1
2
3
# 这里的name获取到的是字典instruments中的键
for name in instruments:
	print(name + ": " + str(instruments[name]))

4.3 枚举

  • 枚举中用于定义一些特殊的标签,其好处之一在于可以在Inspector中进行标签的选取,很方便,这与我们在Unity中的枚举是同样的道理
1
2
3
4
5
6
7
8
9
# 定义了一组阵营标签
enum Alignment {
	ALLY,
	NEUTRAL,
	ENEMY
}

# 比如可以定义某个对象的成员变量中的阵营为敌人标签
@export var slime.alignment = Alignment.ENEMY
  • 枚举的本质上是为其内元素设置了从0开始的索引值,比如我们print()枚举中的某个元素就会打印出来其编号;我们也可以在定义枚举的时候修改索引值,但是一般没必要

五、逻辑结构

5.1 条件语句的使用

5.1.1 判断结构

1
2
3
4
5
6
if 判断语句1:
	执行语句1
elif 判断语句2:
	执行语句2
else:
	执行语句3

5.1.2 比较运算符

  • 等于/不等于:==/!=
  • 大于/小于:>/<
  • 大于等于/小于等于:>=/<=

5.1.3 逻辑运算符

  • 且:and,例如x == y and y > z
  • 或:or,例如x == y or y > z

5.2 match语句的使用

  • 类似C++中的switchcasedefault语句,依据值是否匹配来有选择地执行语句 ```gdscript enum Alignment { ALLY, NEUTRAL, ENEMY }

@export var alignment: Alignment = Alignment.ENEMY

func _ready(): # 对变量alignment的值进行评估,对其不同值进行执行语句的选取 match alignment: Alignment.ALLY: 执行语句1 Alignment.NEUTRAL: 执行语句2 Alignment.ENEMY: 执行语句3 _: 默认情况下的执行语句

1
2
3
4
5
6
7
8
9
10
11
12
13
### 5.3 循环语句的使用

#### 5.3.1 `for`循环
- `for`循环执行指定次数

```gdscript
for n in 4
	print(n)
# 0
# 1
# 2
# 3
  • for循环用于遍历数组
1
2
3
4
5
6
var crychic: Array[String] = ["Tomori", "Sakiko", "Muzimi", "Taki", "Soyo"]

# 遍历数组中的元素
for girl in crychic:
	# 将会逐行打印出数组中的字符串元素
	print(girl)

5.3.2 while循环

  • 一致循环运行直到条件达成才结束循环
1
2
while 循环条件:
	循环语句

5.3.3 breakcontinue

  • break关键字结束循环,执行循环后的语句
  • continue结束当前一层循环,进入下一层循环,并非结束循环

六、函数

6.1 定义格式

  • 可以不接收参数,可以没有return
1
2
3
func 函数名(参数1, 参数2, ...):
	执行语句
	return xxx
  • 指定接收参数类型,也可以不指定
1
2
3
func 函数名(参数1: 数据类型, 参数2: 数据类型, ...):
	执行语句
	return xxx

6.2 系统给的可用函数

对于系统提供的函数,我们可以通过Ctrl + Click这个函数名来跳转到函数的使用文档

6.2.1 以下划线开头的函数

  • 那些开头带下划线_的函数是不由我们激活或者调用的,是由引擎自身所调用

6.2.2 随机数

  • 函数randf()获取0~1间的随机浮点数
1
2
3
4
5
6
func _ready():
	var random_value = ranf()
	if random_value < 0.2:
		print("rare item")
	else:
		print("general item")
  • 函数randi_range(min, max)来获取范围内的随机整数
1
var height = randi_range(150, 200)

6.2.3 Set()Get()函数

这两个函数能够使我们在变量改变或者变量被读取时添加执行代码,比如可以让我们能对被修改的变量被赋的值按我们的需求而不一定完全等于传入的值、对被读取的变量被读到的值不一定等于其真实值,而是读取到一个被加工过后的值,又比如在被修改或读取的同时传递出信号等

6.2.3.1 Set()函数
  • 对于Set()函数的使用,生命值的变化是一个很棒的例子,这基于我们需要将生命值限制在合法的范围区间内的需求
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 创建一个信号表示生命值被改变了,并将新的生命值作为参数传递
signal health_changed(new_health)

# 默认生命值为最大值100
# 在该变量定义完成后添加一个冒号,并设置一个setter
var health := 100:
	# set()函数括号内的值我们来命名,用于指代health被试图更改为的传入值
	set(vallue):
		# health被修改时,即接收到了传入值value后需要执行的语句
		# 将health赋值为对value的clamp,使得生命值最小不低于0,最大不超过100
		health = clamp(value, 0, 100)
		# 传递生命值变化的信号,并将当前生命值作为参数传递出去
		health_changed(health)

# 为了方便我们就将这个信号的链接处放在自身脚本上来监听信号的传递结果
func _on_health_changed(new_health):
	print(new_health)

func _ready():
	# 给health进行赋值,这将会触发setter部分的函数
	health = -10

# 得到的输出结果为0,因为-10超出了clamp限制的范围,则自动取限制范围的最小值
6.2.3.2 Get()函数
  • 对于Get()函数的使用,以百分比(如暴击率、闪避率)意义存储的变量的读取是一个很棒的例子,我们可以让存储的xx率为浮点型的值,被读取时乘上100而读取整型类型的值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 某种几率,小数形式;此语境下chance变量身处幕后,不被直接读写
var chance := 0.2
# 这种几率的百分号前的整数部分;此语境下chance_pct变量才是真正被读写的对象
var chance_pct: int:
	# 该变量被读取时执行的语句,最终需要return一个真正被读取的值
	get:
		return chance*100
	# 该变量被修改时也要同步修改chance
	set(value):
		# 为了保证chance为浮点数,记得类型转换,以及除数也需要是浮点数
		chance = float(value) / 100.0

func _ready():
	# 此处只是一个该变量被读取的其中一种形式,其他的形式还可能为被调用去为别的变量赋值等
	print(chance_pct)
	chance_pct = 60
	print(chance)

# 20
# 0.6

七、类与对象

7.1 节点类的创建

  • 我们新建一个名为PlayerNode类型的节点,为之创建一个脚本,然后我们就可以以构筑类的思想来构筑这个脚本,每个脚本所在的节点即为该类实例化的对象(从这个角度看,带脚本的节点就像Unity中带脚本的GameObject,而Unity脚本的编写就是一个类的编写,这里也类似)
  • 我们在创建一个所谓Player类的时候,本质上是在定义一个新的节点类型
1
2
3
4
5
6
7
8
9
10
11
12
# 类名称
class_name Player
extends Node

# 成员变量,可以通过该节点的引用来调用
@export var profession: String # 玩家职业,在编辑器中手动设置
@export var health: int # 玩家生命值

# 成员函数,可以通过该节点的引用来调用
func die():
	health = 0
	print(profession + " died")

7.2 内部类的创建

  • 我们在节点的内部还可以创建内部类(并不一定是父子关系哦),如玩家的装备类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 类名称
class_name Player
extends Node

# Player类的成员变量中可以使用new()函数实例化装备类的一个对象
var chest := Equipment.new() # 胸甲
var legs := Equipment.new() # 腿甲

# 我们可以访问这些变量所属类的一些属性
func _ready():
	chest.armor = 20 # 更改胸甲护甲值

# 创建一个玩家装备的内部类
class Equipment:
	var weight := 5 # 装备重量
	var armor := 10 # 装备护甲值

7.3 继承

  • 我们所有节点类都是基类Node的子类,我们通过extends来声明了这个继承关系,意味着我们在节点类中可以调用Node类中的变量和函数
1
extends Node
  • 在我们创建一个Godot提供的类型的节点的时候,他会自动继承自其正确的对应父类节点,即在脚本中以extends base_class的方式继承;我们撰写脚本的时候需要注意处理好这些继承关系
本文由作者按照 CC BY-NC-SA 4.0 进行授权