Формат файла TSCN

The TSCN (text scene) file format represents a single scene tree inside Godot. Unlike binary SCN files, TSCN files have the advantage of being mostly human-readable and easy for version control systems to manage.

Формат файла ESCN (экспортированная сцена) идентичен формату файла TSCN, но используется для указания Godot, что файл был экспортирован из другой программы и не должен редактироваться пользователем внутри Godot. В отличие от файлов SCN и TSCN, при импорте файлы ESCN компилируются в двоичные файлы SCN, хранящиеся в папке .import/. Это уменьшает размер данных и ускоряет загрузку, так как двоичные форматы загружаются быстрее, чем текстовые.

Для тех, кто ищет полное описание, парсинг выполняется в файле resource_format_text.cpp в классе ResourceFormatLoaderText.

Структура файла

TSCN-файл состоит из пяти основных секций:

  1. Дескриптор файла

  2. Внешние ресурсы

  3. Внутренние ресурсы

  4. Узлы

  5. Соединения

Дескриптор файла имеет вид [gd_scene load_steps=3 format=2] и должен быть первой записью в файле. Параметр load_steps равен общему количеству ресурсов (внутренних и внешних) плюс один (для самого файла). Если файл не имеет ресурсов, то load_steps опускается. Движок все равно загрузит файл правильно, если load_steps неверно, но это повлияет на загрузочные полосы и любой другой код, полагающийся на это значение.

Эти разделы должны появляться по порядку, но их бывает трудно различить. Единственное различие между ними - это первый элемент в заголовке для всех элементов раздела. Например, заголовок всех внешних ресурсов должен начинаться с [ext_resource .....].

Файл TSCN может содержать однострочные комментарии, начинающиеся с точки с запятой (;). Однако комментарии будут отброшены при сохранении файла с помощью редактора Godot.

Записи внутри файла

Заголовок выглядит как [<resource_type> key=value key=value key=value ...], где тип_ресурса является одним из:

  • ext_resource

  • sub_resource

  • node

  • connection

Below every heading comes zero or more key = value pairs. The values can be complex datatypes such as Arrays, Transforms, Colors, and so on. For example, a spatial node looks like:

[node name="Cube" type="Spatial" parent="."]
transform=Transform( 1.0, 0.0, 0.0 ,0.0, 1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0 )

Дерево сцены

The scene tree is made up of… nodes! The heading of each node consists of its name, parent and (most of the time) a type. For example [node type="Camera" name="PlayerCamera" parent="Player/Head"]

Other valid keywords include:

  • instance

  • instance_placeholder

  • owner

  • index (задает порядок появления в дереве. Если отсутствует, наследуемые узлы будут иметь приоритет над простыми)

  • groups

Первый узел в файле, который также является корнем сцены, не должен иметь запись parent=Path/To/Node в своем заголовке. Все файлы сцен должны иметь ровно один корень сцены. Если этого не произойдет, Godot не сможет импортировать файл. Родительский путь других узлов должен быть абсолютным, но не должен содержать имя корня сцены. Если узел является прямым потомком корня сцены, путь должен быть ".". Вот пример дерева сцены (но без содержимого узла):

[node name="Player" type="Spatial"]             ; The scene root
[node name="Arm" parent="." type="Spatial"]     ; Parented to the scene root
[node name="Hand" parent="Arm" type="Spatial"]
[node name="Finger" parent="Arm/Hand" type="Spatial"]

Подобно внутреннему ресурсу, документ для каждого узла в настоящее время является неполным. К счастью, это легко выяснить, так как вы можете просто сохранить файл с этим узлом. Примерами узлов являются:

[node type="CollisionShape" name="SphereCollision" parent="SpherePhysics"]

shape = SubResource(8)
transform = Transform( 1.0 , 0.0 , -0.0 , 0.0 , -4.371138828673793e-08 , 1.0 , -0.0 , -1.0 , -4.371138828673793e-08 ,0.0 ,0.0 ,-0.0  )


[node type="MeshInstance" name="Sphere" parent="SpherePhysics"]

mesh = SubResource(9)
transform = Transform( 1.0 , 0.0 , -0.0 , 0.0 , 1.0 , -0.0 , -0.0 , -0.0 , 1.0 ,0.0 ,0.0 ,-0.0  )


[node type="OmniLight" name="Lamp" parent="."]

