初面网初面网

GDScript

GDScript是godot引擎官方推出的语言,旨在能够与引擎更加契合。

在引擎中创建脚本

点击某个节点后,点击下图所示的按钮,即可创建脚本。

创建脚本

创建脚本1

或者直接在资源浏览器中创建

创建脚本2

变量(标识符)

推荐以字母、数字、下划线的组合字符串作为变量名。

不能以数字开头,且大小写敏感。

尽管可以用一些Unicode字符,但是不建议。

关键字

那些不能被作为标识符的被称为关键字。下面是其中的一些:

if elif else for while
match break continue pass return
class class_name extends is in
as self signal func static
const enum var breakpoint preload
await yield assert void PI
TAU INF NAN

模板输出

模板输出是指在字符串中插入变量的值。例如:

var name = "张三"
var age = 18
print("%s今年%d岁" % [name, age])

数据类型

在 GDScript 中,数据类型可以归纳为以下几类:

一、基础数据类型(值类型)

类型说明示例
null空值,表示没有值var a = null
bool布尔值,true 或 falsevar flag = true
int整数(有符号 64 位)var score = 100
float浮点数(小数)var speed = 3.5
String字符串,Unicode 文本var name = "Player"

二、向量与数学类型

类型说明
Vector2 / Vector2i2D 向量,含 x、y 字段
Vector33D 向量,含 x、y、z 字段
Rect22D 矩形,含 pos、size 字段
Transform2D2D 变换矩阵(3×2)
Plane3D 平面(标准化形式)
Quat / Quaternion四元数,用于 3D 旋转
AABB轴对齐包围盒(3D)
Basis3×3 矩阵,用于 3D 旋转和缩放
Transform3D3D 变换(Basis + origin)

三、引擎专用类型

类型说明
Color颜色,含 r、g、b、a 字段,也可访问 h、s、v
NodePath节点的编译路径
RID资源 ID,引用不可见数据
Object所有对象的基类

四、容器类型

类型说明
Array动态数组,元素类型可不同,索引从 0 开始
Dictionary字典,键值对容器,键可以是任意类型

五、类型化数组(专用数组)

类型说明
PackedByteArray字节数组(0-255)
PackedInt32Array / PackedInt64Array整数数组
PackedFloat32Array / PackedFloat64Array浮点数数组
PackedStringArray字符串数组
PackedVector2ArrayVector2 数组
PackedVector3ArrayVector3 数组
PackedColorArrayColor 数组

字面量

例子描述
null空值
false, true布尔值
45十进制整数
0x8f51十六进制整数
0b101010二进制整数
3.14, 58.1e-10浮点数(实数)
"Hello", "Hi"常规字符串
"""Hello""", '''Hi'''常规字符串(用三对引号括住)
r"Hello", r'Hi'原始字符串
r"""Hello""", r'''Hi'''原始字符串(用三对引号括住)
&"name"StringName
^"Node/Label"NodePath

注释

使用井号 # 对某一行进行注释。

行间语句接续

当代码太长,一行写不完,或者一行里写完影响美观时,可以换行续写。

var a = 1 + \
2 + \
3

密存数组

当需要存储大量数据时,普通数组对内存的伤害就很大。使用密存数组此时成为了非常好的选择。

类型说明
PackedByteArray字节(从0到255的整数)数组
PackedInt32Array32位整数数组
PackedInt64Array64位整数数组
PackedFloat32Array32位浮点数数组
PackedFloat64Array64位浮点数数组
PackedStringArray字符串数组
PackedVector2ArrayVector2类型的数组
PackedVector3ArrayVector3类型的数组
PackedColorArrayColor类型的数组

数组

数组里只能存放相同类型的元素。

var fruits = ["apple", "banana", "orange"]
# 下标访问
print(fruits[0])

# 增删改
fruits.append("peach")
print(fruits)
fruits.remove("banana")
print(fruits)
fruits[0] = "peach"
print(fruits)

# 插入、弹出元素
fruits.insert(0, "apple")
print(fruits)
fruits.pop_front()
print(fruits)
fruits.pop_back() # 弹出最后一个元素
print(fruits)

# 数组长度
print(fruits.size())
# 是否包含某个元素
print(fruits.has("peach"))
# 获取索引
print(fruits.find("peach"))

# 遍历数组
for fruit in fruits:
    print(fruit)

请注意,数组在godot中是引用传递的,所以如果在函数中修改了数组,那么在函数外部也会被修改。如果不想修改数组,可以使用数组的副本。

