Skip to main content

Indie game storeFree gamesFun gamesHorror games
Game developmentAssetsComics
SalesBundles
Jobs
TagsGame Engines
(+1)

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.