一、脚本结构
1.1 关于GDScript
- GDScript是专门为了Godot进行游戏开发使用的面向对象的编程语言
- GDScript和Python的语法很相似
1.2 GDScript的脚本结构
- 我们在新建项目中创建一个名为
Main
的Node
类型节点后,为这个节点添加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 变量的定义与修改
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
|
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
|
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 常量的定义
三、输入与输出
3.1 信息输出
1
| print("It's MyGO!!!!!")
|
3.2 按键输入
- 在
Project Settings
面板中的Input Map
处设置按键映射,我们新建一个例如名为my_action
的动作,并将空格键Space
绑定到该动作下
- 接下来我们在父节点
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 数组的创建
- 在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 字典的创建
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 数组的循环遍历
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++中的
switch
,case
和default
语句,依据值是否匹配来有选择地执行语句 ```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
|
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
循环
5.3.3 break
与continue
break
关键字结束循环,执行循环后的语句continue
结束当前一层循环,进入下一层循环,并非结束循环
六、函数
6.1 定义格式
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 随机数
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 节点类的创建
- 我们新建一个名为
Player
的Node
类型的节点,为之创建一个脚本,然后我们就可以以构筑类的思想来构筑这个脚本,每个脚本所在的节点即为该类实例化的对象(从这个角度看,带脚本的节点就像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
类中的变量和函数
- 在我们创建一个Godot提供的类型的节点的时候,他会自动继承自其正确的对应父类节点,即在脚本中以
extends base_class
的方式继承;我们撰写脚本的时候需要注意处理好这些继承关系