Godot 4 中的場景組織
場景、腳本或資源
- 場景 (
.tscn):一個可重複使用的節點樹,附帶其自己的資源:例如玩家、敵人、可拾取物品、UI 面板、關卡。任何你會多次放置或希望單獨執行 (F6) 的內容都是一個場景。 - 腳本 (
.gd):附加到單個節點的行為。將其附加到它所控制場景的根節點,而不是附加到一個會深入該場景的上層節點。 - 資源腳本 (
extends Resource搭配class_name):沒有節點的資料,例如物品屬性、波次定義或存檔資料,可在檢查器中編輯並儲存為.tres。
一個場景應該能夠獨立運作。如果它需要外部的東西(例如目標、生成父節點),請使用 @export 或信號來暴露它,而不是使用 get_parent() 鏈。
實例化場景
extends Node2D
@export var enemy_scene: PackedScene
@export var spawn_parent: Node2D
func spawn_enemy(at: Vector2) -> void:
var enemy := enemy_scene.instantiate() as Node2D
enemy.position = at
spawn_parent.add_child(enemy)
func load_level(path: String) -> void:
var packed := load(path) as PackedScene
if packed == null:
push_error("找不到關卡:" + path)
return
get_tree().change_scene_to_packed(packed)
@export var x: PackedScene在檢查器中設定,並且在檔案移動後仍然有效,因為場景會儲存參考。const BULLET := preload("res://bullet.tscn")在解析時解析:遺失的檔案會導致解析錯誤 (Preload file "res://bullet.tscn" does not exist.)。適用於固定資產。load()在執行時解析,如果路徑錯誤則回傳null並輸出錯誤。用於在執行時建構的路徑。instantiate()(不是 Godot 3 的instance()) 回傳一個Node;將其轉型為你需要的型別。使用queue_free()釋放實例。
查找節點
extends Control
@onready var title: Label = %Title
@onready var start_button: Button = %StartButton
@onready var music: AudioStreamPlayer = $Music
@onready var optional_hint: Label = get_node_or_null("Hint")
func _ready() -> void:
title.text = "主選單"
start_button.grab_focus()
if optional_hint:
optional_hint.visible = false
music.play()
$Path是get_node("Path"),相對於此節點。如果節點遺失則會報錯;對於可選節點,請使用get_node_or_null()。%Name會存取在同一個場景中標記為「以唯一名稱存取」的節點(在場景停靠面板中按右鍵),因此可以承受場景內部的重新父級化。它不會存取到實例化的子場景:請使用$Child/%Name,或者當實例本身是唯一時使用%Child/%Name。@onready變數恰好在_ready()之前被賦值。它們在_init()和_enter_tree()中是null。_ready()在子節點上執行的順序早於其父節點,因此父節點的_ready()可以使用其子節點,但子節點的_ready()不能依賴父節點已準備就緒。- 為
@onready變數指定型別(: Label),以便解析器檢查成員名稱;未指定型別的路徑上的$Label.txt僅在執行時才會被捕獲。
群組
群組可以跨場景標記節點。在節點停靠面板(群組標籤)中或透過程式碼新增,然後查詢場景樹:
extends Node
func _ready() -> void:
add_to_group("enemies")
func alert_all_enemies() -> void:
get_tree().call_group("enemies", "set_alerted", true)
for enemy in get_tree().get_nodes_in_group("enemies"):
print(enemy.name)
var player := get_tree().get_first_node_in_group("player") as Node2D
if player:
print("玩家位於 ", player.global_position)
在 Godot 4 中,call_group() 會立即執行。若想延遲執行,請使用 get_tree().call_group_flags(SceneTree.GROUP_CALL_DEFERRED, ...)。
自動載入
自動載入是一個在主場景之前建立並在場景切換期間保留的節點。它用於保存比單一場景更長久的狀態(設定、存檔資料、場景切換器)以及僅發送信號的事件匯流排。在「專案設定 > 全域 > 自動載入」中註冊它;project.godot 檔案中會包含:
[autoload]
Events="*res://autoload/events.gd"
* 表示已勾選「啟用」,因此腳本可以直接使用該名稱(Events.player_died.emit())。不要 free() 或 queue_free() 自動載入。盡量保持自動載入數量稀少:任何屬於某個關卡的東西都應該存在於該關卡的場景中。
給工具開發者的注意事項:godot --check-only --script 不會載入自動載入,因此使用自動載入名稱的腳本會在那裡報告 Identifier not found: Events,即使它執行正常。請透過執行場景來驗證此類腳本(請參閱 godot-verify 技能)。
class_name
class_name Player 會註冊一個全域型別,因此其他腳本可以撰寫 var player: Player 並獲得成員檢查。編輯器會在 .godot/ 中維護一個全域類別快取;在編輯器外部新增 class_name 後,請先開啟編輯器或執行 godot --headless --path . --import,然後再檢查使用它的腳本。
UID、.uid 檔案與移動檔案
Godot 4 透過 UID 以及路徑來參考資源。場景和資源在檔案頭部儲存自己的 UID,導入的資產則儲存在它們的 .import 檔案中。從 Godot 4.4 開始,腳本和著色器會獲得一個伴隨檔案(player.gd.uid、water.gdshader.uid)。編輯器儲存的參考看起來像:
[ext_resource type="Script" uid="uid://dxxoiuob4eoum" path="res://game_manager.gd" id="1_feb5d"]
規則:
- 將
*.uid檔案提交到版本控制。不要將它們加入.gitignore。 - 盡可能使用 Godot 的檔案系統停靠面板來移動和重新命名檔案;它會更新參考。
- 在 Godot 外部(Shell、IDE、代理程式)移動腳本或著色器時,請同時移動其
.uid檔案,然後開啟編輯器或執行godot --headless --path . --import,以便 UID 快取學習到新路徑。在掃描完成之前,仍會使用舊路徑,導致載入失敗。 - 如果
.uid檔案被遺留在原地,Godot 會為已移動的腳本產生一個新的 UID,而參考舊 UID 的場景將退回使用過時的路徑:WARNING: ... invalid UID: uid://... - using text path instead: res://old/path.gd,然後是ERROR: ... referenced non-existent resource。 - 在手工編寫
.tscn時,不要發明uid=值。省略該屬性並使用正確的path;編輯器在下次儲存場景時會新增 UID。 - 手工編寫的 Godot 4 場景以
[gd_scene format=3]開頭,並透過字串 id 參考資源:[ext_resource type="Script" path="res://main.gd" id="1_main"],然後在節點上使用script = ExtResource("1_main")。子節點需要parent="."或從根節點開始的路徑。Godot 4.7 也會在節點上寫入unique_id=;手工編寫時請省略。 load("uid://...")和preload("uid://...")接受 UID 字串。從檔案系統停靠面板的右鍵選單複製 UID;ResourceUID.id_to_text(ResourceLoader.get_resource_uid(path))從程式碼中回傳它。
常見錯誤
- 使用
get_parent().get_parent()向上爬取,或使用絕對路徑如/root/Main/Player。請匯出一個參考、使用群組或發送一個信號。 - 在
_init()中或在節點進入場景樹之前使用它。 - 在許多腳本中硬編碼
res://路徑來參考場景,而不是匯出一個PackedScene。 - 移動
.gd檔案時沒有同時移動它們的.uid檔案,或在 git 中忽略*.uid。 - 因為方便而把所有東西都做成自動載入。