light_energy = 1.0
light_specular = 1.0
transform = Transform( -0.29086464643478394 , -0.7711008191108704 , 0.5663931369781494 , -0.05518905818462372 , 0.6045246720314026 , 0.7946722507476807 , -0.9551711678504944 , 0.199883371591568 , -0.21839118003845215 ,4.076245307922363 ,7.3235554695129395 ,-1.0054539442062378  )
omni_range = 30
shadow_enabled = true
light_negative = false
light_color = Color( 1.0, 1.0, 1.0, 1.0 )


[node type="Camera" name="Camera" parent="."]

projection = 0
near = 0.10000000149011612
fov = 50
transform = Transform( 0.6859206557273865 , -0.32401350140571594 , 0.6515582203865051 , 0.0 , 0.8953956365585327 , 0.44527143239974976 , -0.7276763319969177 , -0.3054208755493164 , 0.6141703724861145 ,14.430776596069336 ,10.093015670776367 ,13.058500289916992  )
far = 100.0

NоdePath

Древовидной структуры недостаточно для представления всей сцены. Godot использует структуру NodePath(Path/To/Node) для ссылки на другой узел или атрибут узла в любом месте дерева сцены. Например, MeshInstance использует NodePath() для указания на свой скелет. Аналогично, треки анимации используют NodePath() для указания на свойства узла для анимации.

[node name="mesh" type="MeshInstance" parent="Armature001"]

mesh = SubResource(1)
skeleton = NodePath("..:")
[sub_resource id=3 type="Animation"]

...
tracks/0/type = "transform
tracks/0/path = NodePath("Cube:")
...

Скелет

Узел Skeleton наследует узел Spatial, но также может иметь список костей, описанных в парах ключ-значение в формате bones/Id/Attribute=Value. Атрибуты костей состоят из:

  • имя

  • parent

  • rest

  • pose

  • enabled

  • bound_children

  1. name must be the first attribute of each bone.

  2. parent is the index of parent bone in the bone list, with parent index, the bone list is built to a bone tree.

  3. rest is the transform matrix of bone in its "resting" position.

  4. pose is the pose matrix; use rest as the basis.

  5. bound_children is a list of NodePath() which point to BoneAttachments belonging to this bone.

Here's an example of a skeleton node with two bones:

[node name="Skeleton" type="Skeleton" parent="Armature001" index="0"]

bones/0/name = "Bone.001"
bones/0/parent = -1
bones/0/rest = Transform( 1, 0, 0, 0, 0, -1, 0, 1, 0, 0.038694, 0.252999, 0.0877164 )
bones/0/pose = Transform( 1.0, 0.0, -0.0, 0.0, 1.0, -0.0, -0.0, -0.0, 1.0, 0.0, 0.0, -0.0 )
bones/0/enabled = true
bones/0/bound_children = [  ]
bones/1/name = "Bone.002"
bones/1/parent = 0
bones/1/rest = Transform( 0.0349042, 0.99939, 0.000512929, -0.721447, 0.0248417, 0.692024, 0.691589, -0.0245245, 0.721874, 0, 5.96046e-08, -1.22688 )
bones/1/pose = Transform( 1.0, 0.0, -0.0, 0.0, 1.0, -0.0, -0.0, -0.0, 1.0, 0.0, 0.0, -0.0 )
bones/1/enabled = true
bones/1/bound_children = [  ]

BoneAttachment

Узел BoneAttachment является промежуточным узлом для описания некоторого узла, являющегося родительским для одной кости в узле Skeleton. Узел BoneAttachment имеет атрибут bone_name=NameOfBone, а соответствующая кость, являющаяся родителем, имеет узел BoneAttachment в списке bound_children.

Пример одного MeshInstance, родительского по отношению к кости в Skeleton:

[node name="Armature" type="Skeleton" parent="."]

transform = Transform(1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0, -0.0219986, 0.0125825, 0.0343127)
bones/0/name = "Bone"
bones/0/parent = -1
bones/0/rest = Transform(1.0, 0.0, 0.0, 0.0, 0.0, -1.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0)
bones/0/pose = Transform(1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0)
bones/0/enabled = true
bones/0/bound_children = [NodePath("BoneAttachment:")]

[node name="BoneAttachment" type="BoneAttachment" parent="Armature"]

bone_name = "Bone"

