Attention: Here be dragons

This is the latest (unstable) version of this documentation, which may document features not available in or compatible with released stable versions of Godot.

TSCN 文件格式

TSCN(文本场景)文件格式表示 Godot 内部的单个场景树。与二进制的 SCN 文件不同,TSCN 具有易于人类阅读、便于使用版本控制系统进行管理的优点。

ESCN(导出场景)文件格式与 TSCN 文件格式相同,但它是用于向 Godot 表明该文件是从另一个程序导出的,不应由用户在 Godot 内部进行编辑。与 SCN 和 TSCN 文件不同,导入时,ESCN 文件会被编译为存储在 .godot/imported/ 文件夹中的二进制 SCN 文件。这减少了数据大小并加快了加载速度,因为与基于文本的格式相比,二进制格式的加载更快。

为了使文件更加紧凑,属性值等于默认值的属性不会储存在场景 / 资源文件中。这些属性可以手动写入,但在储存文件时会被自动丢弃。

如果想要完整的描述,对这些文件格式的解析是在 resource_format_text.cppResourceFormatLoaderText 类中进行处理的。

备注

Godot 4 对场景和资源文件格式做出了重大变化,引入了基于字符串的 UID 来取代递增整数的 ID。

网格,骨骼和动画数据的存储方式也不同于 Godot 3。部分变化可以参考这篇文章: Animation data rework for 4.0

Godot 4.x 中保存的场景和资源在标头中包含 format=3 ,Godot 3.x 则使用 format=2

文件结构

TSCN 文件分为五个主要部分:

  1. 文件描述符

  2. 外部资源

  3. 内部资源

  4. 节点

  5. 连接

文件描述符(file descriptor)看起来像这样: [gd_scene format=3 uid="uid://cecaux1sm7mo0"] ,并且它必须是文件里的第一行内容。需要注意的是,在 Godot 4.6 之前保存的场景文件,其文件描述符里还会包含一个 load_steps=<int> 属性。这个属性现在已经弃用了,如果文件里还有它,直接忽略就好。

uid 是一个唯一的,基于字符串的标识符,用于代表场景。引擎利用该标识符来追踪被移动的文件,即使编辑器关闭也同样如此。脚本也可以利用 uid:// 路径前缀来加载基于 UID 的资源,而不必依赖于文件系统的路径。这样,即使文件在项目中被任意移动,也可以从脚本中读取该文件,而无需对脚本进行更改。Godot 不使用外部文件来追踪 ID,因此项目中不需要中心化元数据储存位置。详细信息请参考 拉取请求

这些部分应按顺序出现,但可能很难区分它们。它们之间的唯一区别是该部分中所有项目的标题中的第一个元素。例如,所有外部资源的标题都应以 [ext_resource ...] 开头。

TSCN 文件可能包含以分号(;)开头的单行注释。但是,使用 Godot 编辑器保存文件时,注释将被丢弃。TSCN 文件中的空格并不重要(字符串内的除外),但保存文件时,多余的空格将被丢弃。

文件中的条目

标题看起来像 [<resource_type> key1=value1 key2=value2 key3=value3 ...],其中 resource_type 是以下之一:

  • ext_resource

  • sub_resource

  • node

  • connection

每个标题下面都有零个或多个 key = value 对。值可以是类似数组、变换、颜色等复杂的数据类型。例如,一个 Node3D 将如下所示:

[node name="Cube" type="Node3D" unique_id=224283918]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 2, 3)

场景树

场景树是由……节点组成的!每个节点的开头都包含了它的名称、父节点、一个唯一 ID(用于在节点被移动或重命名时依然能追踪到它),以及大多数情况下的一个类型。例如:[node name="PlayerCamera" type="Camera" parent="Player/Head" unique_id=1697057368]

请注意, unique_id 仅存在于使用 Godot 4.6 或更高版本保存的场景中。因此,无法保证它一定存在。

其他有效的关键字包括:

  • instance

  • instance_placeholder

  • owner

  • index(设置在树中出现的顺序;如果不存在,则继承的节点将优先于普通节点)

  • groups

  • node_paths (列出那些在代码中被导出为 Node 类型的属性名,但在文件中实际是以 NodePath 形式引用的)

文件里的第一个节点,也就是场景的根节点,它的标题中绝对 不能 包含 parent="Path/To/Node" 这一项。每个场景文件必须有且仅有 一个 场景根节点。如果数量不对,Godot 将无法导入该文件。其他节点的父路径应当是绝对路径,但路径里不能包含场景根节点的名字。如果某个节点是场景根节点的直接子节点,那么它的父路径应该写为 "."。以下是一个场景树结构的示例(不过其中不包含任何具体的节点内容):

