Skip to content

基本数据类型

基本数据类型是GDScript中最基础最简单的变体类型,也是其他更复杂变体类型的基石。它们通常表示一些基础的值,比如数字、字符串、布尔值等等。

这些类型都有一个共同特点:它们都分配在栈上,并按值传递——也就是说,当你把它赋值给另一个变量,或者作为参数传递给函数时,都会复制一份原始的值,不会互相影响

类型说明示例可隐式转换至
null空值,表示“无”、“未赋值”或“错误”nullObject及其子类
bool布尔值,表示真或假true / falseint, float
int64位有符号整数1000114514999float, bool
float64位双精浮点数3.147.62, 100.899int, bool
String可变字符串"hello world"StringName,NodePath
StringName不可变字符串,性能更优于可变字符串,非常适合在字典中作为键名使用&"Immutable String"String
NodePath节点或节点属性的路径^"../Child/NodeName"String

null

null表示空数据类型,不包含任何信息。

null的赋值规则如下

  • 可以把null赋值给没有指定类型的变量(也就是动态类型变量)

  • 可以赋值给引用类型(Reference Types)的变量(例如Node或其他继承自Object的类型)

  • 不能赋值给静态的值类型(Value Types)和集合类型(Collection Types)变量(例如intboolArray等等)

在GDScript中,除了集合类型,其他内置变体类型都是值类型

派生自Object的类型及其本身,都是可空引用类型(Nullable Reference Types)可以赋值为null,代表“没有引用任何对象”

而引用类型始终按引用传递,传递的是内存地址而不是值,不会复制一份新的对象

gdscript
var my_variant = 10
var my_node: Node
var my_integer: int

func _ready():
    my_variant = null # 允许,因为变量不是静态类型变量
    my_node = null # 允许,因为变量是引用类型变量
    my_integer = null # 解析错误,`null`类型的值无法赋值给`int`类型的变量,因为`int`为值类型

bool

布尔类型(boolean)只有两个值true(真)或false(假)

默认值为false

这里的truefalse是大小写敏感的,不能写作TRUE,False等形式

你可以把它理解为是一个开关

  • 当值是true的时候,表示这个开关是开启状态,

  • 当值是false的时候,它是关闭状态

除此之外,布尔类型常常用于表示ifwhile语句的判断条件,构建复杂的条件表达式

bool可以隐式转换为intfloat

  • 当值为true时,转换后的结果为1/1.0

  • 当值为false时,转换后的结果为0/0.0


int

带符号64为整数类型,取值范围为 -2^632^63 - 1,也就是-9,223,372,036,854,775,8089,223,372,036,854,775,807。超过这个范围时,会发生数据溢出,值会绕回另一端

默认值为0

gdscript
var max_int = 9223372036854775807 # int 所能存储的最大值
max_int += 1 # max_int 现在是 -9223372036854775808,因为它绕到了另一端

int可以隐式转换为float类型,转换后的值会尽可能与原始整数相近,但超过浮点数精度上限的整数会发生精度丢失

gdscript
var small: int = 1000
var big: int = 9223372036854775807

func _ready():
    var floating: float = big
    print(floating) # 输出9223372036854775800.0
    var floating_2: float = small
    print(floating_2) # 输出1000.0

在上面的例子中你可以看到,当int的值极大的时候,转换为float后会丢失一部分数据,只有17位数字

这是因为 64 位浮点数(float)的有效数字精度大约为 15~16 位,超出部分会被截断为 0。

int也能隐式转换为bool类型。

  • 0的整数转换为bool后值都是true

  • 0转换为bool的结果为false

除了十进制赋值,int类型也支持二进制和十六进制赋值,也支持大数分位

gdscript
var x = 0b1001 # x 为 9
var y = 0xF5 # y 为 245
var z = 10_000_000 # z 为 10000000
  • 0b前缀表示这个数是一个二进制数(文件数据0-255)

  • 0x前缀表示这个数是一个十六进制数(颜色表达)

  • 可以使用_下划线来分割数字的数位,使其更具可读性


float

64位双精浮点数,表示实数。最大值约为 1.79769e308,最小值约为 -1.79769e308

它拥有14位可靠的十进制小数位精度

默认值为0.0

当浮点数的数值过大或变动太小,就有可能发生精度丢失或变动被忽略的情况:

gdscript
var my_float = 1234567890123456789.0

func _ready() -> void:
    print(my_float) # 因为丢失精度,输出1234567890123456768.0
    my_float += 1
    print(my_float) # 因为改动的值过小而被忽略,输出1234567890123456768.0

