Community编程与开发github.com

ziva-sh/godot-control-layout

构建可自适应缩放的 Godot 4 控制界面,避免裁剪或重叠——涵盖容器与锚点、代码中的锚点预设、大小标志、custom_minimum_size、MarginContainer 边距、Label 自动换行、mouse_filter、CanvasLayer HUD、Godot 4 主题覆盖名称(add_theme_color_override 等)以及多分辨率下的 display/window/stretch 设置。适合创建或修复菜单、HUD、对话框和物品栏界面,以及处理 UI 元素被裁剪、尺寸为零、重叠或无法交互的问题。

godot-control-layout 是什么?

该技能提供了一套完整的 Godot 4 控制布局方案,专注于解决 UI 裁剪、重叠和尺寸为零等常见问题。核心原则是使用容器管理子控件、仅对根 UI 使用锚点、并让最小尺寸驱动一切。它详细介绍了各种容器(如 VBoxContainer、HBoxContainer、GridContainer)及其尺寸标志(size_flags_horizontal/vertical、size_flags_stretch_ratio)和 custom_minimum_size 的用法。技能还解释了如何通过 set_anchors_and_offsets_preset() 正确设置锚点,以及如何利用 CanvasLayer 创建不受视角影响的 HUD。此外,它涵盖了文本裁剪的处理(autowrap_mode、text_overrun_behavior)、鼠标事件过滤(mouse_filter)以及 Godot 4 主题覆盖的 API 变化。最后,技能提供了多分辨率下的拉伸设置(canvas_items 模式、expand 宽高比等)以及一个实用的排查清单。该技能适用于协助开发者通过自然语言指令构建、修复或优化 Godot 4 项目的 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 都源于三个错误:手动放置容器的子控件、锚定控件时未重置偏移量、以及保持拉伸设置为默认值。请使用容器进行布局,只锚定每个 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() 仅移动锚点并保留旧偏移量,因此代码中创建的控件会保持尺寸 (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 (Layout: Full Rect) > VBoxContainer > HBoxContainer > Label, Control (Expand), 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。设置为 STOP 的全屏 Control 或 ColorRect 会吞噬所有点击,导致游戏中的 _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)
.tscn 中的 custom_colors/font_colortheme_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

相关技能