第六章:变量与数据类型
"数据是程序的灵魂,理解数据类型是编程的基础。"
在上一章中,我们了解了GDScript的基础语法。本章将深入探讨GDScript的变量系统和丰富的数据类型,这是编写任何程序的基础。
6.1 变量声明
6.1.1 使用var声明变量
在GDScript中,使用 var 关键字声明变量:
# 基本声明
var health
var player_name
var score
# 声明并赋值
var health = 100
var player_name = "Hero"
var score = 0
6.1.2 类型推断
GDScript支持类型推断,使用 := 运算符:
# 显式类型声明
var health: int = 100
var name: String = "Player"
var position: Vector2 = Vector2(0, 0)
# 类型推断(推荐)
var health := 100 # 推断为int
var name := "Player" # 推断为String
var position := Vector2(0, 0) # 推断为Vector2
6.1.3 静态类型 vs 动态类型
# 动态类型(可以改变类型)
var value = 10
value = "Hello" # 允许,但不推荐
value = [1, 2, 3] # 也允许
# 静态类型(不能改变类型)
var value: int = 10
value = "Hello" # 错误!类型不匹配
为什么推荐静态类型?
- 编译时错误检查
- 更好的自动补全
- 代码更清晰
- 潜在的性能提升
6.1.4 变量初始值
未初始化的变量默认为 null:
var uninitialized # 值为null
# 各类型的默认值(当显式声明类型时)
var my_int: int # 0
var my_float: float # 0.0
var my_string: String # ""
var my_bool: bool # false
var my_array: Array # []
var my_dict: Dictionary # {}
var my_vector: Vector2 # Vector2(0, 0)
6.2 基本数据类型
6.2.1 布尔型(bool)
只有两个值:true 和 false
var is_alive: bool = true
var is_jumping: bool = false
var has_key := true
# 布尔运算
var can_attack = is_alive and not is_jumping
var should_respawn = not is_alive or health <= 0
# 类型转换
var from_int = bool(1) # true(非0为true)
var from_string = bool("") # false(空字符串为false)
var from_array = bool([]) # false(空数组为false)
假值(Falsy):
false0(整数0)0.0(浮点数0)""(空字符串)[](空数组){}(空字典)null
6.2.2 整数型(int)
64位有符号整数:
var health: int = 100
var negative: int = -50
var big_number: int = 9223372036854775807 # 最大值
# 不同进制
var decimal = 42 # 十进制
var hex = 0xFF # 十六进制 = 255
var binary = 0b1010 # 二进制 = 10
var octal = 0o77 # 八进制 = 63
# 可读性分隔符
var million = 1_000_000
var hex_color = 0xFF_AA_BB
# 常用操作
var a = 17
var b = 5
print(a / b) # 3(整数除法结果为3.4,但打印3.4)
print(a % b) # 2(取模)
print(a ** b) # 1419857(幂运算)
# 类型转换
var from_float = int(3.7) # 3(截断)
var from_string = int("42") # 42
var from_bool = int(true) # 1
6.2.3 浮点型(float)
64位双精度浮点数:
var speed: float = 100.5
var gravity: float = 9.8
var tiny: float = 0.001
# 科学计数法
var large = 1.5e10 # 15000000000
var small = 1.5e-10 # 0.00000000015
# 特殊值
var infinity = INF # 正无穷
var neg_inf = -INF # 负无穷
var not_a_number = NAN # 非数字
# 检查特殊值
print(is_inf(infinity)) # true
print(is_nan(not_a_number)) # true
# 浮点数比较(注意精度问题)
var a = 0.1 + 0.2
var b = 0.3
print(a == b) # 可能是false!
# 安全比较
print(is_equal_approx(a, b)) # true
print(absf(a - b) < 0.0001) # true
6.2.4 字符串型(String)
UTF-8编码的字符串:
# 声明方式
var single = 'Hello'
var double = "World"
var multi = """
这是一个
多行字符串
"""
# 转义字符
var newline = "Line1\nLine2"
var tab = "Col1\tCol2"
var quote = "He said \"Hello\""
var backslash = "Path\\to\\file"
# 原始字符串
var raw = r"C:\Users\name" # 不处理转义
# 字符串操作
var name = "Godot"
print(name.length()) # 5
print(name.to_upper()) # GODOT
print(name.to_lower()) # godot
print(name[0]) # G
print(name.substr(0, 3)) # God
# 字符串连接
var greeting = "Hello" + " " + "World"
var repeated = "ab" * 3 # ababab
# 字符串格式化
var template = "Name: %s, Score: %d"
var result = template % ["Player", 100]
# 字符串搜索
var text = "Hello World"
print(text.find("World")) # 6
print(text.contains("Hello")) # true
print(text.begins_with("He")) # true
print(text.ends_with("ld")) # true
# 字符串分割
var csv = "a,b,c,d"
var parts = csv.split(",") # ["a", "b", "c", "d"]
# 字符串替换
var replaced = text.replace("World", "Godot") # "Hello Godot"
6.3 复合数据类型
6.3.1 数组(Array)
动态大小的有序集合:
# 创建数组
var empty: Array = []
var numbers = [1, 2, 3, 4, 5]
var mixed = [1, "two", 3.0, true, null]
# 类型化数组(Godot 4)
var int_array: Array[int] = [1, 2, 3]
var string_array: Array[String] = ["a", "b", "c"]
# 访问元素
print(numbers[0]) # 1(第一个)
print(numbers[-1]) # 5(最后一个)
print(numbers[1:3]) # [2, 3](切片)
# 修改元素
numbers[0] = 10
numbers[-1] = 50
# 添加元素
numbers.append(6) # 末尾添加
numbers.push_back(7) # 同上
numbers.push_front(0) # 开头添加
numbers.insert(2, 100) # 指定位置插入
# 删除元素
numbers.pop_back() # 删除并返回最后一个
numbers.pop_front() # 删除并返回第一个
numbers.remove_at(0) # 删除指定索引
numbers.erase(100) # 删除指定值(第一个匹配)
numbers.clear() # 清空数组
# 查找元素
var idx = numbers.find(3) # 返回索引,未找到返回-1
var has = numbers.has(3) # 返回bool
# 数组操作
var arr = [3, 1, 4, 1, 5]
arr.sort() # 排序:[1, 1, 3, 4, 5]
arr.reverse() # 反转
arr.shuffle() # 随机打乱
var size = arr.size() # 长度
# 数组遍历
for item in numbers:
print(item)
for i in range(numbers.size()):
print(numbers[i])
# 函数式操作
var doubled = numbers.map(func(x): return x * 2)
var evens = numbers.filter(func(x): return x % 2 == 0)
var sum = numbers.reduce(func(acc, x): return acc + x, 0)
6.3.2 字典(Dictionary)
键值对集合:
# 创建字典
var empty: Dictionary = {}
var player = {
"name": "Hero",
"health": 100,
"position": Vector2(0, 0)
}
# 另一种语法(键不需要引号)
var player2 = {
name = "Hero",
health = 100,
position = Vector2(0, 0)
}
# 访问值
print(player["name"]) # Hero
print(player.name) # Hero(如果键是有效标识符)
print(player.get("name")) # Hero
print(player.get("age", 0)) # 0(默认值)
# 修改值
player["health"] = 80
player.health = 80
# 添加键值对
player["mana"] = 50
player.level = 1
# 删除键值对
player.erase("mana")
# 检查键存在
if player.has("health"):
print("有生命值")
if "name" in player:
print("有名字")
# 获取所有键和值
var keys = player.keys() # ["name", "health", ...]
var values = player.values() # ["Hero", 100, ...]
# 遍历字典
for key in player:
print(key, ": ", player[key])
for key in player.keys():
print(key)
for value in player.values():
print(value)
# 合并字典
var extra = {"armor": 10, "weapon": "sword"}
player.merge(extra)
# 字典大小
print(player.size())
# 清空
player.clear()
6.3.3 PackedArray类型
优化的类型化数组,内存效率更高:
# 各种PackedArray类型
var bytes: PackedByteArray = PackedByteArray([1, 2, 3])
var ints: PackedInt32Array = PackedInt32Array([1, 2, 3])
var int64s: PackedInt64Array = PackedInt64Array([1, 2, 3])
var floats: PackedFloat32Array = PackedFloat32Array([1.0, 2.0])
var float64s: PackedFloat64Array = PackedFloat64Array([1.0, 2.0])
var strings: PackedStringArray = PackedStringArray(["a", "b"])
var vectors2: PackedVector2Array = PackedVector2Array([Vector2(1, 2)])
var vectors3: PackedVector3Array = PackedVector3Array([Vector3(1, 2, 3)])
var colors: PackedColorArray = PackedColorArray([Color.RED])
# 使用场景
# - PackedByteArray: 二进制数据、文件操作
# - PackedVector2Array: 多边形顶点、路径点
# - PackedColorArray: 渐变颜色
6.4 向量类型
向量是游戏开发中最常用的数据类型之一。
6.4.1 Vector2
2D向量,包含x和y分量:
# 创建Vector2
var pos := Vector2(100, 200)
var zero := Vector2.ZERO # (0, 0)
var one := Vector2.ONE # (1, 1)
var up := Vector2.UP # (0, -1)
var down := Vector2.DOWN # (0, 1)
var left := Vector2.LEFT # (-1, 0)
var right := Vector2.RIGHT # (1, 0)
# 访问分量
print(pos.x) # 100
print(pos.y) # 200
# 向量运算
var a := Vector2(3, 4)
var b := Vector2(1, 2)
print(a + b) # (4, 6)
print(a - b) # (2, 2)
print(a * 2) # (6, 8)
print(a * b) # (3, 8) 分量相乘
print(a / 2) # (1.5, 2)
# 常用方法
print(a.length()) # 5.0(长度/模)
print(a.length_squared()) # 25(长度平方,更快)
print(a.normalized()) # (0.6, 0.8)(单位向量)
print(a.dot(b)) # 11(点积)
print(a.cross(b)) # 2(叉积,2D返回标量)
print(a.angle()) # 弧度角度
print(a.angle_to(b)) # 到另一向量的角度
print(a.distance_to(b)) # 到另一点的距离
# 插值
var start := Vector2(0, 0)
var end := Vector2(100, 100)
var mid := start.lerp(end, 0.5) # (50, 50)
# 方向和距离
var direction := (end - start).normalized()
var distance := start.distance_to(end)
# 旋转
var rotated := a.rotated(PI / 2) # 旋转90度
# 限制
var clamped := a.limit_length(3) # 限制最大长度为3
6.4.2 Vector3
3D向量:
# 创建Vector3
var pos := Vector3(1, 2, 3)
var zero := Vector3.ZERO
var one := Vector3.ONE
var up := Vector3.UP # (0, 1, 0)
var down := Vector3.DOWN # (0, -1, 0)
var forward := Vector3.FORWARD # (0, 0, -1)
var back := Vector3.BACK # (0, 0, 1)
# 访问分量
print(pos.x, pos.y, pos.z)
# 3D特有操作
var a := Vector3(1, 0, 0)
var b := Vector3(0, 1, 0)
var cross := a.cross(b) # (0, 0, 1)(叉积返回向量)
# 投影
var projected := a.project(b) # a在b上的投影
6.4.3 Vector4和其他向量类型
# Vector4(四维向量)
var v4 := Vector4(1, 2, 3, 4)
# Vector2i/Vector3i/Vector4i(整数向量)
var grid_pos := Vector2i(10, 20)
var voxel := Vector3i(1, 2, 3)
# 类型转换
var float_vec := Vector2(grid_pos) # Vector2i → Vector2
var int_vec := Vector2i(pos) # Vector2 → Vector2i(截断)
6.5 其他内置类型
6.5.1 Color
颜色类型:
# 创建颜色
var red := Color.RED
var green := Color.GREEN
var blue := Color.BLUE
var white := Color.WHITE
var black := Color.BLACK
# RGBA构造(0-1范围)
var custom := Color(1.0, 0.5, 0.0, 1.0) # 橙色
# 从十六进制
var hex_color := Color("#FF5500")
var hex_with_alpha := Color("#FF550080")
# 从HTML名称
var named := Color("coral")
# 8位整数(0-255)
var from_8bit := Color8(255, 128, 0, 255)
# 访问分量
print(custom.r) # 红色分量
print(custom.g) # 绿色分量
print(custom.b) # 蓝色分量
print(custom.a) # Alpha分量
# 颜色操作
var lighter := custom.lightened(0.2)
var darker := custom.darkened(0.2)
var inverted := custom.inverted()
var blended := red.blend(blue)
var lerped := red.lerp(blue, 0.5) # 紫色
# 转换
print(custom.to_html()) # "ff8000"
print(custom.to_rgba32()) # 整数表示
6.5.2 Rect2
2D矩形:
# 创建矩形
var rect := Rect2(0, 0, 100, 50) # 位置(0,0),大小(100,50)
var rect2 := Rect2(Vector2(0, 0), Vector2(100, 50))
# 属性
print(rect.position) # Vector2(0, 0)
print(rect.size) # Vector2(100, 50)
print(rect.end) # Vector2(100, 50)(右下角)
# 方法
print(rect.has_point(Vector2(50, 25))) # true
print(rect.intersects(other_rect)) # 是否相交
var merged := rect.merge(other_rect) # 合并
var clipped := rect.intersection(other_rect) # 交集
var expanded := rect.expand(Vector2(150, 75)) # 扩展以包含点
var grown := rect.grow(10) # 各边扩大10
6.5.3 Transform2D和Transform3D
变换矩阵:
# Transform2D(2D变换)
var t2d := Transform2D()
t2d = t2d.translated(Vector2(100, 50)) # 平移
t2d = t2d.rotated(PI / 4) # 旋转45度
t2d = t2d.scaled(Vector2(2, 2)) # 缩放
# 应用变换
var point := Vector2(10, 10)
var transformed := t2d * point
# Transform3D(3D变换)
var t3d := Transform3D()
t3d = t3d.translated(Vector3(1, 2, 3))
t3d = t3d.rotated(Vector3.UP, PI / 4)
6.5.4 NodePath和StringName
# NodePath(节点路径)
var path: NodePath = ^"Player/Sprite2D"
var relative: NodePath = ^"../Enemy"
var absolute: NodePath = ^"/root/Main/Player"
# 使用节点路径
var node = get_node(path)
var node2 = $Player/Sprite2D # 简写形式
# StringName(字符串名称,用于快速比较)
var sname: StringName = &"player_died"
var signal_name := &"health_changed"
# StringName常用于信号和方法名
signal_name.emit()
6.6 常量与枚举
6.6.1 常量声明
使用 const 声明常量:
# 基本常量
const MAX_HEALTH: int = 100
const GRAVITY: float = 9.8
const PLAYER_NAME: String = "Hero"
# 常量必须在编译时确定
const PI_DOUBLED = PI * 2 # OK
const RANDOM = randi() # 错误!不能是运行时值
# 常量数组和字典
const DIRECTIONS = [Vector2.UP, Vector2.DOWN, Vector2.LEFT, Vector2.RIGHT]
const CONFIG = {
"debug": true,
"version": "1.0.0"
}
# 预加载常量
const PlayerScene = preload("res://scenes/player.tscn")
const EnemyScript = preload("res://scripts/enemy.gd")
6.6.2 枚举定义
# 简单枚举
enum { IDLE, WALK, RUN, JUMP }
# 命名枚举
enum State { IDLE, WALK, RUN, JUMP }
enum Direction { UP, DOWN, LEFT, RIGHT }
# 指定值
enum Priority {
LOW = 0,
MEDIUM = 5,
HIGH = 10,
CRITICAL = 100
}
# 使用枚举
var current_state: State = State.IDLE
func change_state(new_state: State) -> void:
current_state = new_state
# 枚举作为标志位
enum Flags {
NONE = 0,
INVINCIBLE = 1,
INVISIBLE = 2,
FLYING = 4,
ALL = 7
}
var player_flags: int = Flags.INVINCIBLE | Flags.FLYING
# 检查标志
if player_flags & Flags.INVINCIBLE:
print("玩家无敌中")
6.7 类型转换
6.7.1 显式转换
# 数值转换
var i: int = int(3.7) # 3(截断)
var f: float = float(42) # 42.0
var s: String = str(100) # "100"
var b: bool = bool(1) # true
# 字符串转数值
var num = int("42") # 42
var decimal = float("3.14") # 3.14
# 复杂类型转换
var arr = Array(packed_int_array)
var packed = PackedInt32Array(array)
6.7.2 类型检查
# typeof函数
var value = 42
print(typeof(value) == TYPE_INT) # true
# 类型常量
TYPE_NIL # null
TYPE_BOOL # bool
TYPE_INT # int
TYPE_FLOAT # float
TYPE_STRING # String
TYPE_VECTOR2 # Vector2
TYPE_VECTOR3 # Vector3
TYPE_ARRAY # Array
TYPE_DICTIONARY # Dictionary
TYPE_OBJECT # Object
# is关键字(用于对象)
var node = Node2D.new()
print(node is Node) # true
print(node is Node2D) # true
print(node is Node3D) # false
# as关键字(安全转换)
var sprite = node as Sprite2D # 如果不是Sprite2D返回null
if sprite:
sprite.texture = some_texture
6.8 变量作用域
6.8.1 作用域规则
# 全局变量(成员变量)
var global_var = 10
func example():
# 局部变量
var local_var = 20
if true:
# 块级变量
var block_var = 30
print(global_var) # 10 - 可访问
print(local_var) # 20 - 可访问
print(block_var) # 30 - 可访问
print(global_var) # 10 - 可访问
print(local_var) # 20 - 可访问
# print(block_var) # 错误!块级变量在块外不可访问
func another_func():
print(global_var) # 10 - 可访问
# print(local_var) # 错误!其他函数的局部变量不可访问
6.8.2 变量遮蔽
var value = 10 # 成员变量
func test():
var value = 20 # 局部变量遮蔽成员变量
print(value) # 20
print(self.value) # 10(通过self访问成员变量)
6.8.3 静态变量
# 静态变量(类级别共享)
static var instance_count: int = 0
func _init():
instance_count += 1
static func get_count() -> int:
return instance_count
本章小结
本章我们深入学习了GDScript的变量和数据类型:
- 变量声明:var关键字、类型推断、静态类型
- 基本类型:bool、int、float、String
- 复合类型:Array、Dictionary、PackedArray
- 向量类型:Vector2、Vector3、Vector4及其变体
- 其他类型:Color、Rect2、Transform、NodePath
- 常量枚举:const、enum的使用
- 类型转换:显式转换、类型检查
- 变量作用域:全局、局部、块级作用域
掌握数据类型是编程的基础,下一章我们将学习控制流程,让代码能够根据条件执行不同的逻辑。
上一章:GDScript基础语法
下一章:控制流程