[node name="Player" type="Node3D" unique_id=1155673912]                    ; The scene root
[node name="Arm" type="Node3D" parent="." unique_id=1010797352]            ; Parented to the scene root
[node name="Hand" type="Node3D" parent="Arm" unique_id=536436825]          ; Child of "Arm"
[node name="Finger" type="Node3D" parent="Arm/Hand" unique_id=1732647084]  ; Child of "Hand"

小技巧

为了让文件结构更易于理解,可以将任意节点或资源保存为文件,并在外部编辑器中对其进行审查。还可以在 Godot 编辑器中进行渐进式更改,同时在外部文本编辑器自动更新启动的情况下,将 .tscn.tres 文件保持打开,以观察变化内容。

以下是一个示例场景,包含一个基于 RigidBody3D 且具有碰撞的球体,视觉元素(网格和光照)和附加到 RigidBody3D 上的相机:

[gd_scene format=3 uid="uid://cecaux1sm7mo0"]

[sub_resource type="SphereShape3D" id="SphereShape3D_tj6p1"]

[sub_resource type="SphereMesh" id="SphereMesh_4w3ye"]

[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_k54se"]
albedo_color = Color(1, 0.639216, 0.309804, 1)

[node name="Ball" type="RigidBody3D" unique_id=1358867382]

[node name="CollisionShape3D" type="CollisionShape3D" parent="." unique_id=1279975976]
shape = SubResource("SphereShape3D_tj6p1")

[node name="MeshInstance3D" type="MeshInstance3D" parent="." unique_id=558852834]
mesh = SubResource("SphereMesh_4w3ye")
surface_material_override/0 = SubResource("StandardMaterial3D_k54se")

[node name="OmniLight3D" type="OmniLight3D" parent="." unique_id=1581292810 node_paths=PackedStringArray("follow_node")]
light_color = Color(1, 0.698039, 0.321569, 1)
omni_range = 10.0
follow_node = NodePath("..")

[node name="Camera3D" type="Camera3D" parent="." unique_id=795715540]
transform = Transform3D(1, 0, 0, 0, 0.939693, 0.34202, 0, -0.34202, 0.939693, 0, 1, 3)

节点路径

光靠单纯的树形结构并不足以描述整个场景。因此,Godot 使用 NodePath(Path/To/Node) 这种结构,来指向场景树中任意位置的其他节点,或者该节点的某个属性。这些路径都是相对于当前节点而言的,其中 NodePath(".") 指向当前节点自己,而 NodePath("") (空路径)则表示不指向任何节点。

举个例子,MeshInstance3D(3D 网格实例)会利用 NodePath() 来指向它对应的骨骼节点。同样地,动画轨道也会使用 NodePath() 来精准定位需要制作动画的节点属性。

NodePath 还可能用 :property_name 后缀来指向一个属性,还可能指向向量,变换和颜色类型中的一个分量。动画资源利用这一点,来指向要动画的特定属性。例如,NodePath("MeshInstance3D:scale.x") 指向 MeshInstance3D 中的 Vector3 类型的 scale 属性中的 x 分量。

举例来说,名为 mesh 的 MeshInstance3D 节点中,属性 skeleton 指向其父节点 Armature01

[node name="mesh" type="MeshInstance3D" parent="Armature01" unique_id=1638249225]
skeleton = NodePath("..")

Skeleton3D

Skeleton3D (3D 骨骼)节点继承自 Node3D 节点,但它还可以包含一组骨骼列表。这些骨骼信息以键值对的形式进行描述,格式为 bones/<id>/<attribute> = value 。骨骼的属性包括:

  • position:Vector3

  • rotation:Quaternion

  • scale:Vector3

这些属性都是可选的。例如,骨骼可能只定义了 positionrotation ,而不定义其他属性。

具有两个骨骼的骨架节点的示例:

[node name="Skeleton3D" type="Skeleton3D" parent="PlayerModel/Robot_Skeleton" index="0" unique_id=542985694]
bones/1/position = Vector3(0.114471, 2.19771, -0.197845)
bones/1/rotation = Quaternion(0.191422, -0.0471201, -0.00831942, 0.980341)
bones/2/position = Vector3(-2.59096e-05, 0.236002, 0.000347473)
bones/2/rotation = Quaternion(-0.0580488, 0.0310587, -0.0085914, 0.997794)
bones/2/scale = Vector3(0.9276, 0.9276, 0.9276)

BoneAttachment3D

BoneAttachment3D 节点是一个中间节点,用于描述在 Skeleton 节点中以单根骨骼为父节点的某些节点。BoneAttachment 具有 bone_name = "骨骼名称" 属性,以及匹配骨骼索引的属性。

以 Skeleton 中的骨骼为父级的 Marker3D 节点示例:

[node name="GunBone" type="BoneAttachment3D" parent="PlayerModel/Robot_Skeleton/Skeleton3D" index="5" unique_id=63481392]
transform = Transform3D(0.333531, 0.128981, -0.933896, 0.567174, 0.763886, 0.308015, 0.753209, -0.632331, 0.181604, -0.323915, 1.07098, 0.0497144)
bone_name = "hand.R"
bone_idx = 55

[node name="ShootFrom" type="Marker3D" parent="PlayerModel/Robot_Skeleton/Skeleton3D/GunBone" unique_id=679926736]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.4, 0)

