Attacks land and HP goes down. Even so, a number in a screen corner makes it hard to tell "which target, and how much". A bar above the target plus a "20" floating up on each hit lets you feel the impact right there.
This article adds an overhead health bar and damage numbers to the Cube built in the health and damage article. The bar moves with the Cube and the number drifts up from where it appeared and fades. The point is separating where the display is attached from when the display ends .

The bar in the diagram is drawn in five segments so proportions are comparable. What we build is one continuous bar without divisions.
What You'll Learn
- Attaching a health bar above an Actor with a Widget Component
- How Screen and World look, and the container versus contents
- Converting a 3D position to a screen position and showing damage numbers
- Moving the number up while fading it and cleaning up after the animation
- UI placed on screen versus UI attached to an Actor
- Screen and World: readability versus presence in the world
- Preparation: a Cube that takes 20 damage
- Attach the overhead health bar
- Update the bar when HP changes
- Float damage numbers and clean them up
- Run it and confirm
- Bonus: good to know up front
- Summary
UI placed on screen versus UI attached to an Actor
A Widget is UI combining text, bars, and so on. The UMG introduction added a created Widget to the game screen with Add to Viewport . Without logic changing its position, it stays at the same screen position even as the camera moves. That suits showing your own HP or score.
The component for attaching UI to an Actor such as an enemy or box, meanwhile, is a Widget Component . Think of it as the display's mounting point on the Actor. Positioned above the Cube, that mounting point moves with the Cube.
More parts sit inside the Widget Component. Here it is three levels: "the Widget Component attached to the Cube", "the WBP_OverheadHealth it displays", and "the HealthFill bar itself". Set Percent , which changes the HP proportion, is called on the last one, the Progress Bar itself .

Get User Widget Object is the node retrieving the Widget a Widget Component displays. Using Cast To WBP_OverheadHealth from there lets you call functions defined on that kind of Widget. A Cast is not an operation that creates a new bar. It confirms that existing contents can be treated as the intended type.
In the exercise we save the return value from when the Widget was first created. That lets us specify "the bar currently displayed" directly without retrieving it again on every update.
Screen and World: readability versus presence in the world
A Widget Component's Space decides where the UI is drawn. Even attached to the same Actor, it looks different.

| Space | How it looks | Where it fits |
|---|---|---|
Screen | Drawn on screen based on the Actor's position. Text stays unrotated as the camera turns | Overhead health bars, names |
World | Drawn as a surface placed in 3D space. It shrinks with distance and tilts when viewed at an angle | Wall control panels, terminal screens |
Screen is readable, but a wall in front does not by itself hide the UI . In a game where you do not want enemies visible behind walls, you need logic checking visibility and toggling the display. World is affected by occluders like any mesh, though that varies with the material used.
We use Screen here, prioritizing the bar's readability. Screen is not a mechanism that "turns a 3D plane toward the camera each time"; it draws on screen at a position matching the 3D location.
Preparation: a Cube that takes 20 damage
Start from a single-player Third Person project where you built the exercise from the health and damage article. Confirm the player's E key deals 20 damage to the BP_Damageable in front and the Cube disappears on the fifth hit.
Duplicate that Blueprint asset and name it BP_OverheadPractice . Replace the practice Cube in the level with this duplicated Actor. Keep position, rotation, and Scale the same and test within 500 cm of the player. The attacking side's graph works as is.
The goal is that it starts full, one press of E takes it to 80%, and "20" floats up from overhead . If you also added the original article's invulnerability window, space attacks at least a second apart. During invulnerability, HP does not drop and no new number appears.
There are two new UI assets.
| Asset | Role | Where it displays |
|---|---|---|
WBP_OverheadHealth | Displays the current HP proportion | The Cube's Widget Component |
WBP_PracticeDamageNumber | Displays the damage amount, floats up, and fades | The game screen |
Finish the bar first and add the numbers later.

Attach the overhead health bar
1. Build a Widget that is just a bar
Create a "User Interface → Widget Blueprint" in the Content Browser with User Widget as the parent. Name it WBP_OverheadHealth .
In the Designer, put a Size Box at the root and a Progress Bar as its child. If a Canvas Panel is there by default, delete it and place these two. A Size Box is the container for specifying "display this UI at this size".
| Target | Settings |
|---|---|
| Size Box | Width Override on at 160 , Height Override on at 16 |
| Progress Bar | Name it HealthFill with "Is Variable" on |
| HealthFill's Size Box Slot | Horizontal and Vertical Alignment Fill , Padding 0 |
| HealthFill's Progress | Percent 1 , Bar Fill Type Left to Right . Do not set a Bind on Percent |
| HealthFill's Appearance | Fill Color and Opacity blue and opaque |
If the visible area in the Designer is large, setting the preview size to "Desired" makes the bar's size easier to judge. A 160 by 16 bar appearing here means you are ready.