副本:

var fruits_copy = fruits.duplicate()
print(fruits_copy)

字典

可以存储不一样的值的数据。可以有两种创建风格

字符串为键的风格:

var d = {
  22: "value",
  "key": 2
}

Lua风格:

var d = {
  test22 = "value",
  some_key = 2
}

访问字典有两种方式:

# 假设有字典dict
print(dict[22])
print(dict["key"])
print(dict.test22)

可以像访问键一样添加值

var d = {}
d.waiting = 14
d[1] = "First"
var firstKey = 1
print(d[firstKey])

删除键值对:

d.erase(22)
print(d)

查:

print(d.has(22))
print(d.size())

遍历:

# 遍历键
for key in d.keys():
    print(dict[key])

# 遍历值
for value in d.values():
    print(value)

# 直接遍历,等价于键遍历
for key in dict:
    print(dict[key])

理论上可以用方括号读取任何 Object 的属性。但是,属性不存在的时候,读取会报错 所以更建议使用 Object.get() 和 Object.set()方法 字典也是引用类型,所以如果在函数中修改了字典,那么在函数外部也会被修改

字符串

这里的字符串指的是String类型。

var str = "Hello"

print(str.length()) # 长度
print(str[0]) # 第一个字符
print(str.substr(1, 3)) # 第二个到第三个字符

print(str.to_upper()) # 转换为大写
print(str.to_lower()) # 转换为小写

print(str.contains("l")) # 是否包含某个字符
print(str.begin_with("l")) # 是否以某个字符开头
print(str.end_with("o")) # 是否以某个字符结尾

print(str.replace("l", "L")) # 替换字符。仅替换第一个匹配项。

# 拼接字符串
print("Hello" + " World")

# 另一种拼接
print("、".join(["b", "c"])) # b、c

# 拆分字符串
print(str.split("l"))

# 去除空格
print(" Hello  ".strip_edges()) # Hello

# 转数字
print("42".to_int()) # 42
print("42.0".to_float()) # 42.0

静态成员

静态成员用static来标识。它们是直属于类的。由于直属于类,所以可以通过类名直接调用。

注意,由于直属于类,所以是全局同一的,也就是多处使用的时候一定要注意,不可以随意乱改动其值

枚举

一组常量,为某些常量连续分配整数时非常有用。

enum {TILE_BRICK, TILE_FLOOR, TILE_SPIKE, TILE_TELEPORT}

# Is the same as:
const TILE_BRICK = 0
const TILE_FLOOR = 1
const TILE_SPIKE = 2
const TILE_TELEPORT = 3

func _ready():
	# Access values with Name.KEY, prints '5'
	print(State.STATE_JUMP)
	# Use dictionary methods:
	# prints '["STATE_IDLE", "STATE_JUMP", "STATE_SHOOT"]'
	print(State.keys())
	# prints '{ "STATE_IDLE": 0, "STATE_JUMP": 5, "STATE_SHOOT": 6 }'
	print(State)
	# prints '[0, 5, 6]'
	print(State.values())

流程控制

流程控制,包括判断和循环。

判断:if/elif/else,match

循环:for,while

var a = 10
var b = 0
if 1 < a:
    print("小")
elif 8 == a:
    print("等于")
else 9 > a:
    print("大于")

while b < a:
    b += 1
    print(b)

for x in [1,2,3]:
    print(x)

var toMach = 50

match toMach:
    50:
        print("不及格")
    60:
        print("及格")

函数

使用func关键字来声明函数

func onGetName:
    # 空的内容可以用pass占位
    pass

func onGetAge -> int:
    return 18

匿名函数

var lambda = func(x): print(x)
lambda.call(42)

静态函数

静态函数不能访问实例成员变量,也不能使用 self,非常适用于创建辅助函数库。

static func sum2(a, b):
	return a + b

面向对象的思想中,类是一个非常重要的概念。

举例来说,当我们谈到"蛋"的时候,一般就会想到坚硬外壳,近似圆形,能够孕育生命的这种物质。

而当说到"鸡蛋"的时候,形象与描述会更加具象化,会在之前的基础上再附加一些描述,例如椭圆形,通常来说只能孵出鸡,外壳是橙黄色。

所以,当我们说"蛋类"的时候,会想到更宽泛的描述,这也是编程世界中"类"这一概念所想要达到的目的。

在godot中,所有脚本文件都可以视作类。此时可以用文件路径引入。例如,假设有脚本character.gd:

# 扩展自character.gd

