Modernize Godot 3.x UI to 4.x best practices with theme extraction, responsive layouts, hiDPI support, and RichTextLabel BBCode patterns
Scanned 2/12/2026
Install via CLI
openskills install majiayu000/claude-skill-registry---
name: godot-modernize-ui
version: 1.0.0
displayName: Godot UI Modernization
description: Modernize Godot 3.x UI to 4.x best practices with theme extraction, responsive layouts, hiDPI support, and RichTextLabel BBCode patterns
author: Godot Superpowers
license: MIT
category: ui
type: tool
difficulty: beginner
audience:
- developers
- ui-designers
keywords:
- godot
- ui
- theme
- responsive
- hidpi
- bbcode
- anchoring
- layout
- control
- modernize
platforms:
- linux
- macos
- windows
repository: https://github.com/asreonn/godot-superpowers
homepage: https://github.com/asreonn/godot-superpowers#readme
---
# Godot UI Modernization
Modernizes Godot 3.x UI scenes and scripts to Godot 4.x best practices.
## When to Use
- Migrating Godot 3.x projects to 4.x with UI scenes
- UI doesn't scale properly on hiDPI displays
- Hardcoded styles scattered across Control nodes
- Manual positioning instead of responsive anchoring
- RichTextLabel using deprecated BBCode tags
- Creating reusable custom controls
## Core Patterns
### 1. Theme Resource Extraction
**Before (Godot 3.x):**
```gdscript
# Hardcoded in each Control node
$Button.modulate = Color.red
$Button.get("custom_styles/normal").bg_color = Color.blue
$Label.add_font_override("font", preload("res://fonts/big.tres"))
```
**After (Godot 4.x):**
```gdscript
# Centralized in Theme resource
# Create: res://ui/themes/main_theme.tres
# Assign to root Control or project settings
```
**Theme.tres structure:**
```
Theme
├── Button/styles/normal (StyleBoxFlat)
├── Button/colors/font_color = Color(1, 1, 1, 1)
├── Button/fonts/font = preload("res://fonts/button_font.tres")
├── Label/fonts/font = preload("res://fonts/label_font.tres")
└── Panel/styles/panel (StyleBoxFlat)
```
### 2. Control Anchoring Standardization
**Before:**
```gdscript
# Manual positioning
$Panel.position = Vector2(100, 100)
$Panel.size = Vector2(400, 300)
```
**After:**
```
Control (root)
├── Layout Mode: Anchors
├── Anchor Left: 0.5, Anchor Top: 0.5
├── Anchor Right: 0.5, Anchor Bottom: 0.5
├── Grow Horizontal: Center
├── Grow Vertical: Center
└── Custom Minimum Size: (400, 300)
```
**Anchor Presets for Common Layouts:**
| Layout | Anchors | Offsets | Grow |
|--------|---------|---------|------|
| Fullscreen | 0,0 to 1,1 | 0 all sides | Both |
| Top Bar | 0,0 to 1,0 | Bottom: 60 | Horizontal |
| Bottom Bar | 0,1 to 1,1 | Top: 60 | Horizontal |
| Center Panel | 0.5,0.5 | Size defined | Both |
| Left Sidebar | 0,0 to 0,1 | Right: 250 | Vertical |
| Right Sidebar | 1,0 to 1,1 | Left: 250 | Vertical |
### 3. UI Scaling (hiDPI Support)
**Project Settings:**
```
Display > Window > Stretch
├── Mode: canvas_items (or viewport)
├── Aspect: expand
└── Scale: 1.0 (auto-detected)
```
**Script for Dynamic Scaling:**
```gdscript
func _ready():
# Handle hiDPI on different displays
var screen_dpi = DisplayServer.screen_get_dpi()
if screen_dpi > 150:
get_tree().root.content_scale_factor = 2.0
```
**Responsive Sizing:**
```gdscript
@export var base_width: float = 1920.0
@export var base_height: float = 1080.0
func _on_viewport_size_changed():
var viewport_size = get_viewport().get_visible_rect().size
var scale_x = viewport_size.x / base_width
var scale_y = viewport_size.y / base_height
scale = Vector2(min(scale_x, scale_y), min(scale_x, scale_y))
```
### 4. RichTextLabel BBCode Patterns
**Deprecated Godot 3.x:**
```
[color=red]Warning[/color]
[url=https://example.com]Link[/url]
[img]res://icon.png[/img]
```
**Godot 4.x BBCode:**
```
[color=ff0000]Warning[/color]
[color=#ff0000]Hex Color[/color]
[url=https://example.com]Link[/url]
[img=64x64]res://icon.png[/img]
[font=res://fonts/custom.tres]Custom font[/font]
[wave amp=50 freq=5]Animated text[/wave]
[tornado radius=5 freq=2]Spinning text[/tornado]
[shake rate=5 level=10]Shaking text[/shake]
[fgcolor=00ff00 bgcolor=000000]Background[/fgcolor]
[outline_color=ffffff]Outlined[/outline_color]
```
**Script Integration:**
```gdscript
@onready var rich_label: RichTextLabel = $RichTextLabel
func append_colored_text(text: String, color: Color):
rich_label.append_text("[color=%s]%s[/color]" % [color.to_html(), text])
func append_url(text: String, url: String):
rich_label.append_text("[url=%s]%s[/url]" % [url, text])
func _on_meta_clicked(meta):
OS.shell_open(str(meta))
```
### 5. Custom Control Creation
**Custom Button with Built-in Theme:**
```gdscript
@tool
class_name ModernButton
extends Button
@export var button_style: ButtonStyle = ButtonStyle.PRIMARY:
set(value):
button_style = value
_update_style()
enum ButtonStyle { PRIMARY, SECONDARY, DANGER }
func _ready():
_update_style()
func _update_style():
match button_style:
ButtonStyle.PRIMARY:
add_theme_color_override("font_color", Color(1, 1, 1))
add_theme_stylebox_override("normal", preload("res://ui/styles/primary_normal.tres"))
ButtonStyle.SECONDARY:
add_theme_color_override("font_color", Color(0.2, 0.2, 0.2))
add_theme_stylebox_override("normal", preload("res://ui/styles/secondary_normal.tres"))
ButtonStyle.DANGER:
add_theme_color_override("font_color", Color(1, 1, 1))
add_theme_stylebox_override("normal", preload("res://ui/styles/danger_normal.tres"))
```
## Responsive Design Patterns
### Container Hierarchy
```
CanvasLayer (UI Layer)
└── Control (Full Rect anchor, Fullscreen preset)
├── MarginContainer (padding)
│ ├── VBoxContainer (vertical layout)
│ │ ├── HBoxContainer (top bar)
│ │ │ ├── Label (title)
│ │ │ └── Button (close)
│ │ └── ScrollContainer (scrollable content)
│ │ └── VBoxContainer
│ │ └── (dynamic content)
│ └── HBoxContainer (bottom bar)
│ └── Button (action)
└── Panel (overlay/popup)
```
### Safe Area Handling (Mobile)
```gdscript
func _ready():
# Apply safe area insets for notched devices
var safe_area = DisplayServer.get_display_safe_area()
var window_size = DisplayServer.window_get_size()
$MarginContainer.add_theme_constant_override("margin_left", safe_area.position.x)
$MarginContainer.add_theme_constant_override("margin_top", safe_area.position.y)
$MarginContainer.add_theme_constant_override("margin_right",
window_size.x - safe_area.end.x)
$MarginContainer.add_theme_constant_override("margin_bottom",
window_size.y - safe_area.end.y)
```
## Common Mistakes
**Using RectPosition/RectSize directly:**
```gdscript
# ❌ Bad - breaks with different resolutions
$Control.rect_position = Vector2(100, 100)
$Control.rect_size = Vector2(200, 150)
# ✅ Good - use anchors and layout containers
# Set anchors in editor or use custom_minimum_size
$Control.custom_minimum_size = Vector2(200, 150)
```
**Hardcoding pixel values without scaling:**
```gdscript
# ❌ Bad - too small on hiDPI
var padding = 10
# ✅ Good - scale with content_scale_factor
var padding = 10 * get_tree().root.content_scale_factor
```
**Manual font size calculations:**
```gdscript
# ❌ Bad - inconsistent across displays
label.add_theme_font_size_override("font_size", 24)
# ✅ Good - use theme with dynamic fonts
# Set in Theme resource with multiple sizes
```
**Forgetting to handle window resize:**
```gdscript
# ✅ Good - subscribe to resize signal
func _ready():
get_viewport().size_changed.connect(_on_viewport_size_changed)
_on_viewport_size_changed()
func _on_viewport_size_changed():
# Recalculate responsive layout
pass
```
## Quick Reference
| Task | Godot 3.x | Godot 4.x |
|------|-----------|-----------|
| Theme override | `add_stylebox_override()` | `add_theme_stylebox_override()` |
| Font override | `add_font_override()` | `add_theme_font_override()` |
| Color override | `add_color_override()` | `add_theme_color_override()` |
| Constant override | `add_constant_override()` | `add_theme_constant_override()` |
| Size override | `rect_min_size` | `custom_minimum_size` |
| Position | `rect_position` | `position` |
| Size | `rect_size` | `size` |
| Global position | `rect_global_position` | `global_position` |
| BBCode color | `[color=red]` | `[color=ff0000]` or `[color=#ff0000]` |
| BBCode image | `[img]path[/img]` | `[img=widthxheight]path[/img]` |
| hiDPI setting | `ProjectSettings.display/window/dpi/allow_hidpi` | `Display > Window > Stretch > Mode` |
## Migration Checklist
- [ ] Extract all hardcoded styles to Theme resources
- [ ] Replace rect_* properties with position/size
- [ ] Update BBCode syntax (colors, images)
- [ ] Configure hiDPI in project settings
- [ ] Convert manual positioning to anchor/layout system
- [ ] Test on multiple resolutions and DPIs
- [ ] Add viewport resize handling
- [ ] Create custom controls for repeated patterns
No comments yet. Be the first to comment!