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.

The ESCN (exported scene) file format is identical to the TSCN file format, but is used to indicate to Godot that the file has been exported from another program and should not be edited by the user from within Godot. Unlike SCN and TSCN files, during import, ESCN files are compiled to binary SCN files stored inside the .import/ folder. This reduces the data size and speeds up loading, as binary formats are faster to load compared to text-based formats.

若需要完整的說明,請參考 ResourceFormatLoaderText 中負責剖析該格式的 resource_format_text.cpp 檔案。

檔案結構

在 TSCN 檔中主要有五個區塊:

  1. 檔案描述元

  2. 外部資源

  3. 內部資源

  4. 節點

  5. 連接

The file descriptor looks like [gd_scene load_steps=3 format=2] and should be the first entry in the file. The load_steps parameter is equal to the total amount of resources (internal and external) plus one (for the file itself). If the file has no resources, load_steps is omitted. The engine will still load the file correctly if load_steps is incorrect, but this will affect loading bars and any other piece of code relying on that value.

這些區塊應照會順序排列,但要區分出各個段落可能有點困難。在這些區塊間的主要差異就是各個段落中所有項目標題的第一項元素。舉例來說,所有外部資源的標題都應以 [ext_resource ......] 開頭。

TSCN 檔案可以包含以分號 (;) 開頭的單行註解。但,以 Godot 編輯器保存時會移除註解。

檔案內的項目

當 resource_type 為下列其一時,標題看起來會像 [<resource_type> key=value key=value key=value ...]

  • ext_resource

  • sub_resource

  • node

  • connection

在每個標題後都會有 0 或多個 key = value 索引鍵/值組。數值可以是複雜的資料型別,如 Array, Transform, Color …等。舉例來說,Spatial 節點可能會類似:

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

場景樹

場景樹是由……節點構成的!各個節點的標題都是由名稱、母節點與 (大多數時候還會有) 型別所組成的。如, [node type="Camera" name="PlayerCamera" parent="Player/Head"]

其他有效的關鍵字包含:

  • instance

  • instance_placeholder

  • owner

  • index (sets the order of appearance in the tree. If absent, inherited nodes will take precedence over plain ones)

  • 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

NodePath

使用樹狀結構並無法代表完整的場景。Godot 使用 NodePath(Path/To/Node) 結構來參照場景樹中的某個節點或節點的屬性。舉例來說,MeshInstance 使用 NodePoint() 來指向其骨架。同樣地,動畫軌使用 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 節點,但同時 Skeleton 也以 bones/Id/Attribute=Value 這樣的格式來儲存一組索引鍵/值格式的骨頭列表。骨頭屬性由下列這幾個屬性構成:

  • name

  • parent

  • rest

  • pose

  • enabled

  • bound_children

  1. name 必須為各個骨頭的第一個屬性。

  2. parent 為骨頭列表中母級骨頭的索引。有了母級骨頭的索引,就能將骨頭列表建立成骨頭樹。

  3. rest 為將骨骼轉換為「靜止」(Resting) 姿勢用的變換矩陣。

  4. pose 為姿勢矩陣,以 rest 作為基礎。

  5. bound_children 為一組由 NodePath() 組成的列表,用來指向該骨骼的 BoneAttachment。

下列為有兩個骨頭的骨骼節點範例:

[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 屬性,而對應為母節點的骨頭之 bound_children 列表中則有 BoneAttachment 節點。

下列為 Skeleton 中有骨頭母節點的 MeshInstance 範例:

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

AnimationPlayer

AnimationPlayer 提供動畫函式庫的功能。AnimationPlayer 將各個動畫以 anim/Name=SubResource(ResourceId) 這樣的格式來儲存一組動畫列表。其中,每一行都代表一個 Animation 資源。所有的動畫都使用 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 檔自身的資源。外部資源由路徑、型別與 ID 組成。

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 檔可以包含網格、材質與其他資料。這些資料都放在 TSCN 檔中的 內部資源 區塊中。內部資源區塊的標題看起來與外部資源的標題類似,但內部資源的標題不含路徑。內部資源中的各個標題中也有 key=value 索引鍵/值組。舉例來說,膠囊型碰撞區域會像這樣:

[sub_resource type="CapsuleShape" id=2]

radius = 0.5
height = 3.0

有的內部資源會包含指向另一個內部資源的連結 (如網格會具有材質)。這種情況下,被參照的資源的資源必須要放在參照 之前 。這表示,在 TSCN 檔中,要注意檔案內部資源區塊的順序。

不幸的是,有關這些子資源格式的說明文件還不完整。雖然可以通過檢視保存後的資源檔案來找到範例,但有的子資源格式只能在 Godot 原始碼中看到。

ArrayMesh

ArrayMesh 是由數個表面組成的,各個表面的格式都為 surface\Index={} 。每個表面都是一組頂點與材質的組合。

TSCN 檔支援兩種表面格式:

  1. 對於舊版格式,各個表面都有三種主要的索引鍵:

  • primitive

  • arrays

  • morph_arrays

    1. primitive 為列舉類型變數,常用代表 PRIMITIVE_TRIANGLESprimitive=4

    2. array 為 2D 陣列,包含:

      1. 頂點位置陣列

      2. Normals array

      3. 切線陣列

      4. 頂點色彩陣列

      5. UV 陣列 1

      6. UV 陣列 2

      7. 骨頭索引陣列

      8. 骨頭權重陣列

      9. 頂點索引陣列

    3. morph_arrays 為 Morph 陣列。各個 Morph 都恰好為沒有頂點索引陣列的 arrays

下列為一 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, loopstep

  1. lengthstep 都是以秒為單位的時間長度。

每個軌道都是由一組索引鍵/值配對所組成的,其格式為 tracks/Id/Attribute 。每個軌道都包含了:

  • type

  • path

  • interp

  • keys

  • loop_wrap

  • imported

  • enabled

  1. type 必須為各個軌道的第一個屬性。type 的值可以為下列其一:

    • transform

    • value

    • method

  2. path 的格式為 NodePath(Path/To/Node:attribute)path 為動畫化節點或屬性的路徑,相對於 AnimationPlayer 中定義的根節點。

  3. interp 為從關鍵影格中進行插值影格的方法。interp 為一列舉類型的變數,其值為下列其一:

    • 0 (常數)

    • 1 (線性)

    • 2 (三次方)

  4. keys 則對應關鍵影格。以 PoolRealArray() 來表示,但對於不同型別的軌道,可能會有不同的架構。

    • Transform 軌道在 keys 中使用整整 12 個實數來表示關鍵影格。第一個數字為時戳。第二個數字為變換,再接上 3 個數字的變換向量,然後是 4 個數字的旋轉四元數 (X, Y, Z, W),最後則是 3 個數字的縮放向量。在 Transform 軌道中預設的變換為 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 )