2. Create the display spot above the Cube
Open BP_OverheadPractice , choose "Add → Widget" in Components, and name it OverheadUI . Put it as a child of DefaultSceneRoot . Making it DamageMesh's child would inherit that mesh's scaling from the original article.
| OverheadUI setting | Value |
|---|---|
| Location | X=0 / Y=0 / Z=130 |
| Rotation / Scale | Rotation all 0 , Scale all 1 |
| Space | Screen |
| Widget Class | None |
| Draw Size | X=160 / Y=16 |
| Draw at Desired Size | Off |
| Pivot | X=0.5 / Y=0.5 |
| Window Focusable | Off |
The original Cube is 100 cm from center to top, so Z=130 is 30 cm above the top. Pivot is which part of the UI aligns to the specified position. 0.5, 0.5 aligns the bar's center to it.
Widget Class can specify the class to display, but here we create the contents in BeginPlay and pass them in. So leave it None . No bar appears at this stage.
3. Create the contents at Play start
In BP_OverheadPractice's event graph, add the following after the existing Event BeginPlay → Set CurrentHealth . Keep the logic putting MaxHealth into current HP.
Create Widget's Owning Player specifies the player this UI belongs to. Single-player here, so pass the Player Controller at Player Index 0.

- Set Create Widget's Class to
WBP_OverheadHealthand Owning Player toGet Player Controller(Player Index=0). - Right-click the Return Value, choose "Promote to Variable", and name it
HealthWidget. Its type becomesWBP_OverheadHealth Object Reference. - Create
Set WidgetfromGet OverheadUI's blue pin with Target OverheadUI and the Widget input connected to HealthWidget. The white line runsCreate Widget → Set HealthWidget → Set Widget.
An Object Reference is a value for specifying the created Widget later. Saving it into HealthWidget lets later logic specify "which bar to update".

The two diagrams split the same Create Widget's input and output sides. There is no need to place two Create Widgets.
We do not call Add to Viewport for the overhead bar. Set Widget hands it to OverheadUI, which handles display. Compile and Play, and a full bar appears above the Cube. Walk around and confirm it lines up with the Cube's position.
Update the bar when HP changes
The bar's Percent takes a 0 to 1 proportion , not HP itself. HP 80 with a maximum of 100 passes 80 ÷ 100 = 0.8 .
Build an update function on the bar
In WBP_OverheadHealth's Graph, create a function RefreshOverheadHealth with inputs Current and Maximum , both Floats.
Input values come from the function entry's pins. Right-clicking inside the function and searching Get Current and Get Maximum works too.
- Divide Current by Maximum and pass it into
Clamp (Float)'s Value with Min0and Max1. - Connect Clamp's output into
Set Percent's In Percent. - Set Percent's Target is the
HealthFillfrom the Designer. Wire the function entry's white output into Set Percent's white input.

Connect execution order and the display target to that calculation.

The Clamp in the second image is the output side of the same node as the first. There is no need to build a second calculation.
Our MaxHealth is fixed at 100 and uses a value greater than 0. Clamp keeps the result in range, but that does not make dividing by a maximum HP of 0 acceptable.
Call it from the Cube at start and on damage
Return to BP_OverheadPractice. Place HealthWidget as a Get and search Refresh Overhead Health from its blue pin to call it. Target is HealthWidget, Current is Get CurrentHealth , and Maximum is Get MaxHealth .
Place that same call in these two places.

| Where to call it | Order to connect | Purpose |
|---|---|---|
| BeginPlay | Set Widget → Refresh Overhead Health | Reflect the initial HP on the bar |
| AnyDamage logic | The Set CurrentHealth that reduces HP → Refresh Overhead Health → the original Print String | Reflect the reduced HP |
Even with an invulnerability window, place it after the existing HP subtraction . Adding a separate line right after AnyDamage would show effects for attacks that change no HP during invulnerability. Keep the death check and invulnerability-clearing Timer that follow the original Print String.
Play again, press E once, and success is the bar at four fifths. No numbers appear yet. The bar follows position, but the proportion is computed only at start and when HP changes.
Float damage numbers and clean them up
Numbers check "where is above the Cube on the current screen" at spawn time and place a Widget there. Converting a 3D position into an on-screen position like this is called projection here.
We check the position only once, at spawn. After that it moves up on screen. Turning the camera sharply can separate it from the Cube, but the number can display fully even after the Cube is destroyed. That is where it differs from the overhead bar, which follows the Actor for as long as it displays.

