第六章:变量与数据类型

第六章:变量与数据类型

"数据是程序的灵魂,理解数据类型是编程的基础。"

在上一章中,我们了解了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)

只有两个值:truefalse

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):

  • false
  • 0(整数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的变量和数据类型:

  1. 变量声明:var关键字、类型推断、静态类型
  2. 基本类型:bool、int、float、String
  3. 复合类型:Array、Dictionary、PackedArray
  4. 向量类型:Vector2、Vector3、Vector4及其变体
  5. 其他类型:Color、Rect2、Transform、NodePath
  6. 常量枚举:const、enum的使用
  7. 类型转换:显式转换、类型检查
  8. 变量作用域:全局、局部、块级作用域

掌握数据类型是编程的基础,下一章我们将学习控制流程,让代码能够根据条件执行不同的逻辑。


上一章:GDScript基础语法

下一章:控制流程

← 返回目录