extends "res://path/to/character.gd"

# 加载并新建一个节点.

var Character = load("res://path/to/character.gd")
var character_node = Character.new() # 实例化

可以使用class_name关键字为类起名,还可以配合@icon来定制类图标。

图标使用svg格式的比较好,不会在缩放过程中失真

# item.gd

@icon("res://interface/icons/item.png")
class_name Item
extends Node

继承

想继承自某个类可以这样写:

class_name MyNode extends Node

var health = 5

func _ready():
    print(health)

类可以继承自全局类、另一个类文件、另一个类文件中的内部类。

不允许多继承。

使用is判断是否继承自给定类

# Cache the enemy class.
const Enemy = preload("enemy.gd")

# Use 'is' to check inheritance.
if entity is Enemy:
	entity.apply_damage()

使用super关键字调用父类中的方法。

# 父类中有some_func方法,使用下面这种写法调用父类的some_func
func some_func(x):
	super(x)

# 父类中有overriding方法
func dont_override():
	return super.overriding()

构造函数

使用_init关键字作为构造函数的标识。父类构造函数带参,则子类也必须定义构造函数并适当传参。

func _init(arg):
    super("some_default", arg)

静态构造函数,在类载入,静态类成员变量初始化后自动调用。

static var my_static_var = 1

static func _static_init():
	my_static_var = 2

内部类用class关键字定义,用 类名.new() 函数进行实例化

as 和 is

子类的功能通常比父类更多。当你平时调用的是父类,但是某个时候需要调用子类的方法时,需要使用as关键字。

var entity: Node = $SomeNode

if entity is CharacterBody2D:
    # 先用 is 检查
    var character: CharacterBody2D = entity as CharacterBody2D
    character.move_and_slide()

输入

输入使用的是Input类。以事件驱动,需要添加事件。

添加动作

添加操作按键

按键确认

extends RigidBody2D

func _physics_process(delta: float) -> void:
	if Input.is_action_pressed("move_right"):
		apply_force(Vector2(25,0))

获得场景节点

get_tree

get_tree().reload_current_scene() 可以重新加载当前场景

信号与连接

信号可以视作由事件触发的动作。当事件发生时会发送信号。需要与某些函数绑定。

信号可以使用signal关键字自定义,使用connect关键字连接,使用emit关键字触发

extends Node

# 可带参可不带参
# signal health_changed
signal health_changed(old_value, new_value)

# 在game.gd中
func _ready():
	var character_node = get_node('Character')
	character_node.health_depleted.connect(_on_character_health_depleted)

func _on_character_health_depleted():
	get_tree().reload_current_scene()

# character.gd

signal health_changed

func take_damage(amount):
	var old_health = health
	health -= amount

	# We emit the health_changed signal every time the
	# character takes damage.
	health_changed.emit(old_health, health)

等待或开启协程

使用await关键字创建协程。会等待信号或协程函数的完成。

例如:

func wait_confirmation():
	print("Prompting user")
	await $Button.button_up # 等待按钮的抬起事件
	print("User confirmed")
	return true

# 上面的 wait_confimation() 中使用了await,所以它变成了协程函数
# 在其他调用它的地方,也需要使用await,否则会报错
# 下面是调用示例
func request_confirmation():
	print("Will ask the user")
	var confirmed = await wait_confirmation()
	if confirmed:
		print("User confirmed")
	else:
		print("User cancelled")

对非信号和非协程函数使用await跟没写await一样

如果函数返回的是信号,那么对这个函数使用await时,会等待信号发生。例如:

func get_signal():
	return $Button.button_up

func wait_button():
	await get_signal()
    # 等到按钮按下才会执行后面的打印语句
	print("Button was pressed")

assert断言

断言在调试模式下会中断运行。

绝对不要在发布的版本中留有断言!!!

assert(life > 0, "Player failed!" )

一些规范

  • 文件名、函数、变量要小写,单词之间使用下划线分隔。例如:find_obj.gd
  • 类和节点要使用大驼峰命名(首字母大写,其后的单词首字母也大写)。例如:OnGetUserAge。
  • 信号要用过去时态命名
  • 常量和枚举的成员名均使用大写,下划线分隔单词。

代码顺序遵循如下规则:

1、先写信号和属性,然后再写方法。 2、先写公共成员,然后再写私有成员。 3、先写虚函数回调,然后再写类的接口。 4、先写对象的构造函数和初始化函数 _init 和 _ready ,然后再写修改对象的函数。

更新于 2026/7/26