Disposition des contrôles dans Godot 4
La plupart des interfaces Godot cassées proviennent de trois erreurs : positionner manuellement les enfants d'un conteneur, ancrer un contrôle sans réinitialiser ses décalages (offsets), et laisser les paramètres d'étirement par défaut. Disposez avec des conteneurs, ancrez uniquement la racine de chaque UI, et laissez les tailles minimales piloter le reste.
Les conteneurs possèdent leurs enfants
Un enfant d'un Container (VBoxContainer, HBoxContainer, GridContainer, MarginContainer, PanelContainer, CenterContainer, ScrollContainer, FlowContainer) ne peut pas conserver ses propres position, size ou ancres : le conteneur les écrase lors du tri suivant. Contrôlez la part d'espace d'un enfant avec :
size_flags_horizontal/size_flags_vertical:Control.SIZE_FILL(par défaut),SIZE_EXPAND_FILLpour prendre l'espace libre,SIZE_SHRINK_CENTER/SIZE_SHRINK_ENDpour rester petit et aligné.size_flags_stretch_ratio: part de l'espace libre entre les frères en expansion.custom_minimum_size: la plus petite taille que l'enfant accepte. Les conteneurs ne réduisent jamais un enfant en dessous de sa taille minimale combinée, ce qui évite que le texte et les boutons soient coupés.
Choisissez le conteneur selon la disposition : lignes ou colonnes (VBoxContainer, HBoxContainer), une grille (GridContainer avec columns), du padding (MarginContainer, padding défini par les constantes de thème margin_left, margin_top, margin_right, margin_bottom), un arrière-plan (PanelContainer, style box panel), un contenu long (ScrollContainer avec un enfant ayant SIZE_EXPAND_FILL), des éléments qui se répartissent sur plusieurs lignes (HFlowContainer).
Ancres pour la racine uniquement
Les ancres placent un contrôle par rapport au rectangle de son parent et sont ce que vous utilisez pour le nœud UI le plus externe. Depuis le code, utilisez set_anchors_and_offsets_preset(). set_anchors_preset() ne déplace que les ancres et conserve les anciens décalages, donc un contrôle créé dans le code reste à la taille (0, 0).
Présélections : 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, et les présélections centre-bord comme PRESET_CENTER_TOP.
HUD sur un CanvasLayer
Un Control sous un Node2D est dessiné dans l'espace mondial et se déplace avec la Camera2D. Placez le HUD sous un CanvasLayer pour qu'il reste à l'écran ; layer ordonne plusieurs calques de canevas (le plus élevé dessine par-dessus).
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
Construit dans l'éditeur, la même arborescence est : CanvasLayer > MarginContainer (Disposition: Plein écran) > VBoxContainer > HBoxContainer > Label, Control (Expansion), Button.
Texte qui ne se coupe pas
- Un
Labelavecautowrap_modedéfini a besoin d'une largeur quelque part. À l'intérieur d'unHBoxContaineravec les indicateurs par défaut, il se réduit à 1 pixel de large et devient très haut. Donnez-luiSIZE_EXPAND_FILLou uncustom_minimum_size.x. - Pour un texte sur une seule ligne dans un espace fixe, définissez
text_overrun_behavior = TextServer.OVERRUN_TRIM_ELLIPSIS(ouclip_text = true). - Préférez
add_theme_font_size_override("font_size", n)plutôt que de redimensionner un contrôle :scalene change pas la taille réservée par les conteneurs.
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)
Clics qui n'atteignent pas le jeu
mouse_filter détermine si un contrôle consomme les entrées de la souris. Les valeurs par défaut diffèrent : un simple Control, ColorRect et Panel utilisent MOUSE_FILTER_STOP, les conteneurs utilisent MOUSE_FILTER_PASS, Label utilise MOUSE_FILTER_IGNORE. Un Control ou ColorRect plein écran avec STOP avale chaque clic, donc _unhandled_input() dans le jeu ne le voit jamais. Définissez mouse_filter = Control.MOUSE_FILTER_IGNORE sur les superpositions, les espaceurs et les rectangles décoratifs.
Surcharges de thème (noms 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) |
| (la taille de police vivait dans 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 dans .tscn | theme_override_colors/font_color |
Supprimez une surcharge avec le remove_theme_*_override(name) correspondant. Pour un aspect cohérent, attribuez une ressource Theme au contrôle racine au lieu de surcharger chaque nœud ; les enfants l'héritent.
Résolution et étirement
Définissez ces paramètres dans Paramètres du projet > Affichage > Fenêtre (ils sont stockés dans project.godot) :
display/window/size/viewport_widthetviewport_height: la résolution de base pour laquelle vous concevez (par défaut 1152 x 648).display/window/stretch/mode:canvas_itemspour la plupart des jeux (UI et 2D se mettent à l'échelle en douceur),viewportpour le pixel art,disabledpour les outils. Le mode2dde Godot 3 estcanvas_itemsdans Godot 4. La valeur par défaut estdisabled; les projets créés dans Godot 4.7 ou ultérieur démarrent aveccanvas_items.display/window/stretch/aspect:expandpermet à l'UI ancrée d'utiliser l'espace supplémentaire sur les écrans plus larges ou plus hauts ;keepajoute des barres noires. La valeur par défaut estkeep; les projets créés dans Godot 4.7 ou ultérieur démarrent avecexpand.display/window/stretch/scale_mode:integerpour un pixel art net.
Au moment de l'exécution, les mêmes paramètres se trouvent sur la fenêtre : get_window().content_scale_mode = Window.CONTENT_SCALE_MODE_CANVAS_ITEMS et get_window().content_scale_aspect = Window.CONTENT_SCALE_ASPECT_EXPAND.
Checklist lorsque l'UI est coupée ou mal placée
- Le contrôle est-il un enfant d'un conteneur avec une
positionousizedéfinie manuellement ? Utilisez les indicateurs de taille etcustom_minimum_size. - La racine a-t-elle été ancrée avec
set_anchors_preset()uniquement ? Utilisezset_anchors_and_offsets_preset(). - Un
Labelavec retour à la ligne a-t-il une largeur ? - Le HUD est-il sous un
CanvasLayerplutôt que sous unNode2D? display/window/stretch/modeest-il toujoursdisabledalors que la taille de la fenêtre change ?- Un contrôle invisible plein écran avec
MOUSE_FILTER_STOPse trouve-t-il au-dessus ?