1. Build the number Widget
Create WBP_PracticeDamageNumber with a Size Box at the root and a Text Block as its child. Delete a Canvas Panel if present.
| Target | Settings |
|---|---|
| Size Box | Width Override=120 , Height Override=80 |
| Text Block | Name it AmountText with "Is Variable" on |
| AmountText's Size Box Slot | Horizontal and Vertical Alignment Center , Padding 0 |
| Text | Text=20 , Font Size=32 , white, Outline Size=2 , Outline Color black |
| AmountText's Rendering | Render Opacity=1 , Clipping=Inherit |
White numbers with a black outline stay readable over bright floors and Cubes. Leave the Size Box's Clipping at Inherit too so the moving text is not cut off by a frame.
2. Build the rise and fade together
Press "+ Animation" under "Animations" in the Designer and name it FloatAndFade . If the panel is missing, show "Animations" and "Timeline" from "Window".
What this animation moves is AmountText. A track is "an item changed over time" and a keyframe marks "at this time, this value".
With FloatAndFade selected, select AmountText and press the add-key buttons beside "Render Transform → Translation" and "Render Opacity" in the Details panel. That adds tracks for position and opacity to the timeline.
Set the timeline's display unit to seconds and place keys at these two times. Using frames, 0.6 seconds is 18 frames at 30 fps.
| Item | Key at 0 s | Key at 0.6 s |
|---|---|---|
| Translation X | 0 | 0 |
| Translation Y | 0 | -40 |
| Render Opacity | 1 | 0 |
Screen Y increases downward, so moving up uses a negative value . Render Opacity is visible opacity, visible at 1 and transparent at 0.
Beyond changing each value, confirm the keys were placed. Align the playback range's end at 0.6 seconds too. Playing in the Designer with "20" moving up 40 while fading means you are ready. Position units scale with UI display scaling, so 40 here does not always mean 40 physical pixels.

The dotted number at 0.6 seconds marks the destination. In actual display it is fully transparent.
3. Receive the value and clean up when finished
Create a custom event StartDamagePopup in WBP_PracticeDamageNumber's event graph with a Float input Amount .

- Pass Amount into
To Text (Float). Set both Minimum Fractional Digits and Maximum Fractional Digits in the detail pins to0for an integer look. - Connect AmountText into
Set Text (Text)'s Target and the converted value into In Text. Wire the event's white output into Set Text. - Then place
Play Animation with Finished event. Right-click and searchGet a reference to selfand connect its blue output into Widget. Self is this number Widget itself. ConnectGet FloatAndFadeinto In Animation with Start at Time=0, Num Loops to Play=1, Play Mode=Forward, and Playback Speed=1. - Call
Remove from Parent(Target Self) from that node's Finished . Leave the normal white exec output empty.
FloatAndFade is a variable provided when you create the animation. Dragging it from "Animations" in My Blueprint while holding Ctrl places it as a Get.

Finished is the output taken when the animation ends. Wiring Remove from Parent to the normal output, which fires right after playback starts, makes the number vanish immediately. This play node also cannot go inside a function, which is why we use a custom event.
Going transparent alone leaves the Widget added to the screen. Connect through Remove from Parent to end this display.
4. Convert the Cube's overhead position to screen coordinates
In BP_OverheadPractice's event graph, create a custom event ShowPracticeDamageNumber with a Float input Amount .
Place Project World Location to Widget Position and connect these values. That node has no white exec pin; it computes its result when the connected destination uses the value.
| Input | Value |
|---|---|
| Player Controller | Get Player Controller (Player Index=0 ) |
| World Location | Get OverheadUI → Get World Location |
| Player Viewport Relative | Off |
What we use is OverheadUI's world position , not the Cube's center. A world position is relative to the whole level. Unlike the relative Z=130 we set, it includes where the Cube is placed.

Wire the event's white output into a Branch and pass the projection node's Return Value into Condition. Continue to number creation only on True. Leave False unconnected. That avoids starting a display when the position could not be projected, such as behind the camera. Note that True can still yield coordinates outside the screen.

The projection node in this diagram is the same as in the previous one. Here we show only the boolean output indicating success.
Event AnyDamage has no output for where the hit landed. Our number appears above the Cube . To place it at a sword tip or impact point, you move to passing the hit location separately with Point Damage or similar.
5. Create the number and play it at that position
Call Create Widget from the Branch's True. Class is WBP_PracticeDamageNumber and Owning Player is the same Player Controller as before. As with the bar, right-click the Return Value, choose "Promote to Variable", and name it PopupWidget . Its type is WBP_PracticeDamageNumber Object Reference .
From there, wire the white line in this order. Every node's Target is PopupWidget.
| Order | Node | Value to pass |
|---|---|---|
| 1 | Add to Viewport | ZOrder=10 |
| 2 | Set Desired Size in Viewport | Size=X:120 / Y:80 |
| 3 | Set Alignment in Viewport | Alignment=X:0.5 / Y:0.5 |
| 4 | Set Position in Viewport | Position=the projection node's Screen Position, Remove DPI Scale off |
| 5 | Start Damage Popup | Amount=ShowPracticeDamageNumber's Amount |

