It’s very important to provide the player with status updates on how a character is doing. There’s a wide array of scenarios where this is applicable, but two very common ones are a health bar or a status meter. I recently implemented this behavior in my own game as a status bar that increases over time to show when a guard is becoming more and more alert to your actions. This tutorial will go over the same mechanics that I used to create a runtime bar, but apply them to a health bar that hovers over the player and enemies.
Learning outcomes
There are two ways that you can achieve this within your game. This tutorial will only focus on one of the techniques – which is translating a position from world space to screen space. By the end of this tutorial you will be able to:
Understand the difference between your two options.
Design a health bar UI using the UI Builder tool.
Add a runtime UI to your game.
Manipulate the UI by converting world point to screen point.
Toggle the visibility of a visual element.
Prerequisites
Note: This tutorial was created with 2020.3 LTS but should still work with 2019 LTS.
This tutorial assumes you already have basic knowledge of Unity and intermediate knowledge of C#.
Basic understanding of UI Toolkit and UI Builder.
Note: I got a bit carried away playing around with Timeline and Cinemachine when creating the above video. The next tutorial will cover how I animated this with more polish to the sequence.
This is dope, I wound up writing my own implementation before I found this, wish I’d seen this first. A few keywords for the next poor soul searching for this: this helped me find how to programmatically set the position of a UI Toolkit VisualElement in screen space using the world space of an object at runtime.
A quick note for anyone having issues with the position of elements being wrong by a bit, you may have to set the position to absolute in the style:
visualElement.style.position = Position.Absolute;
Any clever ideas for setting the hierarchy of the panels so the closer panel shows on top of farther panels? I’m thinking using something like camera.WorldToScreenPoint(position) and using the z to sort and set the hierarchy, but that seems ugly.
Positioning the UI can get a little bit tricky if the camera moves. I experimented a bunch when working on my asset store package and found two methods to address most needs, especially when combined.
The goal is to have the UI always float above the 3D object regardless of where the camera moves. Both methods can achieve that but they differ in what happens when “zooming” (changing the distance between camera and 3D object)
(A) Offset in UI space > For example adding [x:0, y:100] offset to visualElement.style.position
Zooming: Screen offset is constant. If the 3D object is far away and has the size of just a few pixels on the screen, the UI is still offset by 100 pixels. The offset might be too big for objects far away from the camera and too small for objects close to the camera.
(B) Offset in 3D camera space > For example adding [x: 0, y: 10, z:0] to the position of the 3D object using the coordinate axis of the camera.
Zooming: Screen offset scales with distance. If the 3D object is far away, the offset will also be small. But, if you want to have a constant empty space between 3D object and UI (lets say 20 pixels) you cannot achieve that because also this empty space will scale with distance.
Use (A) for offsets that should not scale and (B) for offsets that should scale with distance.