[node name="Cylinder" type="MeshInstance" parent="Armature/BoneAttachment"]

mesh = SubResource(1)
transform = Transform(1.0, 0.0, 0.0, 0.0, 1.86265e-09, 1.0, 0.0, -1.0, 0.0, 0.0219986, -0.0343127, 2.25595)

АnimationPlayer

AnimationPlayer работает как библиотека анимаций. Он хранит анимации, перечисленные в формате anim/Name=SubResource(ResourceId); каждая строка ссылается на ресурс анимации. Все анимационные ресурсы используют корневой узел AnimationPlayer. Корневой узел хранится как root_node=NodePath(Path/To/Node).

[node name="AnimationPlayer" type="AnimationPlayer" parent="." index="1"]

root_node = NodePath("..")
autoplay = ""
playback_process_mode = 1
playback_default_blend_time = 0.0
playback_speed = 1.0
anims/default = SubResource( 2 )
blend_times = [  ]

Ресурсы

Ресурсы - это компоненты, из которых состоят узлы. Например, узел MeshInstance будет иметь сопутствующий ресурс ArrayMesh. Ресурс ArrayMesh может быть как внутренним, так и внешним по отношению к файлу TSCN.

Ссылки на ресурсы обрабатываются номерами id в заголовке ресурса. Внешние и внутренние ресурсы обозначаются соответственно ExtResource(id) и SubResource(id). Поскольку существуют различные методы ссылки на внутренние и внешние ресурсы, вы можете иметь один и тот же ID для внутреннего и внешнего ресурса.

Например, чтобы обратиться к ресурсу [ext_resource id=3 type="PackedScene" path=....], вы будете использовать ExtResource(3).

Внешние ресурсы

Внешние ресурсы - это ссылки на ресурсы, не содержащиеся в самом файле TSCN. Внешний ресурс состоит из пути, типа и идентификатора.

Godot всегда генерирует абсолютные пути относительно каталога ресурсов и, следовательно, с префиксом res://, но пути относительно местоположения файла TSCN также допустимы.

Примерами внешних ресурсов являются:

[ext_resource path="res://characters/player.dae" type="PackedScene" id=1]
[ext_resource path="metal.tres" type="Material" id=2]

Как и файлы TSCN, файл TRES может содержать однострочные комментарии, начинающиеся с точки с запятой (;). Однако комментарии будут отброшены при сохранении ресурса с помощью редактора Godot.

Внутренние ресурсы

Файл TSCN может содержать сетки, материалы и другие данные. Они содержатся в разделе внутренние ресурсы файла. Заголовок внутреннего ресурса выглядит так же, как и заголовки внешних ресурсов, за исключением того, что у него нет пути. Внутренние ресурсы также имеют пары ``ключ=значение'' под каждым заголовком. Например, форма столкновения капсулы выглядит следующим образом:

[sub_resource type="CapsuleShape" id=2]

radius = 0.5
height = 3.0

Некоторые внутренние ресурсы содержат ссылки на другие внутренние ресурсы (например, сетка, имеющая материал). В этом случае ссылающийся ресурс должен появиться перед ссылкой на него. Это означает, что порядок имеет значение в разделе внутренних ресурсов файла.

К сожалению, документация по форматам этих подресурсов неполная. Некоторые примеры можно найти, изучив сохраненные файлы ресурсов, но другие можно найти, только просмотрев исходный текст Godot.

ArrayMеsh