ZOrder is the front-to-back relationship of overlapping screen Widgets, with higher values in front. Setting the size and centering with Alignment before passing the position aligns the number's center overhead. These steps assume single-player without split screen.

Understand why Remove DPI Scale is off too. UI scales with screen size, and DPI scaling is the mechanism accounting for that display scale. The projection node already returns corrected coordinates, so we avoid stacking the same correction in Set Position in Viewport.
With the position set, search Start Damage Popup from PopupWidget's blue output. Pass the Amount this event received into Amount. That starts the number side's "change the text, float up, clean up" logic.

Finally, add the call to the flow after HP is reduced.

BP_OverheadPractice: continuing the existing damage logic
Set CurrentHealth
→ Refresh Overhead Health (Target: HealthWidget)
→ Show Practice Damage Number (Target: Self, Amount: AnyDamage's Damage)
→ the original Print String
→ the original death check and Timer
We do not add more places that reduce HP. The display logic connects to the updated HP and the damage amount received. The 20 here is the damage of the accepted attack.
Run it and confirm
Compile, replay, and click the game screen. As in the original article, face the character at the Cube and press E.
| Action | Expected result |
|---|---|
| Walk around without attacking | The bar is full and displayed above the Cube |
| Press E once | The bar goes to 80% and "20" rises and vanishes in 0.6 seconds |
| Mash faster than the invulnerability window | With invulnerability, both HP and new numbers stop |
| Space out hits for five total | The Cube and overhead bar disappear and the last number rises and vanishes |
| Turn the camera sharply right after a number | The bar matches the Cube's position. The number rises from its spawn screen position |
| Resize the game window and try | The number's spawn position does not drift far from overhead |
The fifth number completes its motion because the Widget added to the screen handles playback and cleanup itself. That is separate from the logic destroying the Cube Actor.

When it does not work
| Symptom | Where to check |
|---|---|
| No bar from the start | BeginPlay's Create Widget, Set HealthWidget, and Set Widget exec lines. Whether Set Widget's Target is OverheadUI |
| An empty bar from the start | Whether the original BeginPlay put MaxHealth into CurrentHealth, and whether the first Refresh comes after it |
| The HP Print drops but the bar does not change | Whether the call's Target is HealthWidget and whether Set Percent's Target inside the function is HealthFill |
| The bar drops but no number appears | Whether ShowPracticeDamageNumber is called and whether projection success's True reaches Create Widget |
| The number vanishes instantly | Whether Remove from Parent connects to the play node's Finished |
| The number does not move or vanish | Whether FloatAndFade is specified, and whether both keys, the playback range, and loop count match |
| Position shifts with screen size | Whether Remove DPI Scale is off, and whether Desired Size and Alignment are set |
To check values, temporarily output the projection's Return Value or the Amount passed to the display with Print String. Checking the bar and the number separately narrows down where the problem is.
Bonus: good to know up front
When you want it to follow throughout. Our number computes its spawn position once. To keep it over the same 3D point while moving, you need logic saving that point and reprojecting during display. Following the enemy itself also means deciding what happens after the enemy is gone.
HP-reducing logic versus display-updating logic. We called the Widget's function directly from the Actor here. Once the same HP also goes to a screen HUD and listeners multiply, you can extend to announcing "HP changed" with an Event Dispatcher. Matching the display at start is necessary either way.
Revisiting as numbers grow. We still Create Widget on every hit. Switching to on-screen display does not by itself make mass display cheap. First revisit how many appear at once and whether distant enemies need bars, and confirm with the UI performance investigation steps. Adding pooling means resetting the text, position, opacity, and animation playback state each time.
Adding meaning to the numbers. Changing color or size only for criticals conveys the difference between attacks through the same damage display. After adding defense, align the displayed amount with the post-defense calculation. Whether you show "the damage accepted" or "how much HP actually dropped" is also decided by your game's rules.
Summary
An overhead health bar hands its contents Widget to a Widget Component and updates the proportion when HP changes. Damage numbers convert the overhead position into screen coordinates to display, and clean up when the Widget's own animation finishes.
First confirm the bar dropping on 20 damage, then change the number's rise distance and the 0.6-second duration. That lets you tune the feel of an attack while leaving the HP-reducing logic alone.
Reference: Animating UMG widgets, Widget Component, Screen and World, Projecting coordinates for widgets, Set Position in Viewport, Play Animation with Finished event.