AnimationPlayer

AnimationPlayer (动画播放器)节点需要配合一个或多个存储在 AnimationLibrary (动画库)资源中的动画库来工作。一个动画库,其实就是若干个独立 Animation (动画)资源的集合,这些动画资源的具体结构可以 here 查阅相关文档。

动画本身与动画库之间的这种拆分是在 Godot 4 中完成的,这样一来,动画就可以与 3D 网格(meshes)分开导入。这是 3D 动画软件中非常常见的工作流程。想了解具体细节,可以查看 original pull request

如果动画库的名字留空,它就会作为这个 AnimationPlayer 唯一的动画来源。这样一来,你就可以在脚本中直接使用 <animation_name> 来播放动画。如果你给动画库起了名字,那么你就必须使用 <library_name>/<animation_name> 的格式来播放。这样的设计既保证了向后兼容性,也能让那些不需要使用多个动画库的用户,继续保持原有的工作流程。

资源

资源是组成各个节点的元件。举例来说,MeshInstance节点中会有附带的ArrayMesh资源。该ArrayMesh资源可以在TSCN档的内部或外部。

对资源的引用由资源标题中基于字符串的唯一 ID 处理。这与每个外部资源也具有的 uid 属性不同(但子资源没有)。

外部资源和内部资源分别通过 ExtResource("id")SubResource("id") 来引用。由于引用内部资源和外部资源使用的是不同的方法,因此你可以给一个内部资源和一个外部资源分配相同的 ID。

举个例子,如果你想引用 [ext_resource type="Material" uid="uid://c4cp0al3ljsjv" path="res://material.tres" id="1_7bt6s"] 这个外部资源,你应该使用 ExtResource("1_7bt6s") 来调用它。

外部资源

外部资源是指那些没有直接包含在 TSCN 文件内部,而是通过链接引用的资源。一个外部资源通常包含四个要素:路径(资源存放的位置)、类型、UID(唯一标识符,用于将资源在文件系统里的位置映射为一个唯一编号)以及 ID(在场景文件中用来指代这个资源的代号)。

Godot总是生成相对于资源目录的绝对路径, 因此以 res:// 为前缀, 但是相对于TSCN文件位置的路径也有效.

一些示例外部资源是:

[ext_resource type="Texture2D" uid="uid://ccbm14ebjmpy1" path="res://gradient.tres" id="2_eorut"]
[ext_resource type="Material" uid="uid://c4cp0al3ljsjv" path="material.tres" id="1_7bt6s"]

和 TSCN 文件一样,TRES 文件也可以包含以分号(;)开头的单行注释。不过,当你使用 Godot 编辑器保存资源时,这些注释会被自动丢弃。另外,TRES 文件里的空白字符(如空格、换行等)通常不影响文件解析(字符串内部的除外),但在保存文件时,多余的空格也会被自动清理掉。

内部资源

TSCN文件可以包含网格, 材质和其他数据. 这些包含在文件的 内部资源 部分中. 内部资源的标题与外部资源的标题相似, 不同之处在于它没有路径. 内部资源在每个标题下还具有 键=值 对. 例如, 胶囊碰撞形状如下所示:

[sub_resource type="CapsuleShape3D" id="CapsuleShape3D_fdxgg"]
radius = 1.0
height = 3.0

一些内部资源包含到其他内部资源的链接(例如具有材质的网格). 在这种情况下, 引用的资源必须在对其的引用 之前 出现. 这意味着顺序在文件的内部资源部分中很重要.

