My game has a few different fonts, and in order to quickly switch between them and change custom_minimum_size to adapt to them, I added a function that does exactly that.
@export var font_style: Styles:
set(value):
font_style = value
update_font_style() # load the fonts
match value:
Styles.DIALOG:
custom_minimum_size = MIN_SIZE_DIALOG
Styles.DIALOG_SMALL:
custom_minimum_size = MIN_SIZE_DIALOG
Styles.MENU:
custom_minimum_size = MIN_SIZE_MENU
Styles.TITLE_CARD:
custom_minimum_size = MIN_SIZE_TITLE_CARD
if font_style == Styles.DIALOG or font_style == Styles.DIALOG_SMALL:
text_color = ColorPresets.REGULAR
elif font_style == Styles.TITLE_CARD:
text_color = ColorPresets.GOLD
However, while this is really handy when I need to change the font style, it’s not so handy when my labels change their minimum size after a reload. So how can I say something like “I want you to change minimum size when you don’t have a custom font resource or if you have one but it’s not the same as the one you already have loaded”? I tried if not has_theme_font_override("font"): update_font_style(), but that doesn’t work since when the node is initialized, it doesn’t have a font loaded either way
Is this an editor script? Do you change font_style at run time? Maybe you can add if Engine.is_editor_hint() if you do only use this export for changing in-editor?
I know it’s common enough to use await ready to delay setters until the node is in the scene tree, maybe you could check if get_parent() returns null to determine if it isn’t in the scene.
Then maybe await ready is what you want, maybe sharing update_font_style() would help clarify what data you are dependent on, and what exactly is going wrong vs what you expect
it should be non-null, to see it in the scene at all get_parent must be non-null. But nodes aren’t yet in the scene at the time of @export’s initialization, at least as I understand it get_parent() may return null at that time, could be wrong.
There’s a common idiom for setters that need to run some code only if some initialization condition is met. It’s typically done for ready state but can be used for any custom state.
In the setter, run the dependent code only if condition is met. When some other part of the code initializes the condition - call the setter explicitly immediately afterwards.
@export var foo:
set(value):
foo = value
if is_node_ready():
# do dependent stuff
func _ready():
foo = foo
Didn’t it work this way until some update? Or did I get it wrong? Because I explicitly have font_size set an extra time in _ready() and I think I wouldn’t’ve had it if it always worked the way it does now
Edit: okay wait this works in the editor but not in the game for some reason
Okay so I have this stupid setup where instead of instancing those labels, I just have them saved as a class so I can spawn a new one without having to worry about how many of their properties are unique and how many are not
Here’s what happens in _ready():
func _ready() -> void:
if !(material is PaletteMaterial):
material = PaletteMaterial.new()
material.shader = load("res://resources/shaders/palette-swap-shader.gdshader")
var ref_palette := load("res://assets/gfx/ui/font/font-palette-gold.png") as CompressedTexture2D
material.set_shader_parameter("source_palette", ref_palette)
add_theme_constant_override("line_spacing", 0)
mouse_filter = Control.MOUSE_FILTER_IGNORE
#update_font_style()
update_color()
With the update_font_style() line commented, it doesn’t really affect the result in any way, so I’m still stumped as to what’s happening. Is the minimum size just not saved because it’s technically a completely new node every time? If so, why are the properties saved?
Also I tried setting their minimum size with custom_minimum_size = Vector2(min_size.x, min_size.y * get_line_count() + offset), but for some reason it doesn’t return line count correctly in some cases and returns character count instead? Why is that?
It’s kinda both a scene and a script but I think I only ever spawn them with CustomLabel.new() (idk how it actually happens in the editor). Basically I never use the Instantiate Child Scene button for them.
In every scene where CustomLabels are used? Like the options menu, pause screen, etc
@tool
class_name CustomLabel
extends Label
enum Styles {
DIALOG,
DIALOG_SMALL,
MENU,
TITLE_CARD,
}
const MIN_SIZE_DIALOG := Vector2(8, 8)
const MIN_SIZE_MENU := Vector2(16, 16)
const MIN_SIZE_TITLE_CARD := Vector2(32, 32)
@export var font_style: Styles:
set(value):
font_style = value
if is_node_ready():
update_font_style()
set_min_size()
enum ColorPresets {
REGULAR, ## White palette. Used by regular labels and unselected buttons.
YELLOW, ## Yellow palette. Used by focused buttons.
RED, ## Glossy red palette. Used by start menu buttons and warning labels.
GOLD, ## Fancy golden palette. Mostly used by the title card font.
}
@export var text_color: ColorPresets: ## The palette used by the text.
set(value):
text_color = value
update_color()
func _ready() -> void:
if !(material is PaletteMaterial):
material = PaletteMaterial.new()
material.shader = load("res://resources/shaders/palette-swap-shader.gdshader")
var ref_palette := load("res://assets/gfx/ui/font/font-palette-gold.png") as CompressedTexture2D
material.set_shader_parameter("source_palette", ref_palette)
update_color()
add_theme_constant_override("line_spacing", 0)
mouse_filter = Control.MOUSE_FILTER_IGNORE
func update_font_style(style := font_style) -> void:
var font: FontFile
match style:
Styles.DIALOG:
font = load("res://assets/gfx/ui/font/font-dialog.png")
Styles.DIALOG_SMALL:
font = load("res://assets/gfx/ui/font/font-dialog-small.png")
Styles.MENU:
font = load("res://assets/gfx/ui/font/font-menu.png")
Styles.TITLE_CARD:
font = load("res://assets/gfx/ui/font/font-titlecard.png")
add_theme_font_override("font", font)
if font_style == Styles.DIALOG or font_style == Styles.DIALOG_SMALL:
text_color = ColorPresets.REGULAR
elif font_style == Styles.TITLE_CARD:
text_color = ColorPresets.GOLD
if font_style == Styles.DIALOG:
uppercase = false
else:
uppercase = true
func set_min_size() -> void:
var min_size: Vector2
var paragraph_spacing: int
match font_style:
Styles.DIALOG:
min_size = MIN_SIZE_DIALOG
Styles.DIALOG_SMALL:
min_size = MIN_SIZE_DIALOG
Styles.MENU:
min_size = MIN_SIZE_MENU
Styles.TITLE_CARD:
min_size = MIN_SIZE_TITLE_CARD
if font_style != Styles.TITLE_CARD:
paragraph_spacing = 4
else:
paragraph_spacing = 0
custom_minimum_size = Vector2(min_size.x, min_size.y + paragraph_spacing)
func update_color(color := text_color) -> void:
if !(material is PaletteMaterial):
return
var palette: CompressedTexture2D
match color:
ColorPresets.REGULAR:
palette = load("res://assets/gfx/ui/font/font-palette-white.png")
ColorPresets.YELLOW:
palette = load("res://assets/gfx/ui/font/font-palette-yellow.png")
ColorPresets.RED:
palette = load("res://assets/gfx/ui/font/font-palette-red.png")
ColorPresets.GOLD:
palette = load("res://assets/gfx/ui/font/font-palette-gold.png")
var palette_image := palette.get_image()
for x in palette_image.get_width():
material.set_shader_parameter("color" + str(x), Color(palette_image.get_pixel(x, 0)))
func get_text_length_pixels() -> int:
var text_length_pixels := 0
var text_displayed := text.to_upper()
for i in text_displayed:
text_length_pixels += get_theme_font("font").get_char_size(ord(i), 16).x
return text_length_pixels