浮点数在参与数学运算时几乎总会产生微小误差,即使两个结果看起来“肉眼一样”,但程序判断时可能会认为它们不相等:

gdscript
var a = 0.1 + 0.2
var b = 0.3
var c = 0.1 + 0.2 - 0.3

func _ready():  
    print(a)         # 输出: 0.3(但其实有误差)
    print(b)         # 输出: 0.3
    print(c)         # 输出:0.0(但其实有误差)
    print(a == b)    # 输出: false(实际数值略有偏差)
    print(c == 0)    # 输出:false(实际数值略有偏差)

在这种情况下,我们需要使用GDScript内部的数学函数进行比较

gdscript
print(is_equal_approx(a, b)) # 判断是否近似相等
print(is_zero_approx(c))    # 判断是否与0近似相等

所以在涉及小数运算的时候,应使用上面两个方法判断相等,不应该使用==

需要注意的是Vector2(2维向量)和Vector3(3维向量)以及Vector4(4维向量)这三种数据类型内部所使用的浮点数默认是32位单精度浮点数,与float相比,有效数位和取值范围要小,大约7位可靠数据

gdscript
var my_vector

func _ready() -> void:
    my_vector = Vector2(123456789012345, 123456789012345)
    print(my_vector) # 输出(123456788103168.0, 123456788103168.0)

除了完整数位赋值,float还支持科学计数法赋值

gdscript
var a: float = 2.1e9 # 21亿(2.1 * 10^9)

func _ready():
    print(a) # 输出2100000000.0

float可以隐式转换为int,但是具有副作用,它会截断小数位直接取整:

gdscript
var a = 3.14
var b: int = a

func _ready():
    print(b) # 输出3