ArrayMesh

ArrayMesh 由包含在 _surfaces 数组中的多个表面组成(注意开头的下划线)。每个表面的数据都储存在具有以下键的字典中:

  • aabb:为了计算可见性而生成的轴对齐包围盒。

  • attribute_data:顶点属性数据,例如法线、切线、顶点颜色、UV1、UV2和自定义顶点数据。

  • bone_aabbs:每个骨骼的轴对齐边界框以提高可见性。

  • format:表面的缓冲区格式。

  • index_count:表面中索引的数量。这必须与 index_data 的大小相符。

  • index_data :索引数据,它决定了要从 vertex_data 中抽取并绘制哪些顶点。

  • lods:细节变化的级别,储存为数组。每个LOD等级代表数组中的两个值。第一个值是LOD等级最适合的屏幕空间百分比(边缘长度);第二个值是应为给定LOD等级绘制的索引列表。

  • material:绘制表面时所使用的材质。

  • name:表面的名称。这可以在脚本中使用,并从3D DCC汇入。

  • primitive(图元类型): 表面的图元类型, 与Godot Mesh.PrimitiveType Godot 枚举一一对应。 0 = 点, 1 = 线段, 2 = 线带, 3 = 三角形 (最常见), 4 = 三角带。

  • skin_data:骨骼重量数据。

  • vertex_count:表面中的顶点数。这必须与 vertex_data 的大小相符。

  • vertex_data:顶点位置数据。

这里是一个将 ArrayMesh(数组网格)单独保存为 .tres 文件的示例。为了简洁起见,部分字段用 ... 进行了缩短处理:

[gd_resource type="ArrayMesh" format=3 uid="uid://dww8o7hsqrhx5"]

[ext_resource type="Material" path="res://player/model/playerobot.tres" id="1_r3bjq"]

[resource]
resource_name = "player_Sphere_016"
_surfaces = [{
"aabb": AABB(-0.207928, 1.21409, -0.14545, 0.415856, 0.226569, 0.223374),
"attribute_data": PackedByteArray(63, 121, ..., 117, 63),
"bone_aabbs": [AABB(0, 0, 0, -1, -1, -1), ..., AABB(-0.207928, 1.21409, -0.14545, 0.134291, 0.226569, 0.223374)],
"format": 7191,
"index_count": 1224,
"index_data": PackedByteArray(30, 0, ..., 150, 4),
"lods": [0.0382013, PackedByteArray(33, 1, ..., 150, 4)],
"material": ExtResource("1_r3bjq"),
"name": "playerobot",
"primitive": 3,
"skin_data": PackedByteArray(15, 0, ..., 0, 0),
"vertex_count": 1250,
"vertex_data": PackedByteArray(196, 169, ..., 11, 38)
}]
blend_shape_mode = 0

动画

每个动画都包含以下属性:

  • length :动画的长度(以秒为单位)。请注意,关键帧可以放置在 [0; length] 的区间之外,但根据所选的插值模式这样可能没有效果。

  • 循环模式(loop_mode): 0 = 不循环(no looping), 1 = 环绕循环(wrap-around looping), 2 = 钳制循环(clamped looping).

  • step :在编辑器中编辑此动画时所使用的步长。该属性只在编辑器中可用;它不会以任何方式影响动画播放。

每个轨道均由格式为 tracks/<id>/<attribute> 的键值对列表描述。每个轨道包括:

  • type :轨道的类型。这个属性定义了该轨道可以动画化哪种属性,以及如何在编辑器中向用户公开它。有效类型为 value (通用属性轨道)、 position_3drotation_3dscale_3dblend_shape (优化的 3D 动画轨道)、 method (方法调用轨道)、 bezier (贝塞尔曲线轨道)、 audio (音频播放轨道)、 animation (播放其他动画的轨道)。

  • imported :如果轨道是从导入的 3D 场景创建的,则为 true ;如果轨道是由用户在 Godot 编辑器中或使用脚本手动创建的,则为 false

  • enabled :如果轨道有效的(即被启用),则为 true ;如果轨道在编辑器中被禁用,则为 false

  • path :将受轨道影响的节点属性的路径。该属性写在节点路径后面,并使用 : 分隔符。

  • interp :要使用的插值模式。0 = 最近(nearest),1 = 线性(linear),2 = 立方(cubic),3 = 线性角度(linear angle),4 = 立方角度(cubic angle)。

  • loop_wrap :如果该轨道被设计为在动画循环时首尾衔接(回绕),则为 true ;如果该轨道在循环时停留在首/尾关键帧(即不产生回绕过渡),则为 false

  • keys :动画轨道的值。该属性的结构取决于 type

