Mini devlog: Transparent menus in Unto Deepest Depths
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.