Godot 4 中的控制項佈局
多數破損的 Godot UI 來自三個錯誤:手動定位容器的子節點、錨定控制項卻未重置其偏移量(offsets),以及保留拉伸設定的預設值。請使用容器進行佈局,僅對每個 UI 的根節點設定錨點,並讓最小尺寸驅動一切。
容器擁有其子節點
任何 Container(VBoxContainer、HBoxContainer、GridContainer、MarginContainer、PanelContainer、CenterContainer、ScrollContainer、FlowContainer)的子節點無法保留自己的 position、size 或錨點:容器會在下次排序時覆寫它們。控制子節點可分配空間的方式:
size_flags_horizontal/size_flags_vertical:Control.SIZE_FILL(預設)、SIZE_EXPAND_FILL用於佔用剩餘空間、SIZE_SHRINK_CENTER/SIZE_SHRINK_END用於保持小型並對齊。size_flags_stretch_ratio:在擴充的兄弟節點之間分配剩餘空間的比例。custom_minimum_size:子節點接受的最小尺寸。容器永遠不會將子節點縮小至低於其組合最小尺寸,這是文字與按鈕避免被裁切的方式。
依據佈局選擇容器:行或列(VBoxContainer、HBoxContainer)、網格(GridContainer 搭配 columns)、內邊距(MarginContainer,透過主題常數 margin_left、margin_top、margin_right、margin_bottom 設定)、背景(PanelContainer,樣式盒 panel)、長內容(ScrollContainer,搭配一個具有 SIZE_EXPAND_FILL 的子節點)、換行項目(HFlowContainer)。
僅對根節點使用錨點
錨點將控制項相對於其父節點的矩形定位,用於最外層的 UI 節點。從程式碼中,使用 set_anchors_and_offsets_preset()。set_anchors_preset() 僅移動錨點並保留舊的偏移量(offsets),因此透過程式碼建立的控制項會保持大小為 (0, 0)。
預設:PRESET_FULL_RECT、PRESET_TOP_LEFT、PRESET_TOP_RIGHT、PRESET_BOTTOM_LEFT、PRESET_BOTTOM_RIGHT、PRESET_CENTER、PRESET_TOP_WIDE、PRESET_BOTTOM_WIDE、PRESET_LEFT_WIDE、PRESET_RIGHT_WIDE、PRESET_HCENTER_WIDE、PRESET_VCENTER_WIDE,以及中央邊緣預設,例如 PRESET_CENTER_TOP。
在 CanvasLayer 上的 HUD
位於 Node2D 下方的 Control 會在世界空間中繪製,並隨 Camera2D 移動。將 HUD 放在 CanvasLayer 下,使其保持在畫面上;layer 能排列多個畫布圖層(較高者繪製在上方)。
extends CanvasLayer
var score_label: Label
func _ready() -> void:
var margin := MarginContainer.new()
margin.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
for side in ["margin_left", "margin_top", "margin_right", "margin_bottom"]:
margin.add_theme_constant_override(side, 16)
add_child(margin)
var column := VBoxContainer.new()
margin.add_child(column)
var top_bar := HBoxContainer.new()
column.add_child(top_bar)
score_label = Label.new()
score_label.text = "Score: 0"
score_label.add_theme_font_size_override("font_size", 24)
score_label.add_theme_color_override("font_color", Color.GOLD)
top_bar.add_child(score_label)
var spacer := Control.new()
spacer.size_flags_horizontal = Control.SIZE_EXPAND_FILL
spacer.mouse_filter = Control.MOUSE_FILTER_IGNORE
top_bar.add_child(spacer)
var pause_button := Button.new()
pause_button.text = "Pause"
pause_button.custom_minimum_size = Vector2(96, 40)
top_bar.add_child(pause_button)
func set_score(value: int) -> void:
score_label.text = "Score: %d" % value
在編輯器中建立的相同節點樹為:CanvasLayer > MarginContainer(佈局:全矩形)> VBoxContainer > HBoxContainer > Label、Control(展開)、Button。
不會裁切的文字
- 設定了
autowrap_mode的Label需要從某處取得寬度。在具有預設旗標的HBoxContainer內部,它會縮小為 1 像素寬並變得很高。請賦予它SIZE_EXPAND_FILL或設定custom_minimum_size.x。 - 對於固定空間中的單行文字,請設定
text_overrun_behavior = TextServer.OVERRUN_TRIM_ELLIPSIS(或clip_text = true)。 - 優先使用
add_theme_font_size_override("font_size", n)而非縮放控制項:scale不會改變容器保留的尺寸。
extends VBoxContainer
func add_message(text: String) -> void:
var label := Label.new()
label.text = text
label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
label.size_flags_horizontal = Control.SIZE_EXPAND_FILL
add_child(label)
func add_title(text: String) -> void:
var label := Label.new()
label.text = text
label.text_overrun_behavior = TextServer.OVERRUN_TRIM_ELLIPSIS
label.custom_minimum_size = Vector2(0, 32)
add_child(label)
不會傳遞到遊戲的點擊
mouse_filter 決定控制項是否消耗滑鼠輸入。預設值各有不同:一般的 Control、ColorRect 和 Panel 使用 MOUSE_FILTER_STOP,容器使用 MOUSE_FILTER_PASS,Label 使用 MOUSE_FILTER_IGNORE。一個全螢幕的 Control 或 ColorRect 搭配 STOP 會吞掉所有點擊,因此遊戲中的 _unhandled_input() 永遠不會看到它。在覆蓋層、間隔元件和裝飾性矩形上設定 mouse_filter = Control.MOUSE_FILTER_IGNORE。
主題覆寫(Godot 4 名稱)
| Godot 3 | Godot 4 |
|---|---|
add_color_override("font_color", c) | add_theme_color_override("font_color", c) |
add_font_override("font", f) | add_theme_font_override("font", f) |
| (字型大小存在 DynamicFont 中) | add_theme_font_size_override("font_size", 24) |
add_stylebox_override("normal", sb) | add_theme_stylebox_override("normal", sb) |
add_constant_override("separation", 8) | add_theme_constant_override("separation", 8) |
add_icon_override("icon", tex) | add_theme_icon_override("icon", tex) |
custom_colors/font_color 在 .tscn 中 | theme_override_colors/font_color |
使用對應的 remove_theme_*_override(name) 移除覆寫。若要獲得一致的外觀,請將一個 Theme 資源指派給根控制項,而非覆寫每個節點;子節點會繼承它。
解析度與拉伸
在專案設定 > 顯示 > 視窗中設定(它們儲存在 project.godot 中):
display/window/size/viewport_width和viewport_height:你設計時依據的基礎解析度(預設為 1152 x 648)。display/window/stretch/mode:canvas_items適用於大多數遊戲(UI 與 2D 會平滑縮放),viewport適用於像素藝術,disabled適用於工具。Godot 3 的2d模式在 Godot 4 中為canvas_items。此設定的預設值為disabled;在 Godot 4.7 或更高版本中建立的專案起始值為canvas_items。display/window/stretch/aspect:expand讓錨定的 UI 在更寬或更高的螢幕上使用額外空間;keep會加入黑邊。預設值為keep;在 Godot 4.7 或更高版本中建立的專案起始值為expand。display/window/stretch/scale_mode:integer適用於清晰的像素藝術。
執行時期,相同的設定在視窗上:get_window().content_scale_mode = Window.CONTENT_SCALE_MODE_CANVAS_ITEMS 和 get_window().content_scale_aspect = Window.CONTENT_SCALE_ASPECT_EXPAND。
UI 被裁切或錯位時的檢查清單
- 該控制項是否為容器的子節點,且手動設定了
position或size?請使用尺寸旗標和custom_minimum_size。 - 根節點是否僅使用
set_anchors_preset()進行錨定?請使用set_anchors_and_offsets_preset()。 - 會換行的
Label是否有寬度? - HUD 是否位於
CanvasLayer而非Node2D下? - 在視窗大小變更時,
display/window/stretch/mode是否仍為disabled? - 是否有一個具有
MOUSE_FILTER_STOP的隱形全螢幕控制項位於最上層?