第三十五章:主题与样式
Godot 4的主题系统允许开发者统一管理UI控件的外观,包括颜色、字体、图标和样式盒等。本章将详细介绍如何创建和使用主题,实现一致的视觉风格。
35.1 主题系统概述
35.1.1 主题基础
# theme_basics.gd
# 主题系统基础
extends Control
## 主题组成部分
## Theme包含以下类型的资源:
## - 颜色(Color)
## - 常量(int)
## - 字体(Font)
## - 字体大小(int)
## - 图标(Texture2D)
## - 样式盒(StyleBox)
func _ready():
_explore_theme_system()
func _explore_theme_system():
# 获取当前主题
var current_theme = theme
if current_theme == null:
current_theme = get_theme()
print("=== 主题信息 ===")
# 获取主题中的类型列表
var types = current_theme.get_type_list() if current_theme else []
print("控件类型数量: ", types.size())
## 主题继承
## 控件的主题查找顺序:
## 1. 控件自身的theme_override_*
## 2. 控件的theme属性
## 3. 父节点的theme
## 4. 项目默认主题
func demonstrate_theme_inheritance():
var button = Button.new()
button.text = "测试按钮"
# 1. 直接覆盖(最高优先级)
button.add_theme_color_override("font_color", Color.RED)
# 2. 设置控件主题
var custom_theme = Theme.new()
custom_theme.set_color("font_color", "Button", Color.BLUE)
button.theme = custom_theme
# 注意:override优先于theme
add_child(button)
## 获取主题值
func get_theme_values(control: Control):
# 获取颜色
var font_color = control.get_theme_color("font_color", "Button")
# 获取常量
var margin = control.get_theme_constant("margin", "Panel")
# 获取字体
var font = control.get_theme_font("font", "Label")
# 获取字体大小
var font_size = control.get_theme_font_size("font_size", "Label")
# 获取图标
var icon = control.get_theme_icon("icon", "Button")
# 获取样式盒
var stylebox = control.get_theme_stylebox("panel", "Panel")
print("字体颜色: ", font_color)
print("边距: ", margin)
35.1.2 创建主题资源
# create_theme.gd
# 创建主题资源
extends Node
func create_custom_theme() -> Theme:
var theme = Theme.new()
# 设置默认字体
var default_font = load("res://fonts/main_font.ttf")
theme.default_font = default_font
theme.default_font_size = 16
# 设置Button样式
_setup_button_theme(theme)
# 设置Label样式
_setup_label_theme(theme)
# 设置Panel样式
_setup_panel_theme(theme)
# 设置LineEdit样式
_setup_line_edit_theme(theme)
return theme
func _setup_button_theme(theme: Theme):
# 颜色
theme.set_color("font_color", "Button", Color(0.9, 0.9, 0.9))
theme.set_color("font_hover_color", "Button", Color.WHITE)
theme.set_color("font_pressed_color", "Button", Color(0.8, 0.8, 0.8))
theme.set_color("font_disabled_color", "Button", Color(0.5, 0.5, 0.5))
theme.set_color("font_focus_color", "Button", Color.WHITE)
# 样式盒
var normal = _create_button_stylebox(Color(0.2, 0.2, 0.2))
var hover = _create_button_stylebox(Color(0.3, 0.3, 0.3))
var pressed = _create_button_stylebox(Color(0.15, 0.15, 0.15))
var disabled = _create_button_stylebox(Color(0.1, 0.1, 0.1))
var focus = _create_button_stylebox(Color(0.2, 0.2, 0.2))
focus.border_color = Color(0.4, 0.6, 1.0)
focus.border_width_left = 2
focus.border_width_right = 2
focus.border_width_top = 2
focus.border_width_bottom = 2
theme.set_stylebox("normal", "Button", normal)
theme.set_stylebox("hover", "Button", hover)
theme.set_stylebox("pressed", "Button", pressed)
theme.set_stylebox("disabled", "Button", disabled)
theme.set_stylebox("focus", "Button", focus)
# 常量
theme.set_constant("h_separation", "Button", 5)
func _create_button_stylebox(bg_color: Color) -> StyleBoxFlat:
var style = StyleBoxFlat.new()
style.bg_color = bg_color
style.corner_radius_top_left = 6
style.corner_radius_top_right = 6
style.corner_radius_bottom_left = 6
style.corner_radius_bottom_right = 6
style.content_margin_left = 15
style.content_margin_right = 15
style.content_margin_top = 8
style.content_margin_bottom = 8
return style
func _setup_label_theme(theme: Theme):
theme.set_color("font_color", "Label", Color(0.85, 0.85, 0.85))
theme.set_color("font_shadow_color", "Label", Color(0, 0, 0, 0))
theme.set_constant("shadow_offset_x", "Label", 1)
theme.set_constant("shadow_offset_y", "Label", 1)
theme.set_constant("line_spacing", "Label", 3)
func _setup_panel_theme(theme: Theme):
var panel_style = StyleBoxFlat.new()
panel_style.bg_color = Color(0.15, 0.15, 0.15)
panel_style.corner_radius_top_left = 8
panel_style.corner_radius_top_right = 8
panel_style.corner_radius_bottom_left = 8
panel_style.corner_radius_bottom_right = 8
theme.set_stylebox("panel", "Panel", panel_style)
theme.set_stylebox("panel", "PanelContainer", panel_style)
func _setup_line_edit_theme(theme: Theme):
theme.set_color("font_color", "LineEdit", Color(0.9, 0.9, 0.9))
theme.set_color("font_placeholder_color", "LineEdit", Color(0.5, 0.5, 0.5))
theme.set_color("caret_color", "LineEdit", Color.WHITE)
theme.set_color("selection_color", "LineEdit", Color(0.3, 0.5, 0.8, 0.5))
var normal = StyleBoxFlat.new()
normal.bg_color = Color(0.1, 0.1, 0.1)
normal.border_color = Color(0.3, 0.3, 0.3)
normal.border_width_left = 1
normal.border_width_right = 1
normal.border_width_top = 1
normal.border_width_bottom = 1
normal.corner_radius_top_left = 4
normal.corner_radius_top_right = 4
normal.corner_radius_bottom_left = 4
normal.corner_radius_bottom_right = 4
normal.content_margin_left = 8
normal.content_margin_right = 8
normal.content_margin_top = 6
normal.content_margin_bottom = 6
var focus = normal.duplicate()
focus.border_color = Color(0.4, 0.6, 1.0)
theme.set_stylebox("normal", "LineEdit", normal)
theme.set_stylebox("focus", "LineEdit", focus)
## 保存主题到文件
func save_theme(theme: Theme, path: String):
ResourceSaver.save(theme, path)
## 加载主题
func load_theme(path: String) -> Theme:
if ResourceLoader.exists(path):
return load(path) as Theme
return null
35.2 样式盒详解
35.2.1 StyleBoxFlat
# stylebox_flat.gd
# StyleBoxFlat详解
extends Control
func _ready():
_demonstrate_stylebox_flat()
func _demonstrate_stylebox_flat():
# StyleBoxFlat是最常用的样式盒
var style = StyleBoxFlat.new()
# === 背景 ===
style.bg_color = Color(0.2, 0.4, 0.6)
# === 边框 ===
style.border_color = Color(0.4, 0.6, 0.8)
style.border_width_left = 2
style.border_width_right = 2
style.border_width_top = 2
style.border_width_bottom = 2
style.border_blend = false # 边框颜色是否与背景混合
# === 圆角 ===
style.corner_radius_top_left = 10
style.corner_radius_top_right = 10
style.corner_radius_bottom_left = 10
style.corner_radius_bottom_right = 10
style.corner_detail = 8 # 圆角的平滑度
# === 内容边距 ===
style.content_margin_left = 10
style.content_margin_right = 10
style.content_margin_top = 8
style.content_margin_bottom = 8
# === 阴影 ===
style.shadow_color = Color(0, 0, 0, 0.3)
style.shadow_size = 5
style.shadow_offset = Vector2(2, 2)
# === 抗锯齿 ===
style.anti_aliasing = true
style.anti_aliasing_size = 1.0
# === 扩展边距 ===
style.expand_margin_left = 0
style.expand_margin_right = 0
style.expand_margin_top = 0
style.expand_margin_bottom = 0
# === 绘制中心 ===
style.draw_center = true # 是否绘制中心区域
# 应用到Panel
var panel = Panel.new()
panel.add_theme_stylebox_override("panel", style)
panel.custom_minimum_size = Vector2(200, 100)
add_child(panel)
## 创建渐变背景
func create_gradient_style() -> StyleBoxFlat:
# StyleBoxFlat不直接支持渐变
# 但可以通过着色器实现
var style = StyleBoxFlat.new()
style.bg_color = Color(0.2, 0.3, 0.5)
return style
## 创建玻璃效果
func create_glass_style() -> StyleBoxFlat:
var style = StyleBoxFlat.new()
style.bg_color = Color(1, 1, 1, 0.1)
style.border_color = Color(1, 1, 1, 0.3)
style.border_width_left = 1
style.border_width_right = 1
style.border_width_top = 1
style.border_width_bottom = 1
style.corner_radius_top_left = 12
style.corner_radius_top_right = 12
style.corner_radius_bottom_left = 12
style.corner_radius_bottom_right = 12
style.shadow_color = Color(0, 0, 0, 0.2)
style.shadow_size = 10
return style
## 创建凹陷效果
func create_inset_style() -> StyleBoxFlat:
var style = StyleBoxFlat.new()
style.bg_color = Color(0.1, 0.1, 0.1)
style.border_color = Color(0.05, 0.05, 0.05)
style.border_width_top = 2
style.border_width_left = 2
style.corner_radius_top_left = 4
style.corner_radius_top_right = 4
style.corner_radius_bottom_left = 4
style.corner_radius_bottom_right = 4
return style
35.2.2 StyleBoxTexture
# stylebox_texture.gd
# StyleBoxTexture纹理样式盒
extends Control
func create_texture_stylebox() -> StyleBoxTexture:
var style = StyleBoxTexture.new()
# 设置纹理
style.texture = preload("res://ui/panel_bg.png")
# 九宫格边距(不拉伸的区域)
style.texture_margin_left = 20
style.texture_margin_right = 20
style.texture_margin_top = 20
style.texture_margin_bottom = 20
# 拉伸模式
style.axis_stretch_horizontal = StyleBoxTexture.AXIS_STRETCH_MODE_STRETCH
style.axis_stretch_vertical = StyleBoxTexture.AXIS_STRETCH_MODE_STRETCH
# 可选模式:
# AXIS_STRETCH_MODE_STRETCH: 拉伸
# AXIS_STRETCH_MODE_TILE: 平铺
# AXIS_STRETCH_MODE_TILE_FIT: 适应平铺
# 绘制中心
style.draw_center = true
# 调制颜色
style.modulate_color = Color.WHITE
# 区域选择(纹理图集)
style.region_rect = Rect2(0, 0, 64, 64)
# 内容边距
style.content_margin_left = 15
style.content_margin_right = 15
style.content_margin_top = 10
style.content_margin_bottom = 10
return style
## 从图集创建多个样式
func create_styles_from_atlas(atlas: Texture2D, regions: Dictionary) -> Dictionary:
var styles = {}
for name in regions:
var style = StyleBoxTexture.new()
style.texture = atlas
style.region_rect = regions[name]
# 设置九宫格边距...
styles[name] = style
return styles
35.2.3 其他样式盒类型
# other_styleboxes.gd
# 其他样式盒类型
extends Control
## StyleBoxEmpty - 空样式盒
func create_empty_stylebox() -> StyleBoxEmpty:
var style = StyleBoxEmpty.new()
# 仅设置内容边距
style.content_margin_left = 10
style.content_margin_right = 10
style.content_margin_top = 5
style.content_margin_bottom = 5
return style
## StyleBoxLine - 线条样式盒
func create_line_stylebox() -> StyleBoxLine:
var style = StyleBoxLine.new()
style.color = Color(0.5, 0.5, 0.5)
style.thickness = 1
style.grow_begin = 0
style.grow_end = 0
style.vertical = false # 水平线
return style
## 综合示例:创建按钮样式集
func create_button_style_set() -> Dictionary:
return {
"normal": _create_button_normal(),
"hover": _create_button_hover(),
"pressed": _create_button_pressed(),
"disabled": _create_button_disabled(),
"focus": _create_button_focus()
}
func _create_button_normal() -> StyleBoxFlat:
var style = StyleBoxFlat.new()
style.bg_color = Color(0.25, 0.25, 0.25)
style.corner_radius_top_left = 5
style.corner_radius_top_right = 5
style.corner_radius_bottom_left = 5
style.corner_radius_bottom_right = 5
style.content_margin_left = 12
style.content_margin_right = 12
style.content_margin_top = 6
style.content_margin_bottom = 6
return style
func _create_button_hover() -> StyleBoxFlat:
var style = _create_button_normal()
style.bg_color = Color(0.35, 0.35, 0.35)
return style
func _create_button_pressed() -> StyleBoxFlat:
var style = _create_button_normal()
style.bg_color = Color(0.2, 0.2, 0.2)
return style
func _create_button_disabled() -> StyleBoxFlat:
var style = _create_button_normal()
style.bg_color = Color(0.15, 0.15, 0.15)
return style
func _create_button_focus() -> StyleBoxFlat:
var style = _create_button_normal()
style.border_color = Color(0.4, 0.6, 1.0)
style.border_width_left = 2
style.border_width_right = 2
style.border_width_top = 2
style.border_width_bottom = 2
return style
35.3 主题变体与类型
35.3.1 自定义控件类型
# custom_control_type.gd
# 自定义控件类型
extends Control
## 创建自定义控件的主题类型
func setup_custom_type(theme: Theme):
# 定义新的类型 "PrimaryButton"
_setup_primary_button(theme)
# 定义新的类型 "SecondaryButton"
_setup_secondary_button(theme)
# 定义新的类型 "DangerButton"
_setup_danger_button(theme)
func _setup_primary_button(theme: Theme):
var type_name = "PrimaryButton"
# 颜色
theme.set_color("font_color", type_name, Color.WHITE)
theme.set_color("font_hover_color", type_name, Color.WHITE)
theme.set_color("font_pressed_color", type_name, Color(0.9, 0.9, 0.9))
# 样式盒
var normal = StyleBoxFlat.new()
normal.bg_color = Color(0.2, 0.5, 0.8)
normal.corner_radius_top_left = 6
normal.corner_radius_top_right = 6
normal.corner_radius_bottom_left = 6
normal.corner_radius_bottom_right = 6
normal.content_margin_left = 20
normal.content_margin_right = 20
normal.content_margin_top = 10
normal.content_margin_bottom = 10
var hover = normal.duplicate()
hover.bg_color = Color(0.3, 0.6, 0.9)
var pressed = normal.duplicate()
pressed.bg_color = Color(0.15, 0.4, 0.7)
theme.set_stylebox("normal", type_name, normal)
theme.set_stylebox("hover", type_name, hover)
theme.set_stylebox("pressed", type_name, pressed)
func _setup_secondary_button(theme: Theme):
var type_name = "SecondaryButton"
theme.set_color("font_color", type_name, Color(0.9, 0.9, 0.9))
var normal = StyleBoxFlat.new()
normal.bg_color = Color(0.3, 0.3, 0.3)
normal.border_color = Color(0.5, 0.5, 0.5)
normal.border_width_left = 1
normal.border_width_right = 1
normal.border_width_top = 1
normal.border_width_bottom = 1
normal.corner_radius_top_left = 6
normal.corner_radius_top_right = 6
normal.corner_radius_bottom_left = 6
normal.corner_radius_bottom_right = 6
normal.content_margin_left = 20
normal.content_margin_right = 20
normal.content_margin_top = 10
normal.content_margin_bottom = 10
theme.set_stylebox("normal", type_name, normal)
func _setup_danger_button(theme: Theme):
var type_name = "DangerButton"
theme.set_color("font_color", type_name, Color.WHITE)
var normal = StyleBoxFlat.new()
normal.bg_color = Color(0.8, 0.2, 0.2)
normal.corner_radius_top_left = 6
normal.corner_radius_top_right = 6
normal.corner_radius_bottom_left = 6
normal.corner_radius_bottom_right = 6
normal.content_margin_left = 20
normal.content_margin_right = 20
normal.content_margin_top = 10
normal.content_margin_bottom = 10
var hover = normal.duplicate()
hover.bg_color = Color(0.9, 0.3, 0.3)
theme.set_stylebox("normal", type_name, normal)
theme.set_stylebox("hover", type_name, hover)
## 应用自定义类型
func apply_custom_type(button: Button, type_name: String):
button.theme_type_variation = type_name
35.3.2 主题变体
# theme_variations.gd
# 主题变体
extends Control
## 设置主题变体
## theme_type_variation允许控件使用不同的主题类型
func setup_button_variations():
var primary_btn = Button.new()
primary_btn.text = "主要按钮"
primary_btn.theme_type_variation = "PrimaryButton"
add_child(primary_btn)
var secondary_btn = Button.new()
secondary_btn.text = "次要按钮"
secondary_btn.theme_type_variation = "SecondaryButton"
add_child(secondary_btn)
var danger_btn = Button.new()
danger_btn.text = "危险按钮"
danger_btn.theme_type_variation = "DangerButton"
add_child(danger_btn)
## 动态切换变体
func switch_button_variation(button: Button, variation: String):
button.theme_type_variation = variation
## 创建变体切换器
func create_variation_demo() -> VBoxContainer:
var vbox = VBoxContainer.new()
var button = Button.new()
button.text = "可变按钮"
vbox.add_child(button)
var options = OptionButton.new()
options.add_item("默认", 0)
options.add_item("Primary", 1)
options.add_item("Secondary", 2)
options.add_item("Danger", 3)
options.item_selected.connect(func(idx):
match idx:
0: button.theme_type_variation = ""
1: button.theme_type_variation = "PrimaryButton"
2: button.theme_type_variation = "SecondaryButton"
3: button.theme_type_variation = "DangerButton"
)
vbox.add_child(options)
return vbox
35.4 主题管理系统
35.4.1 主题管理器
# theme_manager.gd
# 主题管理器单例
extends Node
## 可用主题
enum ThemeType {
LIGHT,
DARK,
HIGH_CONTRAST
}
var current_theme_type: ThemeType = ThemeType.DARK
var themes: Dictionary = {}
var root_control: Control
signal theme_changed(theme_type: ThemeType)
func _ready():
_initialize_themes()
func _initialize_themes():
themes[ThemeType.DARK] = _create_dark_theme()
themes[ThemeType.LIGHT] = _create_light_theme()
themes[ThemeType.HIGH_CONTRAST] = _create_high_contrast_theme()
func set_root_control(control: Control):
root_control = control
apply_theme(current_theme_type)
func apply_theme(theme_type: ThemeType):
if not themes.has(theme_type):
return
current_theme_type = theme_type
if root_control:
root_control.theme = themes[theme_type]
theme_changed.emit(theme_type)
func get_current_theme() -> Theme:
return themes.get(current_theme_type)
func toggle_theme():
var next_type: ThemeType
match current_theme_type:
ThemeType.DARK: next_type = ThemeType.LIGHT
ThemeType.LIGHT: next_type = ThemeType.HIGH_CONTRAST
ThemeType.HIGH_CONTRAST: next_type = ThemeType.DARK
apply_theme(next_type)
## 创建深色主题
func _create_dark_theme() -> Theme:
var theme = Theme.new()
# 背景色
var bg_primary = Color(0.12, 0.12, 0.14)
var bg_secondary = Color(0.18, 0.18, 0.2)
var bg_tertiary = Color(0.24, 0.24, 0.26)
# 前景色
var fg_primary = Color(0.95, 0.95, 0.95)
var fg_secondary = Color(0.7, 0.7, 0.7)
# 强调色
var accent = Color(0.3, 0.5, 0.9)
_apply_colors_to_theme(theme, bg_primary, bg_secondary, bg_tertiary, fg_primary, fg_secondary, accent)
return theme
## 创建浅色主题
func _create_light_theme() -> Theme:
var theme = Theme.new()
var bg_primary = Color(0.95, 0.95, 0.97)
var bg_secondary = Color(0.9, 0.9, 0.92)
var bg_tertiary = Color(0.85, 0.85, 0.87)
var fg_primary = Color(0.1, 0.1, 0.1)
var fg_secondary = Color(0.4, 0.4, 0.4)
var accent = Color(0.2, 0.4, 0.8)
_apply_colors_to_theme(theme, bg_primary, bg_secondary, bg_tertiary, fg_primary, fg_secondary, accent)
return theme
## 创建高对比度主题
func _create_high_contrast_theme() -> Theme:
var theme = Theme.new()
var bg_primary = Color.BLACK
var bg_secondary = Color(0.1, 0.1, 0.1)
var bg_tertiary = Color(0.2, 0.2, 0.2)
var fg_primary = Color.WHITE
var fg_secondary = Color(0.9, 0.9, 0.9)
var accent = Color.YELLOW
_apply_colors_to_theme(theme, bg_primary, bg_secondary, bg_tertiary, fg_primary, fg_secondary, accent)
return theme
func _apply_colors_to_theme(theme: Theme, bg1: Color, bg2: Color, bg3: Color, fg1: Color, fg2: Color, accent: Color):
# Panel
var panel_style = StyleBoxFlat.new()
panel_style.bg_color = bg1
panel_style.corner_radius_top_left = 8
panel_style.corner_radius_top_right = 8
panel_style.corner_radius_bottom_left = 8
panel_style.corner_radius_bottom_right = 8
theme.set_stylebox("panel", "Panel", panel_style)
theme.set_stylebox("panel", "PanelContainer", panel_style)
# Button
var btn_normal = StyleBoxFlat.new()
btn_normal.bg_color = bg3
btn_normal.corner_radius_top_left = 6
btn_normal.corner_radius_top_right = 6
btn_normal.corner_radius_bottom_left = 6
btn_normal.corner_radius_bottom_right = 6
btn_normal.content_margin_left = 15
btn_normal.content_margin_right = 15
btn_normal.content_margin_top = 8
btn_normal.content_margin_bottom = 8
var btn_hover = btn_normal.duplicate()
btn_hover.bg_color = bg3.lightened(0.1)
var btn_pressed = btn_normal.duplicate()
btn_pressed.bg_color = bg3.darkened(0.1)
theme.set_stylebox("normal", "Button", btn_normal)
theme.set_stylebox("hover", "Button", btn_hover)
theme.set_stylebox("pressed", "Button", btn_pressed)
theme.set_color("font_color", "Button", fg1)
theme.set_color("font_hover_color", "Button", fg1)
theme.set_color("font_pressed_color", "Button", fg2)
# Label
theme.set_color("font_color", "Label", fg1)
# LineEdit
var edit_style = StyleBoxFlat.new()
edit_style.bg_color = bg2
edit_style.border_color = bg3
edit_style.border_width_bottom = 2
edit_style.corner_radius_top_left = 4
edit_style.corner_radius_top_right = 4
edit_style.content_margin_left = 10
edit_style.content_margin_right = 10
edit_style.content_margin_top = 8
edit_style.content_margin_bottom = 8
var edit_focus = edit_style.duplicate()
edit_focus.border_color = accent
theme.set_stylebox("normal", "LineEdit", edit_style)
theme.set_stylebox("focus", "LineEdit", edit_focus)
theme.set_color("font_color", "LineEdit", fg1)
theme.set_color("font_placeholder_color", "LineEdit", fg2)
theme.set_color("caret_color", "LineEdit", fg1)
## 保存主题偏好
func save_preference():
var config = ConfigFile.new()
config.set_value("theme", "type", current_theme_type)
config.save("user://theme_preference.cfg")
## 加载主题偏好
func load_preference():
var config = ConfigFile.new()
if config.load("user://theme_preference.cfg") == OK:
var saved_type = config.get_value("theme", "type", ThemeType.DARK)
apply_theme(saved_type)
35.4.2 主题切换动画
# theme_transition.gd
# 主题切换动画
extends CanvasLayer
@onready var overlay: ColorRect = $Overlay
var tween: Tween
func _ready():
overlay.color = Color(0, 0, 0, 0)
overlay.mouse_filter = Control.MOUSE_FILTER_IGNORE
## 带动画的主题切换
func switch_theme_animated(new_theme_type: int, duration: float = 0.3):
overlay.mouse_filter = Control.MOUSE_FILTER_STOP
if tween:
tween.kill()
tween = create_tween()
# 淡入
tween.tween_property(overlay, "color:a", 1.0, duration / 2)
# 切换主题
tween.tween_callback(func():
ThemeManager.apply_theme(new_theme_type)
)
# 淡出
tween.tween_property(overlay, "color:a", 0.0, duration / 2)
# 恢复交互
tween.tween_callback(func():
overlay.mouse_filter = Control.MOUSE_FILTER_IGNORE
)
## 滑动切换效果
func switch_theme_slide(new_theme_type: int, direction: Vector2 = Vector2.RIGHT):
var screenshot = _capture_current_ui()
# 显示截图
var sprite = TextureRect.new()
sprite.texture = screenshot
sprite.set_anchors_preset(Control.PRESET_FULL_RECT)
add_child(sprite)
# 切换主题
ThemeManager.apply_theme(new_theme_type)
# 滑出动画
var tween = create_tween()
tween.tween_property(sprite, "position", direction * get_viewport().size, 0.4)
tween.set_trans(Tween.TRANS_QUAD)
tween.set_ease(Tween.EASE_IN)
tween.tween_callback(sprite.queue_free)
func _capture_current_ui() -> ImageTexture:
var image = get_viewport().get_texture().get_image()
return ImageTexture.create_from_image(image)
35.5 字体管理
35.5.1 字体配置
# font_manager.gd
# 字体管理
extends Node
var fonts: Dictionary = {}
func _ready():
_load_fonts()
func _load_fonts():
# 加载主字体
var main_font = load("res://fonts/NotoSansSC-Regular.ttf")
fonts["main"] = main_font
# 加载标题字体
var title_font = load("res://fonts/NotoSansSC-Bold.ttf")
fonts["title"] = title_font
# 加载代码字体
var code_font = load("res://fonts/JetBrainsMono-Regular.ttf")
fonts["code"] = code_font
func get_font(name: String) -> Font:
return fonts.get(name)
## 创建带有字体变体的FontVariation
func create_font_variation(base_font: Font, bold: bool = false, italic: bool = false) -> Font:
var variation = FontVariation.new()
variation.base_font = base_font
# 设置OpenType特性
if bold:
variation.variation_opentype = {"wght": 700}
if italic:
variation.variation_opentype["slnt"] = -12
return variation
## 设置主题字体
func apply_fonts_to_theme(theme: Theme):
theme.default_font = fonts["main"]
theme.default_font_size = 16
# 标题字体
theme.set_font("font", "HeaderLabel", fonts["title"])
theme.set_font_size("font_size", "HeaderLabel", 24)
# 代码字体
theme.set_font("font", "CodeEdit", fonts["code"])
theme.set_font_size("font_size", "CodeEdit", 14)
35.5.2 动态字体大小
# dynamic_font_size.gd
# 动态字体大小
extends Control
@export_range(0.5, 2.0) var font_scale: float = 1.0:
set(value):
font_scale = value
_apply_font_scale()
var base_font_sizes: Dictionary = {}
func _ready():
_store_base_sizes()
func _store_base_sizes():
base_font_sizes = {
"small": 12,
"normal": 16,
"large": 20,
"header": 24,
"title": 32
}
func _apply_font_scale():
if not theme:
return
for size_name in base_font_sizes:
var base_size = base_font_sizes[size_name]
var scaled_size = int(base_size * font_scale)
# 应用到主题
match size_name:
"normal":
theme.default_font_size = scaled_size
theme.set_font_size("font_size", "Label", scaled_size)
theme.set_font_size("font_size", "Button", scaled_size)
"small":
theme.set_font_size("font_size", "SmallLabel", scaled_size)
"large":
theme.set_font_size("font_size", "LargeLabel", scaled_size)
"header":
theme.set_font_size("font_size", "HeaderLabel", scaled_size)
"title":
theme.set_font_size("font_size", "TitleLabel", scaled_size)
## 根据屏幕大小自动调整
func auto_adjust_font_scale():
var viewport_size = get_viewport_rect().size
var base_width = 1920.0
font_scale = clamp(viewport_size.x / base_width, 0.7, 1.3)
35.6 实际案例:完整UI主题
# complete_theme.gd
# 完整UI主题示例
extends Node
func create_game_theme() -> Theme:
var theme = Theme.new()
# 字体
_setup_fonts(theme)
# 颜色方案
var colors = _define_color_scheme()
# 应用到所有控件
_setup_common_controls(theme, colors)
_setup_button_styles(theme, colors)
_setup_input_styles(theme, colors)
_setup_container_styles(theme, colors)
_setup_popup_styles(theme, colors)
_setup_progress_styles(theme, colors)
return theme
func _define_color_scheme() -> Dictionary:
return {
"background": Color(0.1, 0.1, 0.12),
"surface": Color(0.15, 0.15, 0.18),
"surface_light": Color(0.2, 0.2, 0.24),
"primary": Color(0.3, 0.5, 0.9),
"primary_dark": Color(0.2, 0.4, 0.8),
"secondary": Color(0.4, 0.4, 0.45),
"success": Color(0.2, 0.7, 0.3),
"warning": Color(0.9, 0.7, 0.2),
"error": Color(0.8, 0.2, 0.2),
"text_primary": Color(0.95, 0.95, 0.95),
"text_secondary": Color(0.7, 0.7, 0.7),
"text_disabled": Color(0.4, 0.4, 0.4),
"border": Color(0.3, 0.3, 0.35),
"shadow": Color(0, 0, 0, 0.3),
}
func _setup_fonts(theme: Theme):
var main_font = load("res://fonts/main.ttf")
theme.default_font = main_font
theme.default_font_size = 16
func _setup_common_controls(theme: Theme, colors: Dictionary):
# Label
theme.set_color("font_color", "Label", colors.text_primary)
theme.set_color("font_shadow_color", "Label", Color.TRANSPARENT)
# RichTextLabel
theme.set_color("default_color", "RichTextLabel", colors.text_primary)
func _setup_button_styles(theme: Theme, colors: Dictionary):
var base_style = StyleBoxFlat.new()
base_style.corner_radius_top_left = 6
base_style.corner_radius_top_right = 6
base_style.corner_radius_bottom_left = 6
base_style.corner_radius_bottom_right = 6
base_style.content_margin_left = 16
base_style.content_margin_right = 16
base_style.content_margin_top = 10
base_style.content_margin_bottom = 10
# Normal Button
var btn_normal = base_style.duplicate()
btn_normal.bg_color = colors.surface_light
var btn_hover = base_style.duplicate()
btn_hover.bg_color = colors.surface_light.lightened(0.1)
var btn_pressed = base_style.duplicate()
btn_pressed.bg_color = colors.surface_light.darkened(0.1)
var btn_disabled = base_style.duplicate()
btn_disabled.bg_color = colors.surface
var btn_focus = base_style.duplicate()
btn_focus.bg_color = colors.surface_light
btn_focus.border_color = colors.primary
btn_focus.border_width_left = 2
btn_focus.border_width_right = 2
btn_focus.border_width_top = 2
btn_focus.border_width_bottom = 2
theme.set_stylebox("normal", "Button", btn_normal)
theme.set_stylebox("hover", "Button", btn_hover)
theme.set_stylebox("pressed", "Button", btn_pressed)
theme.set_stylebox("disabled", "Button", btn_disabled)
theme.set_stylebox("focus", "Button", btn_focus)
theme.set_color("font_color", "Button", colors.text_primary)
theme.set_color("font_hover_color", "Button", colors.text_primary)
theme.set_color("font_pressed_color", "Button", colors.text_secondary)
theme.set_color("font_disabled_color", "Button", colors.text_disabled)
# Primary Button
var primary_normal = base_style.duplicate()
primary_normal.bg_color = colors.primary
var primary_hover = base_style.duplicate()
primary_hover.bg_color = colors.primary.lightened(0.1)
var primary_pressed = base_style.duplicate()
primary_pressed.bg_color = colors.primary_dark
theme.set_stylebox("normal", "PrimaryButton", primary_normal)
theme.set_stylebox("hover", "PrimaryButton", primary_hover)
theme.set_stylebox("pressed", "PrimaryButton", primary_pressed)
theme.set_color("font_color", "PrimaryButton", Color.WHITE)
func _setup_input_styles(theme: Theme, colors: Dictionary):
# LineEdit
var edit_normal = StyleBoxFlat.new()
edit_normal.bg_color = colors.surface
edit_normal.border_color = colors.border
edit_normal.border_width_left = 1
edit_normal.border_width_right = 1
edit_normal.border_width_top = 1
edit_normal.border_width_bottom = 1
edit_normal.corner_radius_top_left = 4
edit_normal.corner_radius_top_right = 4
edit_normal.corner_radius_bottom_left = 4
edit_normal.corner_radius_bottom_right = 4
edit_normal.content_margin_left = 10
edit_normal.content_margin_right = 10
edit_normal.content_margin_top = 8
edit_normal.content_margin_bottom = 8
var edit_focus = edit_normal.duplicate()
edit_focus.border_color = colors.primary
edit_focus.border_width_left = 2
edit_focus.border_width_right = 2
edit_focus.border_width_top = 2
edit_focus.border_width_bottom = 2
theme.set_stylebox("normal", "LineEdit", edit_normal)
theme.set_stylebox("focus", "LineEdit", edit_focus)
theme.set_color("font_color", "LineEdit", colors.text_primary)
theme.set_color("font_placeholder_color", "LineEdit", colors.text_secondary)
theme.set_color("caret_color", "LineEdit", colors.text_primary)
theme.set_color("selection_color", "LineEdit", Color(colors.primary.r, colors.primary.g, colors.primary.b, 0.3))
func _setup_container_styles(theme: Theme, colors: Dictionary):
# Panel
var panel_style = StyleBoxFlat.new()
panel_style.bg_color = colors.surface
panel_style.corner_radius_top_left = 8
panel_style.corner_radius_top_right = 8
panel_style.corner_radius_bottom_left = 8
panel_style.corner_radius_bottom_right = 8
panel_style.shadow_color = colors.shadow
panel_style.shadow_size = 4
panel_style.shadow_offset = Vector2(0, 2)
theme.set_stylebox("panel", "Panel", panel_style)
theme.set_stylebox("panel", "PanelContainer", panel_style)
# TabContainer
var tab_panel = panel_style.duplicate()
theme.set_stylebox("panel", "TabContainer", tab_panel)
# Separator
theme.set_color("color", "HSeparator", colors.border)
theme.set_color("color", "VSeparator", colors.border)
func _setup_popup_styles(theme: Theme, colors: Dictionary):
var popup_style = StyleBoxFlat.new()
popup_style.bg_color = colors.surface
popup_style.border_color = colors.border
popup_style.border_width_left = 1
popup_style.border_width_right = 1
popup_style.border_width_top = 1
popup_style.border_width_bottom = 1
popup_style.corner_radius_top_left = 8
popup_style.corner_radius_top_right = 8
popup_style.corner_radius_bottom_left = 8
popup_style.corner_radius_bottom_right = 8
popup_style.shadow_color = colors.shadow
popup_style.shadow_size = 8
popup_style.shadow_offset = Vector2(0, 4)
theme.set_stylebox("panel", "PopupPanel", popup_style)
theme.set_stylebox("panel", "PopupMenu", popup_style)
func _setup_progress_styles(theme: Theme, colors: Dictionary):
# ProgressBar
var progress_bg = StyleBoxFlat.new()
progress_bg.bg_color = colors.surface
progress_bg.corner_radius_top_left = 4
progress_bg.corner_radius_top_right = 4
progress_bg.corner_radius_bottom_left = 4
progress_bg.corner_radius_bottom_right = 4
var progress_fill = StyleBoxFlat.new()
progress_fill.bg_color = colors.primary
progress_fill.corner_radius_top_left = 4
progress_fill.corner_radius_top_right = 4
progress_fill.corner_radius_bottom_left = 4
progress_fill.corner_radius_bottom_right = 4
theme.set_stylebox("background", "ProgressBar", progress_bg)
theme.set_stylebox("fill", "ProgressBar", progress_fill)
theme.set_color("font_color", "ProgressBar", colors.text_primary)
35.7 本章小结
本章详细介绍了Godot 4的主题与样式系统:
- 主题系统概述:主题组成、继承机制、值获取
- 样式盒详解:StyleBoxFlat、StyleBoxTexture及其属性
- 主题变体与类型:自定义控件类型、主题变体
- 主题管理系统:主题管理器、切换动画
- 字体管理:字体配置、动态字体大小
- 完整主题示例:游戏UI主题实现
掌握主题系统能够创建统一、专业的UI视觉风格,并支持主题切换功能。下一章我们将学习UI动画,为界面添加生动的交互效果。