Skip to content

Khemitron Industries

Amateur Game Dev, Gaming, and TTRPGs

Menu
  • Home
  • Game Dev
  • About
Menu

Quality of Life Improvements – Nested Tooltips

Posted on 2025-05-12

This week, I had a big distraction to my gamedev time in the form of Clair Obscur: Expedition 33, which is a fantastic game. I want nothing more than to dedicate all my free time to playing it, and then the Oblivion Remaster afterwards. However, I have managed to resist long enough to spend some time building a nested tooltip system for my game.

Let’s start with nested tooltips.

What is a nested tooltip?

We’re all familiar with the idea of a tooltip in both games and other software. You hover your mouse over a certain area and a little window pops up with a bit more information. Maybe this is used to provide a summary of a skill from its name. Or perhaps it’s used to describe a key concept, such as “Poison” or “Exhaust.”

Slay the Spire displays tooltips explaining keywords when the player hovers their mouse over a card

A tooltip is a way to give the player extra information without cluttering up the screen too much.

Steam’s achievements panel has a tooltip appear when the user hovers their mouse over an achievement icon

But what happens when there’s another keyword on the first tooltip? How can the player interact with that?

Another one from Slay the Spire. The Jaw Worm is aggressive and intends to Attack – another keyword

And this is where the concept of a nested tooltip comes in.

A nested tooltip is a tooltip within another tooltip. Perhaps the user can keep the first tooltip open while their mouse cursor is over the top of it, and then hover over another keyword. Or maybe the player can press a button to keep the tooltip open, even if they move the mouse away.

Old World allows the player to “Freeze” a tooltip by shift + clicking on any of the gold words

However it’s managed, the idea here is the that player can drill down into the specifics of any of the game’s concepts, without needing to look up the details outside of the main game.

Baldur’s Gate 3 lets the player hold open a tooltip by pressing ‘T’ (for keyboard and mouse players). From there, you can open one additional tooltip

Since my game is based on Pathfinder 2E, there will be quite a few different keywords. Having an easy way to check what each one means is vital to learning how the game works.

Building a nested tooltip solution

With this in mind, I decided that setting up tooltips which can support multiple layers of nesting was a good next step for my game’s development.

My goals for this system are as follows:

  • It should be obvious which words are keywords or ability names
  • The player should be able to open a tooltip from any keyword or ability name
  • Tooltips should remain open when the player clicks on them
  • Tooltips should try not to overlap each other so that the player can compare information
  • The player should be able to close specific tooltips
  • There should be a way to close all the tooltips at once

Highlighting keywords

I started by taking a look at Godot’s Rich Text Label node. I’d seen that these nodes can parse BBCode, which is somewhat similar to HTML. Checking Godot’s documentation, I spotted that URL tags will produce underlined, clickable text. This is exactly what I need.

In this prototype tooltip, stride and strike are within url tags

The documentation also says that the url link doesn’t have to be a hyperlink – it can be json data. So, the text for my Sudden Charge tooltip looks like this:

[url={"Keyword": "stride"}]Stride[/url] twice. If you end your movement within melee range of an enemy, you may make a [url={"Keyword": "strike"}]strike[/url] against them.

When the player clicks on “Stride” or “strike” an event is emitted with the appropriate url metadata. It is then trivial to write some code to extract the keyword.

public class KeywordMeta
{
	public string Keyword { get; set; }
}

// on component with a rich text label
private void OnMetaClicked(Variant meta)
{
	var data = JsonSerializer.Deserialize<KeywordMeta>((string)meta);
	GD.Print(data.Keyword); // prints keyword (e.g. "stride")
	// this should be replaced with the function that 
	// creates the new tooltip
}

With our keyword in hand, we can look up the matching tooltip title and text and then display that to the player.

Where to place the new tooltip?

Working out where to place the new tooltip is a surprisingly tricky proposition. There are three main requirements here. The new tooltip should be placed such that:

  • it is next to its parent, but does not overlap
  • it does not overlap any other tooltips
  • the whole tooltip is visible on screen

My approach to this was to check if there’s space for the tooltip next to its parent on each side. I started by checking to the right and then moving clockwise (right > below > left > above). Since I’ve given my tooltips a fixed width, checking right or left was relatively straightforward.

Unfortunately, this didn’t work quite so well for above or below because it takes a few frames for the Rich Text Label to calculate its size.

