Actually, I went ahead and had a conversation with some AI so it could output a super generic guide to probably better explain what I'm talking about, in full, and in order, and with descriptive variables wrapped in square brackets. I was just steam of consciousness typing my last reply. 😅 This should *actually* help you do this. I read it over and I think it's right.
1. class_name — making a script globally available
When you put class_name [YourEffectName] at the top of a GDScript file, Godot registers it project-wide. Every other script can use [YourEffectName] by name with no import needed.
class_name [YourEffectName] extends RichTextEffect # Now any script in the project can write: # [YourEffectName].new()
Without class_name you'd have to use load("res://[path/to/your/file].gd").new() every time. class_name skips all of that.
2. RichTextEffect — what it is and how Godot uses it
Godot's RichTextLabel node has a built-in system for per-character visual effects. You plug in any number of effects and Godot calls them automatically every frame for every character.
To create one, extend RichTextEffect and implement one function:
extends RichTextEffect var bbcode = "[your_tag]" # the tag this effect listens for func _process_custom_fx(char_fx: CharFXTransform) -> bool: # Godot calls this every frame for every character inside [[your_tag]]...[[/your_tag]] # char_fx gives you: index, color, offset, and more # whatever you do here IS what appears on screen return true
3. The bbcode variable — which characters get the effect
The bbcode variable tells Godot which tag to listen for. If you set it to "[your_tag]", only characters wrapped in [[your_tag]]...[[/your_tag]] get processed.
# In your effect script: var bbcode = "[your_tag]" # In your label text: label.text = "Normal text [[your_tag]]this gets the effect[[/your_tag]] normal again"
Setting bbcode = "" applies the effect to all characters with no tag needed. Built-in tags like [b], [color], [i] are unaffected — they run independently and stack fine.
4. custom_effects — registering effects on a label
custom_effects is a built-in array on every RichTextLabel. It ships empty. You fill it with instances of your effects and Godot runs all of them every frame.
# In [YourDialogueScript] or wherever you control the label: @onready var [your_label_variable]: RichTextLabel = $[YourPanelNode]/[YourLabelNode] func _ready() -> void: [your_label_variable].custom_effects = [ [YourFirstEffect].new(), [YourSecondEffect].new(), [YourThirdEffect].new() ]
All registered effects are active. Which ones actually do anything depends on whether the text contains their BBCode tag.
5. @onready — safely referencing child nodes
Node references can only be resolved once the scene tree is ready. @onready delays the assignment until that moment automatically.
# This is shorthand for assigning in _ready(): @onready var [your_label_variable]: RichTextLabel = $[YourPanelNode]/[YourLabelNode] # It's exactly equivalent to writing: var [your_label_variable]: RichTextLabel func _ready() -> void: [your_label_variable] = $[YourPanelNode]/[YourLabelNode]
The variable name ([your_label_variable]) is your choice. The node path ($[YourPanelNode]/[YourLabelNode]) must match the actual scene tree structure.
6. Combining multiple effects
Because each effect has its own BBCode tag, you can stack them freely on the same text:
[your_label_variable].text = "[[first_tag]][[second_tag]]both effects here[[/second_tag]] only first here[[/first_tag]]"
Godot runs each registered effect on each character. A character inside both tags gets both effects applied.
One file per effect is the right structure. Don't try to bundle multiple effects into one file — you can only declare one bbcode variable per class, and GDScript only allows one class_name per file.
7. The full plug-and-play setup
1
Create effect files — one per effect, each with class_name [YourEffectName], var bbcode = "[your_tag]", and _process_custom_fx().
2
Register them in [YourDialogueScript] — add all effects to [your_label_variable].custom_effects in _ready().
3
Use tags in text — wrap any text in [[your_tag]]..[[/your_tag]] and the effect runs automatically.
4
Adding a new effect — create the file, add one line to custom_effects. Nothing else needs to change.
8. What "boilerplate" means here
Boilerplate is reusable code that solves a general problem with no project-specific logic in it. A well-written [YourEffectName] qualifies — it knows nothing about your game, just how to transform characters visually. Drop it into any Godot project and it works.
A dialogue box script is boilerplate at its core (entry cycling, timing, dismiss fade) but often has project-specific parts on top (portrait modes, character-specific flags). Strip those out and the skeleton becomes reusable too.