Variant 类
最后同步日期:2026-04-15 | Godot 官方原文 — Variant class
Variant 类
Variant 是 Godot 引擎中最重要的数据类型——没有之一。它就像一个"万能容器",能够装下引擎中的任何数据。不管你想存一个数字、一段文字、一个向量、甚至一个完整的对象,Variant 都能搞定。
在 64 位系统上,一个 Variant 只占 24 个字节(32 位系统上占 20 字节)。它的设计目标是在保持小巧的同时,提供尽可能多的能力。
Variant 能做什么?
Variant 不仅仅是个"容器",它还自带一套完整的操作能力:
| 能力 | 说明 |
|---|---|
| 存储任意类型 | 可以存放 Godot 中几乎所有数据类型 |
| 执行操作 | 支持 +、-、*、/ 等数学和逻辑运算 |
| 哈希 | 可以作为字典(Dictionary)的键 |
| 类型转换 | 可以在不同类型之间自动或手动转换 |
| 方法调用 | 可以调用 Variant 内部数据的成员方法 |
| 延迟调用与线程 | 支持将调用推迟到下一帧,或发送到其他线程执行 |
| 序列化 | 支持二进制和文本格式的序列化与反序列化 |
| 导出属性 | 可以在编辑器中作为可编辑的属性暴露给用户 |
提示
你在 GDScript 中写的每一行代码,背后几乎都在和 Variant 打交道。GDScript 的所有变量本质上都是 Variant——这也是 GDScript 如此灵活的原因之一。
Variant 的工作原理
打个比方:Variant 就像一个"信封"。信封外面写着标签(类型信息),里面装着实际的物品(数据)。
- 类型标签:告诉引擎这个 Variant 当前存的是什么类型的数据
- 数据区域:存放实际的数据。对于小数据(如
int、float),直接存在 Variant 内部;对于大数据(如String、Array),存的是指向堆内存的指针
Variant 类型列表
Godot 定义了约 30 种 Variant 类型。下面是完整列表:
| 类型 | 说明 | 大小/备注 |
|---|---|---|
NIL | 空值(null) | 不包含任何数据 |
BOOL | 布尔值 | true 或 false |
INT | 整数 | 64 位有符号整数 |
FLOAT | 浮点数 | 64 位双精度 |
STRING | 字符串 | Unicode 文本 |
VECTOR2 | 二维向量 | x 和 y 两个浮点分量 |
VECTOR2I | 二维整数向量 | x 和 y 两个整数分量 |
VECTOR3 | 三维向量 | x、y、z 三个浮点分量 |
VECTOR3I | 三维整数向量 | x、y、z 三个整数分量 |
VECTOR4 | 四维向量 | x、y、z、w 四个浮点分量 |
VECTOR4I | 四维整数向量 | x、y、z、w 四个整数分量 |
RECT2 | 二维矩形 | position + size |
RECT2I | 二维整数矩形 | 整数版本的矩形 |
TRANSFORM2D | 2D 变换矩阵 | 2×3 矩阵,用于 2D 旋转/缩放/平移 |
TRANSFORM3D | 3D 变换矩阵 | 4×3 矩阵,用于 3D 旋转/缩放/平移 |
PLANE | 平面 | 法线 + 距离 |
QUATERNION | 四元数 | 用于表示 3D 旋转 |
AABB | 轴对齐包围盒 | 用于碰撞检测和空间查询 |
BASIS | 3×3 矩阵 | 表示 3D 旋转和缩放 |
PROJECTION | 4×4 投影矩阵 | 用于相机投影和 3D 渲染 |
COLOR | 颜色 | RGBA + Alpha |
STRING_NAME | 字符串名称 | 内部化(interned)的字符串标识符 |
NODE_PATH | 节点路径 | 指向场景树中的节点 |
RID | 资源 ID | 服务器的内部资源标识符 |
OBJECT | 对象 | 任何 Godot Object 的引用 |
CALLABLE | 可调用对象 | 封装的方法引用,可用于延迟调用 |
SIGNAL | 信号 | 对象间的通信机制 |
DICTIONARY | 字典 | 键值对集合(键必须是 Variant) |
ARRAY | 数组 | 有序元素集合(元素都是 Variant) |
PACKED_*_ARRAY | 打包数组 | 专用类型数组(如 PackedByteArray) |
空值性(Nullability)
Variant 的空值规则很简单:
- Nil 类型:专门用来表示"什么都没有"(类似于其他语言中的
null) - Object 类型:可以为
null,表示"没有引用任何对象" - 其他所有类型:不可为空。一个
INT类型的 Variant 要么包含一个整数值,要么不存在(根本不是INT类型)
这意味着你不需要像 C# 那样到处写 int? 来处理空值——在 Variant 的世界里,"有没有值"和"是什么类型"是两件完全独立的事情。
容器:Array 和 Dictionary
Array 和 Dictionary 是 Variant 类型系统中最常用的两种容器。它们有一个非常重要的特性需要特别注意。
它们是引用类型
Array 和 Dictionary 的行为类似于"共享文档"——如果你把一个 Array 赋值给另一个变量,两个变量实际上指向同一份数据。修改其中一个,另一个也会跟着变。
多线程访问需要加锁
正因为 Array 和 Dictionary 是引用共享的,多线程同时读写同一个 Array 或 Dictionary 是不安全的。如果你需要在多个线程中访问它们,必须使用 Mutex(互斥锁)来保护:
// C++ 中使用 Mutex 保护 Dictionary 的多线程访问
Mutex mutex;
// 线程 A:写入数据
mutex.lock();
my_dictionary["key"] = value;
mutex.unlock();
// 线程 B:读取数据
mutex.lock();
Variant value = my_dictionary["key"];
mutex.unlock();源码参考:
core/variant/variant.hcore/variant/dictionary.hcore/variant/array.h
Variant 的性能考量
Variant 虽然功能强大,但也不是没有代价:
| 场景 | 建议 |
|---|---|
| GDScript 日常使用 | 放心用,Variant 的开销对 GDScript 来说可以忽略 |
| 引擎核心代码(C++) | 性能敏感路径上,尽量使用具体类型而非 Variant |
| 需要大量计算的循环 | 避免在循环中反复进行 Variant 类型转换 |
| 内存受限场景 | 注意 Variant 的 24 字节开销,大量使用时比原生类型更占内存 |
提示
理解 Variant 是理解 Godot 引擎内部工作原理的关键。几乎所有引擎的通信机制——属性、信号、序列化、编辑器——都建立在 Variant 之上。