float可以隐式转换为bool,与int的规则相同

  • 0.0false

  • 0.0true(即使它几乎等于 0,也仍会被视为 true

String

可变字符串,可以包含任意数量的Unicode字符,有引用计数,使用写时复制技术(每次对字符串的修改都会返回新的 String),所以传递字符串的资源损耗很小。

默认值为空字符串("")

字符串必须用引号包起来,支持单引号 ' 或双引号 ",也支持三引号 """ 表示多行字符串或省去转义。

可以通过索引来访问和修改某个字符,索引从0开始,表示第一个字符,如果索引为负则从末尾开始获取

gdscript
var a = "Hello World!"

func _ready():
    print(a[0]) # 输出 H
    a[0] = "W"
    print(a) # 输出 Wello World!

字符串中也存在一些具有特殊用途的字符,我们称之为转义符:

转义符转义为
\n换行(line feed,LF)
\t水平制表符(tab)
\r回车(carriage return,CR)
\a警报(蜂鸣/响铃)
\b退格键(Backspace)
\f换页符(form feed,FF)
\v垂直制表符(tab)
\"双引号
\'单引号
\\反斜杠
\uXXXXUnicode UTF-16 码位 XXXX (16进制,不区分大小写)
\UXXXXXXUnicode UTF-32 码位 XXXXXX (16进制,不区分大小写)

想要在字符串当中插入引号或反斜杠,这时就需要用到转义符

gdscript
print("char"char") # 错误
print("char\"char") # 输出 char"char

赋值时也可以使用三引号,此时如果想在字符串中赋值连续三个以下的引号(字符串闭合)就不需要转义

gdscript
var a = """L""ine1Line2Line3""" # 连续的引号三个以下,不需要转义
var b = """L""\"ine1Line2Line3""" # 第三个连续的引号需要转义
var c = """L""\"""\"ine1Line2Line3""" # 第二次的第三个连续的引号需要转义

在三引号的情况下,因为字符串闭合字符也是三个连续的引号,所以每逢三个连续的引号就需要转义一个

当你使用双引号作为字符串闭合字符时,就不需要转义单引号,反之亦然

赋值时可以换行,其值也会包含换行符

gdscript
var a = "Line1
Line2
Line3"

var b = "Line1\nLine2\nLine3" # 等效于变量a

func _ready():
    print(a) # 将会输出三行文本
    print(b) # 与a的结果相同

赋值时如果在引号前加一个小写的字母r,则会将常规字符串变为原始字符串(Raw String)字面量

同时也会使得转义符无效化,这个特性在正则表达式和路径中尤其实用!

gdscript
var a = r"Line1
Line2
Line3"

var b = r"Line1\nLine2\nLine3"

func _ready():
    print(a) # 将会输出三行文本
    print(b) # 只输出单行文本,换行符`\n`被无效化

GDScript也支持格式化字符串(变量插值),以下是一些最常用的做法示例

gdscript
var player = "玩家"
var format_string = "正在等待%s"
var actual_string = format_string % player
print(actual_string) # 输出“正在等待玩家”
print("正在等待%s" % player) # 等效语法
gdscript
var hp = 100
var player = "玩家"
print("%s当前生命为:%s" % [player, hp]) # 输出“玩家当前生命为:100”
print("%s当前生命为:%s" % ["玩家", 100]) # 也可以用常量
gdscript
var pi = 3.1415926

print("pi:%.2f" % pi) # 格式化为两位小数,输出“pi:3.14”

注意:String在布尔语境下,即参与条件判断时,空字符串("")的结果为false,否则其结果始终为true

gdscript
var a = ""
if a:
    print("非空")  # 不会执行

String可以与StringNameNodePath之间进行互相转换

StringName

不可变字符串、唯一字符串

默认值为空StringName(&"") 一旦创建就不能更改,相同值的StringName被认为是同一个实例,相比于String性能更佳,非常适合作为字典的键名使用

gdscript
var str = &"Hello World!"
str[0] = "W" # 错误,无法在"StringName"类型的基础上使用下标运算符
print(str[0]) # 错误,无法在"StringName"类型的基础上使用下标运算符

赋值StringName时需要在引号前加上&,否则会被视为是String

gdscript
var a = "StringName"
var b = &"StringName"
print(type_string(typeof(a))) # 输出String
print(type_string(typeof(b))) # 输出StringName

不过StringStringName之间可以互相转换,StringName也可以调用String所拥有的方法,返回的也是String,但是效率非常低,应该只在需要字符串时使用

注意:在布尔语境下,空的StringName (StringName(""))为false,其他均为true

gdscript
var a = &""
if a:
    print("非空")  # 不会执行

NodePath

节点路径,一种特殊的字符串类型,用来表示 节点树中某个节点或属性的路径

默认值为空NodePath(^"")

它经常被用于 get_node()set_indexed()tween_property() 等方法中

节点路径无法检查自身的有效性,可能指向不存在的节点或属性。具体含义完全由使用场合决定。

赋值NodePath时需要在引号前加上^,否则会被当作普通的字符串对待:

gdscript
var path = ^"Player/Camera"
  • 斜杠(/)分割节点名称,表示层级关系

  • 英文冒号(:)分割属性名称,可以指定访问某个属性或属性的某个子值

  • ".."指向父节点

  • "."指向当前节点

以下示例是相对于当前节点的路径(相对路径)

gdscript
^"A"     # 指向直接子节点 A。
^"A/B"   # 指向 A 的子节点 B。
^"."     # 指向当前节点。
^".."    # 指向父节点。
^"../C"  # 指向同级节点 C。
^"../.." # 指向祖父节点(父节点的父节点)。

以斜杠开头的路径是绝对路径(从 /root 开始):

gdscript
^"/root"            # 指向 SceneTree 的根 Window。
^"/root/Title"      # 可能指向主场景的根节点,名叫“Title”。
^"/root/Global"     # 可能指向名叫“Global”的自动加载节点或场景。

虽然名字里说的是“节点”,但是节点路径也可以指向属性:

gdscript
^":position"           # 指向该对象的位置。
^":position:x"         # 指向该对象在 X 轴的位置。
^"Camera3D:rotation:y" # 指向 Camera3D 子节点及其 Y 轴旋转。
^"/root:size:x"        # 指向根 Window 及其宽度。

在某些情况下,指向对象属性时可以省略前导:

例如,Object.set_indexed()Tween.tween_property()

但是,为了可读和明确性通常建议保留: 前缀。

StringNodePath之间可以互相转换,String在必要时会自动转换为NodePath

注意:在布尔环境中,NodePath 为空时取值为 falseNodePath(""))。否则 NodePath始终为 true

gdscript
var a = ^""
if a:
    print("非空")  # 不会执行

小练习

  1. 下面的值在布尔语境下是不是true,说说你认为的答案和原因

    • A. " "

    • B. 0

    • C. 0.00001

    • D. ^""

    • E. &""

  2. 下面代码的输出是什么?

    gdscript
    var a = 3.14
    var b: int = a
    print(b)
    
    var c = "Hello"
    c[0] = "Y"
    print(c)
    
    var d = 9223372036854775807
    d += 1
    print(d)
    
    var e = "Line1\nLine2\nLine3"
    print(e)
  3. 代码补全,使得它输出“正在等待玩家”以及“玩家当前生命为:100”

    gdscript
    var name = _________
    var hp = 100
    print("_________" % name)
    print("%s当前生命为:%s" % [______, ____])
  4. 捉虫(找BUG):

    gdscript
    var x = 3.1415
    var y: int = "100"
    print(x + y)