Community程式設計與開發github.com

ziva-sh/godot-control-layout

建構 Godot 4 控制項 UI,使其能自動縮放而非裁切或重疊 — 容器與錨點、程式中的錨點預設、尺寸旗標、custom_minimum_size、MarginContainer 邊距、Label 換行、mouse_filter、CanvasLayer HUD、Godot 4 主題覆寫名稱以及多重解析度的顯示/視窗/拉伸設定。適用於建立或修復選單、HUD、對話框與物品欄畫面,以及 UI 元素被裁切、大小為零、重疊或無法正確響應滑鼠點擊等情況。

godot-control-layout 是什麼?

這項技能專注於 Godot 4 中的控制項佈局,提供建立能正確縮放而非裁切或重疊 UI 的準則。它解釋了容器的正確使用方式(如 VBoxContainer、HBoxContainer、GridContainer 等),並強調容器會覆寫其子節點的 position、size 與錨點,因此應使用 size_flags、custom_minimum_size 和 stretch_ratio 來控制子節點空間。技能也涵蓋了正確的錨點設定,建議使用 `set_anchors_and_offsets_preset()` 而非 `set_anchors_preset()`,以避免控制項維持零尺寸。此外,它說明了如何使用 CanvasLayer 建立固定於畫面的 HUD、處理文字換行與裁切(包含 autowrap_mode、text_overrun_behavior 和 theme override)、調整 mouse_filter 以防止攔截遊戲輸入,以及提供 Godot 3 至 4 的主題覆寫名稱對應表。最後,它詳細說明了解析度與拉伸設定(stretch/mode、stretch/aspect、scale_mode),並提供一份檢查清單,幫助診斷 UI 裁切或錯位等常見問題。

相容平台~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/ziva-sh/godot-skills/tree/HEAD/skills/godot-control-layout

Installed? Explore more 程式設計與開發 skills: steipete/bluebubbles, steipete/eightctl, steipete/blucli · View all 6 →

在你喜歡的 AI 中提問

開啟一個已預先載入此 Agent Skill 的新對話。

說明文件

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 3Godot 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 被裁切或錯位時的檢查清單

  1. 該控制項是否為容器的子節點,且手動設定了 position 或 size?請使用尺寸旗標和 custom_minimum_size。
  2. 根節點是否僅使用 set_anchors_preset() 進行錨定?請使用 set_anchors_and_offsets_preset()。
  3. 會換行的 Label 是否有寬度?
  4. HUD 是否位於 CanvasLayer 而非 Node2D 下?
  5. 在視窗大小變更時,display/window/stretch/mode 是否仍為 disabled?
  6. 是否有一個具有 MOUSE_FILTER_STOP 的隱形全螢幕控制項位於最上層?

Individual skills in this repo

This repo contains 4 individual skills — each has its own dedicated page.

ziva-sh/godot-4-api-pitfalls

Fixes Godot 3 APIs and GDScript syntax that AI assistants write into Godot 4 projects, such as KinematicBody2D, move_and_slide(velocity), export, onready, setget, yield, string connect(), instance(), rand_range, the Tween node, File, change_scene, Spatial, rect_* properties, Pool*Array, OS time functions and the old TileMap API. Use when writing, reviewing or porting GDScript for Godot 4.x, when a script fails with "not declared", "not found in base", "was removed in Godot 4" or "is not present

ziva-sh/godot-scene-organization

Structures Godot 4 projects so scenes stay reusable and references do not break - when to make a scene, a script or a Resource, instancing with @export PackedScene, preload and load, @onready and %UniqueName node access, _ready order, groups, autoloads, class_name, and resource UIDs with the .uid files added in Godot 4.4, including how to move or rename files safely. Use when creating new scenes or scripts, deciding how nodes find each other, adding global state or an event bus, moving or renami

ziva-sh/godot-signals

Godot 4 signal patterns and their failure modes - declaring typed signals, emit(), connect() and disconnect() with Callables, bind() and unbind(), lambdas, CONNECT_ONE_SHOT and CONNECT_DEFERRED, await on signals and timers, editor-made connections stored in .tscn files, and the call down, signal up rule. Use when wiring UI buttons, timers, Area2D or body events and custom game events, when replacing Godot 3 connect("signal", self, "method") or yield code, or when debugging "already connected", "

ziva-sh/godot-tilemaplayer

Correct Godot 4.3+ TileMapLayer code - set_cell(coords, source_id, atlas_coords), reading cells and custom data, erasing, coordinate conversion, terrains, tile collision through TileSet physics layers, finding the tile a body hit, and migrating from the deprecated TileMap node and its layer-index API. Use when placing, reading or erasing tiles from GDScript, building a TileSet in code, generating levels procedurally, handling tile collisions, or porting TileMap code from Godot 3 or Godot 4.0 to

相關技能