Determining the UI position via world-to-screen projection

Godot Version

4.6

Question

Hello,

I have a question about UI and “ingame” elements.

In many games, text is displayed when the some entites are hurt, for example. Sometimes we want to display some text or icon close to a world entity.

I’m trying to do this, but I don’t find a correct solution to the problem.

In many places I read that I have to create a Scene with Control node as root and a Label inside. Then attach, instanciate to a Node2D. For example, attaching it to a CharacterBody2D)
If I have a decent font size and after scaling down the whole control node, this will work but there is some problems that arise, as the text will go behind other elements.

I can solve this by increasing the Z-index, but then the “z-index” war will start, with myself trying to figure out the indexes for this.

The text element will go behind other UI Elements, which is good. But not always.

For that fixing that, we have the node CanvasLayer and I can also “fake the position” so it looks really good:

But here is where my problem start to shine.

What if we have a camera limit and/or the entity is not close to the anchor that we setted (for example, center)?

Then the text will keep its position to that anchor, obviously.

So I thought that maybe I can position the text via script and here is where I bumped into the wall.

I tried to position from the entity script with something like:

interaction_control.set_position(get_global_transform_with_canvas().get_origin().round())

But as you can watch on the video, the text will do a weird effect only when the entity moves vertically and not when moving horizontally.

Futhermore, now the text can leave the screen (also a problem with the first approach)

So now I wonder,

What is the correct way of positioning a UI element in a CanvasLayer relative to an entity of the game world?

How can I prevent it from leaving the screen?

Thank you for your time!

Kinds regards to everyone.

Scaling Control nodes can create visual problems when attached to moving entities. Calculating the screen position dynamically and clamping it within the viewport edges provides better results for HUD elements that need to stay visible and readable.

Yes, this is what I want to achieve.

The scaling only happens in the first cases, when I use the canvas layer there is no need for scaling.

How can you calculate the screen position and clamp it to the view port?

Is the get_global_transform_with_canvas().get_origin() the correct method?

Multiply the world position with canvas_transform gotten from camera’s viewport. This will give you the position in viewport space which you can directly assign to controls on the canvas layer.

The second thing is constraining the label rect to viewport rect. After you’ve positioned the label check how much its rect is extending over viewport rect on each of 4 sides and translate the label for that amount in opposite direction.