核心类型
最后同步日期:2026-04-15 | Godot 官方原文 — Core types
核心类型
Godot 引擎的底层有一套丰富的类和模板,它们构成了整个引擎的骨架。你在引擎中看到的一切功能——节点、资源、渲染、物理——最终都建立在这些核心类型之上。
打个比方:核心类型就像乐高积木的"基础颗粒"——每个颗粒看起来都很简单,但它们组合在一起就能拼出任何东西。
基本数据类型定义
Godot 使用标准的 C99 数据类型,比如 uint8_t、uint32_t、int64_t 等。这些类型在现代编译器上都得到广泛支持,没必要重新造轮子。
在引擎代码中,一般不会刻意追求"最省内存"的类型(除非处理大型结构或数组)。大多数地方直接使用 int,因为现在的设备至少都有 32 位总线,一次周期就能完成 int 运算,而且代码也更易读。
对于文件大小或内存大小,使用 size_t 类型,保证是 64 位的。
对于 Unicode 字符,Godot 定义了 CharType 而不是直接用 wchar_t。原因是很多架构的 wchar_t 是 4 字节的,但有时 2 字节更合适。默认情况下,CharType 直接映射为 wchar_t。
源码参考:
core/typedefs.h
内存模型
PC 是很厉害的架构——动辄几个 GB 的内存、几个 TB 的硬盘、几个 GHz 的 CPU。但移动设备和游戏主机就没这么阔绰了,内存资源更加有限。
最常见的内存模型是堆(Heap):程序向操作系统申请一块内存,操作系统找一个合适的位置返回给你。这种方式灵活好用,但时间长了容易产生碎片化(Segmentation)——内存中会出现越来越多"太小而无法利用"的空洞。
研究表明,只要保留 10%-20% 的空闲内存,且单次分配的大小不超过一定阈值,碎片化就不会成为严重问题。
Godot 确保所有动态分配的对象都很小(通常不超过几 KB)。那大块数据(如图片、网格几何体、大数组)怎么办?Godot 提供了动态内存池:这块内存需要加锁才能访问,如果空间不够,池会自动整理和压缩。游戏开发者可以根据需要配置这个内存池的大小。
内存分配
Godot 内置了内存追踪工具,尤其在调试模式下能帮你监控内存使用情况。因此,不要使用标准的 C/C++ 内存分配函数,而要用 Godot 提供的替代方案。
C 风格的内存分配
// 等同于标准 C 的 malloc / realloc / free
void *data = memalloc(size);
data = memrealloc(data, new_size);
memfree(data);C++ 风格的对象分配
// 创建一个对象(等同于 new)
MyClass *obj = memnew(MyClass);
MyClass *obj2 = memnew(MyClass(arg1, arg2));
// 删除一个对象(等同于 delete)
memdelete(obj);
// 创建对象数组(等同于 new[])
MyClass *arr = memnew_arr(MyClass, 10);
// 删除对象数组(等同于 delete[])
memdelete_arr(arr);memnew / memdelete 还有一点额外功能:它们会在对象创建后和删除前通知 Object 系统,让引擎能正确追踪对象的生命周期。
动态数组
对于需要动态增长的内存,优先使用 Godot 提供的序列类型:
| 类型 | 特点 | 适用场景 |
|---|---|---|
Vector<> | 写时复制(CoW),线程安全的读取 | 需要在多处共享数据时 |
LocalVector<> | 无 CoW,开销更低 | 内部使用,不需要共享数据时 |
List<> | 双向链表 | 需要频繁在中间插入/删除时 |
写时复制(Copy-On-Write,CoW) 是 Vector<> 的核心特性:当你复制一个 Vector<> 时,新旧对象共享同一块内存,只有当你修改其中一方时,才会真正复制数据。这意味着:
- 复制操作非常快(只是增加引用计数)
- 不同线程可以安全地读取同一份 CoW 数据
- 但多个线程不能安全地操作同一个
Vector<>实例
Packed*Array 类型(如 PackedByteArray、PackedInt32Array)是特定 Vector<*> 类型的别名,可以通过 GDScript 访问。在核心代码之外,如果函数需要暴露给脚本使用,优先使用 Packed*Array 别名。
源码参考:
core/os/memory.h
容器
Godot 提供了四个常用的容器模板:
这些容器设计得尽量精简。C++ 模板往往会被内联,导致二进制体积膨胀(包括调试符号和代码),所以 Godot 的容器比 STL 的对应物更轻量。
遍历容器
List、Set、Map 使用指针遍历:
// 遍历 List 中的每个元素
for (List<int>::Element *E = somelist.front(); E; E = E->next()) {
print_line(E->get()); // 打印当前元素
}Vector 的两个亮点
- 写时复制:复制
Vector<>几乎零成本,直到你修改它时才会真正拷贝数据 - 原子引用计数:使用原子操作管理引用计数,天然支持多线程读取
提示
优先使用 Vector<>(或 LocalVector<>)而不是 List<>。对现代 CPU 来说,连续内存的缓存命中率和碎片问题通常比链表的插入优势更重要。
源码参考:
core/templates/vector.h、core/templates/list.h、core/templates/set.h、core/templates/map.h
String(字符串)
Godot 提供了自己的 String 类。它不像 std::string 那样只有基本的字符串功能,而是一个功能极为丰富的"字符串瑞士军刀":
- 完整的 Unicode 支持:所有字符串操作(大小写转换、查找、替换等)都正确处理 Unicode
- UTF-8 解析和提取:方便地在 UTF-8 编码和其他编码之间转换
- 丰富的辅助方法:类型转换、格式化、可视化等
源码参考:
core/string/ustring.h
StringName(字符串名称)
StringName 是一种特殊的字符串——相同内容的字符串在内存中只存一份。
打个比方:如果普通的 String 是每人各拿一份复印件,那 StringName 就是大家共享一张原件。当你用相同的字符串创建多个 StringName 时,它们在内部都指向同一个指针。
这意味着:
| 操作 | StringName | String |
|---|---|---|
| 创建 | 较慢(需要查找或注册) | 快 |
| 比较 | 极快(只需比较指针) | 较慢(需要逐字符比较) |
StringName 非常适合用作标识符(如属性名 "position"、方法名 "_ready"、信号名 "pressed"),因为这些字符串需要被频繁比较,但创建只需要一次。
源码参考:
core/string/string_name.h
数学类型
Godot 在 core/math 目录下提供了丰富的线性数学类型,用于 2D 和 3D 图形、物理计算等:
- 向量:
Vector2、Vector2i、Vector3、Vector3i、Vector4 - 矩阵:
Transform2D、Transform3D、Basis、Projection - 几何体:
Rect2、Rect2i、AABB、Plane - 旋转:
Quaternion - 颜色:
Color
源码参考:
core/math
NodePath(节点路径)
NodePath 是一种专门用于存储场景树中路径的数据类型。它让你能高效地引用场景中的某个节点,例如 "Sprite2D/Texture" 或 "../Player"。
你可以把它理解为场景树里的"地址"——就像文件系统路径指向某个文件一样,NodePath 指向场景树中的某个节点。
源码参考:
core/string/node_path.h
RID(资源 ID)
RID(Resource ID)是服务器用来引用内部数据的唯一标识符。在架构概览中我们提到过,Godot 的服务器层(如 RenderingServer、PhysicsServer 等)负责底层运算,而 RID 就是你与这些服务器打交道时的"凭证"。
// 创建一个 RID(通常由服务器方法返回)
RID rid = RenderingServer::get_singleton()->texture_create();
// 用 RID 操作服务器中的数据
RenderingServer::get_singleton()->texture_set_data(rid, image);RID 的特点:
- 不透明的:你拿到的是一个 ID,不能直接访问它背后的数据
- 唯一的:每个 RID 都是唯一的,即使是不同类型的数据
源码参考:
core/templates/rid.h