这是一个包含 AnimationPlayer 的场景,它使用通用属性轨道,随时间推进缩小立方体。由于未使用 AnimationLibrary 工作流程,此动画库的名称为空(但动画仍指定为 scale_down 名称)。请注意,为了简洁起见,没有在此 AnimationPlayer 中创建 RESET 轨道:

[gd_scene format=3 uid="uid://cdyt3nktp6y6"]

[sub_resource type="Animation" id="Animation_r2qdp"]
resource_name = "scale_down"
length = 1.5
loop_mode = 2
step = 0.05
tracks/0/type = "value"
tracks/0/imported = false
tracks/0/enabled = true
tracks/0/path = NodePath("Box:scale")
tracks/0/interp = 1
tracks/0/loop_wrap = true
tracks/0/keys = {
"times": PackedFloat32Array(0, 1),
"transitions": PackedFloat32Array(1, 1),
"update": 0,
"values": [Vector3(1, 1, 1), Vector3(0, 0, 0)]
}

[sub_resource type="AnimationLibrary" id="AnimationLibrary_4qx36"]
_data = {
"scale_down": SubResource("Animation_r2qdp")
}

[sub_resource type="BoxMesh" id="BoxMesh_u688r"]

[node name="Node3D" type="Node3D" unique_id=2076735200]

[node name="AnimationPlayer" type="AnimationPlayer" parent="." unique_id=2139773137]
autoplay = "scale_down"
libraries = {
"": SubResource("AnimationLibrary_4qx36")
}

[node name="Box" type="MeshInstance3D" parent="." unique_id=711004519]
mesh = SubResource("BoxMesh_u688r")

对于通用属性 value 轨道,keys 是一个字典,其中包含 3 个数组。三个数组分别是:位置(position)位于 times (PackedFloat32Array)中;缓动值(easing value)位于 transitions (PackedFloat32Array)中;值(value)位于 values (Array)中。还有一个附加的 update 属性,它是一个整数,其值代表的含义分别是: 0 = 连续(continuous),1 = 离散(discrete),2 = 捕获(capture)。

这是第二个动画资源,它利用了 3D 位置和 3D 旋转轨道。这些轨道(除了 3D 缩放轨道)取代了 Godot 3 中的 Transform 轨道。它们经过优化,可以快速播放,并且可以选择进行压缩。

这些优化轨道类型的缺点是,它们无法使用自定义缓动值。相反地,所有关键帧都使用线性插值。也就是说,你仍然可以通过更改轨道的插值模式,来选择对给定轨道中的所有关键帧使用最近插值或三次插值。

[sub_resource type="Animation" id="Animation_r2qdp"]
resource_name = "move_and_rotate"
length = 1.5
loop_mode = 2
step = 0.05
tracks/0/type = "position_3d"
tracks/0/imported = false
tracks/0/enabled = true
tracks/0/path = NodePath("Box")
tracks/0/interp = 1
tracks/0/loop_wrap = true
tracks/0/keys = PackedFloat32Array(0, 1, 0, 0, 0, 1.5, 1, 1.5, 1, 0)
tracks/1/type = "rotation_3d"
tracks/1/imported = false
tracks/1/enabled = true
tracks/1/path = NodePath("Box")
tracks/1/interp = 1
tracks/1/loop_wrap = true
tracks/1/keys = PackedFloat32Array(0, 1, 0.211, -0.047, 0.211, 0.953, 1.5, 1, 0.005, 0.976, -0.216, 0.022)

对于 3D 位置、旋转和缩放轨道,key 是一个将所有值都存储在序列中的 PackedFloat32Array。

在下面的视觉指南中,T 是自动画开始以来关键帧的时间(以秒为单位),E 是关键帧的过渡(当前始终为 1 )。对于 3D 位置和比例轨道,XYZ 是 Vector3 的坐标。对于 3D 旋转轨道,XYZW 是四元数的坐标。

# For 3D position and scale, which use Vector3:
tracks/<id>/keys = PackedFloat32Array(T, E,   X, Y, Z,      T, E,   X, Y, Z, ...)

# For 3D rotation, which use Quaternion:
tracks/<id>/keys = PackedFloat32Array(T, E,   X, Y, Z, W,      T, E,   X, Y, Z, W, ...)