ArrayMesh состоит из нескольких поверхностей, каждая из которых имеет формат ``surfaceIndex={}`. Каждая поверхность представляет собой набор вершин и материал.

Файлы TSCN поддерживают два формата поверхности:

  1. В старом формате каждая поверхность имеет три основные клавиши:

  • primitive

  • arrays

  • morph_arrays

    1. primitive - это переменная перечисления, часто используется primitive=4, которая является PRIMITIVE_TRIANGLES.

    2. arrays - это двумерный массив, он содержит:

      1. Vertex positions array

      2. Normals array

      3. Массив касательных

      4. Массив цветов вершин

      5. UV массив 1

      6. UV массив 2

      7. Массив индексов костей

      8. Массив весов костей

      9. Массив вершинных индексов

    3. morph_arrays is an array of morphs. Each morph is exactly an arrays without the vertex indexes array.

Пример ArrayMesh:

[sub_resource id=1 type="ArrayMesh"]

surfaces/0 = {
    "primitive":4,
    "arrays":[
        Vector3Array(0.0, 1.0, -1.0, 0.866025, -1.0, -0.5, 0.0, -1.0, -1.0, 0.866025, 1.0, -0.5, 0.866025, -1.0, 0.5, 0.866025, 1.0, 0.5, -8.74228e-08, -1.0, 1.0, -8.74228e-08, 1.0, 1.0, -0.866025, -1.0, 0.5, -0.866025, 1.0, 0.5, -0.866025, -1.0, -0.5, -0.866025, 1.0, -0.5),
        Vector3Array(0.0, 0.609973, -0.792383, 0.686239, -0.609973, -0.396191, 0.0, -0.609973, -0.792383, 0.686239, 0.609973, -0.396191, 0.686239, -0.609973, 0.396191, 0.686239, 0.609973, 0.396191, 0.0, -0.609973, 0.792383, 0.0, 0.609973, 0.792383, -0.686239, -0.609973, 0.396191, -0.686239, 0.609973, 0.396191, -0.686239, -0.609973, -0.396191, -0.686239, 0.609973, -0.396191),
        null, ; No Tangents,
        null, ; no Vertex Colors,
        null, ; No UV1,
        null, ; No UV2,
        null, ; No Bones,
        null, ; No Weights,
        IntArray(0, 2, 1, 3, 1, 4, 5, 4, 6, 7, 6, 8, 0, 5, 9, 9, 8, 10, 11, 10, 2, 1, 10, 8, 0, 1, 3, 3, 4, 5, 5, 6, 7, 7, 8, 9, 5, 0, 3, 0, 9, 11, 9, 5, 7, 9, 10, 11, 11, 2, 0, 10, 1, 2, 1, 6, 4, 6, 1, 8)
    ],
    "morph_arrays":[]
}

Анимация

Анимационный ресурс состоит из дорожек. Кроме того, у него есть длительность (length), петля (loop) и шаг (step), применяемые ко всем дорожкам.

  1. длительность и шаг - это продолжительность в секундах.

Каждый трек описывается списком пар ключ-значение в формате tracks/Id/Attribute. Каждый трек включает в себя:

  • тип (type)

  • path

  • interp

  • ключи (keys)

  • loop_wrap

  • импортировано (imported)

  • enabled

  1. Атрибут type должен быть первым атрибутом каждого трека. Значение type может быть:

    • transform

    • value

    • метод (method)

  2. path имеет формат NodePath(Path/To/Node:attribute). Это путь к анимированному узлу или атрибуту, относительно корневого узла, определенного в AnimationPlayer.

  3. interp - это метод интерполяции кадров из ключевых кадров. Это переменная enum с одним из следующих значений:

    • 0 (константа)

    • 1 (linear)

    • 2 (cubic)

  4. The keys correspond to the keyframes. It appears as a PoolRealArray(), but may have a different structure for tracks with different types.

    • A Transform track uses every 12 real numbers in the keys to describe a keyframe. The first number is the timestamp. The second number is the transition followed by a 3-number translation vector, followed by a 4-number rotation quaternion (X, Y, Z, W) and finally a 3-number scale vector. The default transition in a Transform track is 1.0.

[sub_resource type="Animation" id=2]

length = 4.95833
loop = false
step = 0.1
tracks/0/type = "transform"
tracks/0/path = NodePath("Armature001")
tracks/0/interp = 1
tracks/0/loop_wrap = true
tracks/0/imported = true
tracks/0/enabled = true
tracks/0/keys = PoolRealArray( 0, 1, -0.0358698, -0.829927, 0.444204, 0, 0, 0, 1, 0.815074, 0.815074, 0.815074, 4.95833, 1, -0.0358698, -0.829927, 0.444204, 0, 0, 0, 1, 0.815074, 0.815074, 0.815074 )
tracks/1/type = "transform"
tracks/1/path = NodePath("Armature001/Skeleton:Bone.001")
tracks/1/interp = 1
tracks/1/loop_wrap = true
tracks/1/imported = true
tracks/1/enabled = false
tracks/1/keys = PoolRealArray( 0, 1, 0, 5.96046e-08, 0, 0, 0, 0, 1, 1, 1, 1, 4.95833, 1, 0, 5.96046e-08, 0, 0, 0, 0, 1, 1, 1, 1 )