This late size calculation means that the tooltip initially thinks it’s only a few pixels tall. So my initial algorithm for tooltip placement thought there was enough space for the tooltip. However, after a couple of frames, the Rich Text Label would finish calculating its size and then cause the tooltip to grow in size. This then meant that part of the tooltip was being pushed off screen, which is not ideal.

To counter this, I had to hide the tooltip and put in a pause while it reached its proper size before calculating the position.

In C# code, that pause looks like this:

// waits for 5 frames
for (var i = 0; i < 5; i++)
{
	await ToSignal(GetTree(), SceneTree.SignalName.ProcessFrame);
}

I’ve found that 5 frames is enough so far, but it’s easy to increase the pause if it’s not enough on slower systems.

Avoiding overlaps

With the tooltip invisible, but now the correct size, I just had to figure out where to place it. My first algorithm for doing so was simple – calculate the position for the new tooltip right of the parent and check if there’s already a tooltip at that position. If so, repeat for below, then left, then right. If there are tooltips in all these positions, ignore the overlap and just place the tooltip where it will fit.

This actually works surprisingly well.

The screen resolution used to test this is smaller than the smallest resolution I intend to support (1200 x 800)

As you can see, there are at least 10 tooltips that spawn (including the parent) before any overlap occurs. It is unlikely that players are going to open up that many tooltips, so I think this algorithm is absolutely fine for my game. Yes, I could tweak it to use the space on the screen better – e.g. it could fill the gap in the top middle of the screen before overlapping. However, as I said, this example is a smaller resolution than I intend to support, and I think the majority of players won’t open 10 or more tooltips without closing some.

I’m sure I’ll be proven wrong by some enterprising player with a tooltip fetish, but I hope that’ll be an outlier.

Importing it into the game

Now that I had a rough solution, it was time to import it into my game. I tidied up the code and added a check for duplicate tooltips – checking if a tooltip with that title already exists. A future goal will be to highlight the existing tooltip if this happens.

So that tooltips are always displayed on top of everything else, I set up the tooltip renderer as a Canvas layer within the main game scene, just below the loading screen.

To allow any component to add or remove a tooltip, I’ve set up a tooltip event bus that has the sole purpose of listening for tooltip requests and passing them on to the tooltip renderer.

I then spent far too long trying to style the tooltip and ended up with something that I quite like.

It will probably need tweaking

And with that done, I decided to move on to something else for a bit.

Further improvements

While I’m happy with the nested tooltips so far, there are definitely some improvements to be made.

  • Make the keywords a different colour to the rest of the text. This is actually quite easy to do, I’ve just not got around to it yet
  • Change the default tooltip behaviour to display on hover with a pin button, or specific button press to stop it closing when the player moves the mouse away. I need to explore Godot’s built in tooltip system a bit more to see if I can adapt it to achieve this goal
  • Close all the tooltips if the player clicks somewhere other than on a tooltip, or potentially with a close all tooltips button
  • Allow the player to move open tooltips around. This is more like a “stretch goal” – it would be really nice to have, but is definitely not as essential as the first two points are

Next steps

As is customary on my devlogs, it’s time to turn my attention to what I’d like to accomplish in the coming week.

This week, I want to turn my attention to the saving and loading experience.

Currently, the player can click “Save Game” and it automatically saves the game as “test.json” in a save game folder. Clicking “Load Game” just looks for “test.json” and loads it. This is fine for testing purposes, but definitely needs to change.

So I’d like to have:

  • A proper save game screen where the player can enter the name for the saved game
  • A load game screen where the player can see their saved games and choose which one to load
  • A button on the in-game menu that lets the player quit to the main menu

That sounds achievable in a week.

So long as I don’t get too distracted by Clair Obscur. Why must other games be so good!?

Share on Social Media
facebook tumblr reddit emailwhatsapp

Like this:

Like Loading...

Recent Posts

  • Refactoring the World Map 2025-06-09
  • Using Themes to Enhance my Game 2025-05-19
  • Quality of Life Improvements – Nested Tooltips 2025-05-12
  • Improving Quality of Life 2025-05-05
  • Godot4.4 – Powerful Localisation with gettext 2025-04-27
©2025 Khemitron Industries | Design: Newspaperly WordPress Theme
%d