UE5 Overhead Health Bars and Damage Numbers: UI Attached to Actors and UI That Floats Away

Created: 2026-07-23Last updated: 2026-09-06

Add a health bar to a Cube you attack with the E key and float a 20-damage number. Covers the Widget Component's container and contents, Screen versus World, coordinate conversion, and finishing UMG animations, plus initial display and DPI-related position offsets.

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 finished result: the Cube's HP dropping from full to 80% with a 20 appearing and vanishing after 0.6 seconds

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

Sponsored

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 .

A User Widget inside the Widget Component, with the Progress Bar itself inside that

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.

Comparing Screen overhead UI not hidden automatically by a wall with a World panel occluded as a surface in 3D space
SpaceHow it looksWhere it fits
ScreenDrawn on screen based on the Actor's position. Text stays unrotated as the camera turnsOverhead health bars, names
WorldDrawn as a surface placed in 3D space. It shrinks with distance and tilts when viewed at an angleWall 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.

AssetRoleWhere it displays
WBP_OverheadHealthDisplays the current HP proportionThe Cube's Widget Component
WBP_PracticeDamageNumberDisplays the damage amount, floats up, and fadesThe game screen

Finish the bar first and add the numbers later.

The goal: an 80% bar and a floating 20, with the bar on the Cube's Widget Component and the number on the game screen

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".

TargetSettings
Size BoxWidth Override on at 160 , Height Override on at 16
Progress BarName it HealthFill with "Is Variable" on
HealthFill's Size Box SlotHorizontal and Vertical Alignment Fill , Padding 0
HealthFill's ProgressPercent 1 , Bar Fill Type Left to Right . Do not set a Bind on Percent
HealthFill's AppearanceFill 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.

HealthFill inside a Size Box, with DamageMesh and OverheadUI as siblings under DefaultSceneRoot on the Actor

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 settingValue
LocationX=0 / Y=0 / Z=130
Rotation / ScaleRotation all 0 , Scale all 1
SpaceScreen
Widget ClassNone
Draw SizeX=160 / Y=16
Draw at Desired SizeOff
PivotX=0.5 / Y=0.5
Window FocusableOff

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.

Running Create Widget after HP initialization and connecting the Player Controller into Owning Player
  1. Set Create Widget's Class to WBP_OverheadHealth and Owning Player to Get Player Controller (Player Index=0).
  2. Right-click the Return Value, choose "Promote to Variable", and name it HealthWidget . Its type becomes WBP_OverheadHealth Object Reference .
  3. Create Set Widget from Get OverheadUI 's blue pin with Target OverheadUI and the Widget input connected to HealthWidget. The white line runs Create 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".

Saving Create Widget's return value into HealthWidget and passing it into OverheadUI's Set Widget

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.

Sponsored

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 Min 0 and Max 1 .
  • Connect Clamp's output into Set Percent 's In Percent.
  • Set Percent's Target is the HealthFill from the Designer. Wire the function entry's white output into Set Percent's white input.
Passing Current into the division's A and Maximum into B, clamping the result between 0 and 1

Connect execution order and the display target to that calculation.

Passing the function's exec line, the HealthFill reference, and the Clamp result into Set Percent

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.

Passing current and max HP into Refresh Overhead Health with HealthWidget as the update target
Where to call itOrder to connectPurpose
BeginPlaySet Widget → Refresh Overhead HealthReflect the initial HP on the bar
AnyDamage logicThe Set CurrentHealth that reduces HP → Refresh Overhead Health → the original Print StringReflect 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.

Converting the 3D position above the Cube into a screen position and rising from there

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.

TargetSettings
Size BoxWidth Override=120 , Height Override=80
Text BlockName it AmountText with "Is Variable" on
AmountText's Size Box SlotHorizontal and Vertical Alignment Center , Padding 0
TextText=20 , Font Size=32 , white, Outline Size=2 , Outline Color black
AmountText's RenderingRender 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.

ItemKey at 0 sKey at 0.6 s
Translation X00
Translation Y0-40
Render Opacity10

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.

Moving the number up 40 and changing opacity from 1 to 0 between 0 and 0.6 seconds

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 .

Converting Amount to text with To Text and passing it into AmountText's Set Text
  1. Pass Amount into To Text (Float) . Set both Minimum Fractional Digits and Maximum Fractional Digits in the detail pins to 0 for an integer look.
  2. Connect AmountText into Set Text (Text) 's Target and the converted value into In Text. Wire the event's white output into Set Text.
  3. Then place Play Animation with Finished event . Right-click and search Get a reference to self and connect its blue output into Widget. Self is this number Widget itself. Connect Get FloatAndFade into In Animation with Start at Time=0 , Num Loops to Play=1 , Play Mode=Forward , and Playback Speed=1 .
  4. 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.

Passing the number Widget itself and FloatAndFade into the play node and calling Remove from Parent from Finished

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.

Sponsored

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.

InputValue
Player ControllerGet Player Controller (Player Index=0 )
World LocationGet OverheadUI → Get World Location
Player Viewport RelativeOff

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.

Passing OverheadUI's world position and the Player Controller into the projection node

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.

Passing the projection node's Return Value into a Branch's Condition and creating the number only on True

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.

OrderNodeValue to pass
1Add to ViewportZOrder=10
2Set Desired Size in ViewportSize=X:120 / Y:80
3Set Alignment in ViewportAlignment=X:0.5 / Y:0.5
4Set Position in ViewportPosition=the projection node's Screen Position, Remove DPI Scale off
5Start Damage PopupAmount=ShowPracticeDamageNumber's Amount
Adding PopupWidget to the screen and setting its display size to 120 by 80

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.

Passing the projected Screen Position into PopupWidget's Set Position in Viewport with Remove DPI Scale off

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.

Passing ShowPracticeDamageNumber's Amount into Start Damage Popup with PopupWidget as Target

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

Wiring the health bar update's exec output into the number display and passing AnyDamage's Damage into Amount
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.

ActionExpected result
Walk around without attackingThe bar is full and displayed above the Cube
Press E onceThe bar goes to 80% and "20" rises and vanishes in 0.6 seconds
Mash faster than the invulnerability windowWith invulnerability, both HP and new numbers stop
Space out hits for five totalThe Cube and overhead bar disappear and the last number rises and vanishes
Turn the camera sharply right after a numberThe bar matches the Cube's position. The number rises from its spawn screen position
Resize the game window and tryThe 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.

How it looks before attacking, on the first hit, and on the fifth. Even when the box disappears, the number finishes rising and vanishes

When it does not work

SymptomWhere to check
No bar from the startBeginPlay's Create Widget, Set HealthWidget, and Set Widget exec lines. Whether Set Widget's Target is OverheadUI
An empty bar from the startWhether the original BeginPlay put MaxHealth into CurrentHealth, and whether the first Refresh comes after it
The HP Print drops but the bar does not changeWhether the call's Target is HealthWidget and whether Set Percent's Target inside the function is HealthFill
The bar drops but no number appearsWhether ShowPracticeDamageNumber is called and whether projection success's True reaches Create Widget
The number vanishes instantlyWhether Remove from Parent connects to the play node's Finished
The number does not move or vanishWhether FloatAndFade is specified, and whether both keys, the playback range, and loop count match
Position shifts with screen sizeWhether 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.

Unreal Engine Notes in this section98