5 minute read

If you’ve done much with custom UI in Godot, you may have noticed a nasty little side effect some components, such as the OptionButton, come with: a lack of transparency on corners. Sure, you can create such a style, but it typically doesn’t actually render as you’d expect.

There’s a few bug reports and proposals about this on GitHub, and it does seem to be on the radar, but I needed some way to make it work with Unto Deepest Depths, which not only is built with 4.2, but also had a planned demo release in September (which was hit!), so I couldn’t wait for a new engine version. I did figure out a funky little way to fix it, though.

If you look at the Godot docs, when a user clicks on an OptionButton, it displays a PopupMenu containing the list of options. This menu has Viewport as an ancestor, which has a transparent_bg property. When this property is set to true, the viewport will render its background as transparent, and a quick test confirms that getting a reference to the PopupMenu on an OptionButton and setting this property does indeed fix the rendering issue:

my_option_button.get_popup().transparent_bg = true

So there’s the fix, but it is a bit unwieldy. You now have to have every option button apply this to its popup menu. Each button does seem to at least only generate one popup menu and then keep it for the life of the button, so one option is to create a custom class and always have your option buttons inherit from it:

class_name TransparentOptionButton
extends OptionButton

func _ready() -> void:
    get_popup().transparent_bg = true

This is better, but that still means every option button has to have this script, or a child script, on it. Plus, while I don’t think PopupMenus are used elsewhere in the UI, I’m not actually 100% sure of that. It’s not a terrible fix, but I wanted something a bit more thorough.

So I went with something a little funkier, but much easier to manage: Make an autoloaded script that connects to the node_added signal of the scene tree and automatically apply a transparent background to any PopupMenu that is added to it.

extends Node

func _enter_tree() -> void:
	get_tree().node_added.connect(on_node_added)

func on_node_added(node: Node) -> void:
	var pp := node as PopupMenu
	if pp:
		pp.transparent_bg = true

node_added is emitted anytime a node enters the scene tree, so for every node that is added, I try and cast it to a PopupMenu. If it succeeds, the variable will be properly set, and therefore truthy, and I can set transparent_bg = true. If casting fails, the variable will be set to null, and therefore falsey, and that’s the end of its time in the function.

Now, every PopupMenu in the game is automatically configured to have a transparent background when used. It’s not exactly great that this code has to run on every node, but my testing shows that this check takes about a microsecond, and sometimes less, to run, so I can process a lot of nodes in a single frame before causing any appreciable performance impact.

It’s all tradeoffs, but this way I don’t have to worry about forgetting to add a script somewhere.

And that’s how I gave myself transparent PopupMenus in Godot.