Floating Text
Floating text displays short messages near an entity, such as damage numbers or healing amounts. We will build the visual effect one step at a time, starting with the texture that holds our characters.
Follow the steps below to import the example atlas, create its Material, and display the first character. Check the preview after each stage before continuing.
Step 1 — Get the example texture atlas
A texture atlas is a single image divided into equal cells. Each cell contains one character. Later, the effect will select the cells it needs to display a number such as 1250.
If the link opens the image in your browser, save the image as T_FloatingAtlas.png.
The example uses:
- Image size: 1024 × 1024 pixels.
- Grid: 4 columns × 4 rows.
- Cell size: 256 × 256 pixels.
- Characters: white, on a transparent background.
The cells are arranged from left to right, then top to bottom:
0 1 2 3
4 5 6 7
8 9 + -
. , K M
This order matches the default glyph mapping in the Mass Warden Floating Profile. The image resolution and 4 × 4 grid are example settings, not requirements. You can use more or fewer rows and columns, including a rectangular image. Keep all cells equally sized and match your Floating Profile to your image when setting it up later.

The dark background above is only for this preview. The PNG itself has a transparent background.
You can use this image for the first test. If you draw your own version later, keep the cells equally sized and leave transparent space around each character. Do not draw grid lines into the texture.
Step 2 — Import the image into Unreal Engine
- Open your project in Unreal Editor.
- Open the Content Browser or Content Drawer.
- Under your project's Content folder, create a folder named
FloatingText. - Open that folder.
- Drag
T_FloatingAtlas.pngfrom File Explorer into the Content Browser. You can also use the Import button and select the file. - Confirm that a Texture asset named T_FloatingAtlas appears.
At this point, you only need the imported Texture asset. It will not display floating numbers in the level by itself.
Step 3 — Set up the texture
- Double-click T_FloatingAtlas to open the Texture Editor.
- In the Details panel, use the search box to find each setting below. Clear the search between settings. Some properties may be under advanced options.
- Set the following values:
| Setting | Value | What it does |
|---|---|---|
| Compression Settings | Default | Uses the standard texture compression setting for this initial example. |
| sRGB | Enabled | Treats the texture's visible color as color data. Keep this enabled for the example artwork. |
| Compress Without Alpha | Disabled | Keeps the transparency information in the texture. |
| X-axis Tiling Method | Clamp | Stops horizontal sampling from wrapping around the outer edge of the texture. |
| Y-axis Tiling Method | Clamp | Stops vertical sampling from wrapping around the outer edge of the texture. |
| Filter | Default | Uses the texture group's filtering for the first test. |
Clamp applies to the outer edge of the entire image. Transparent padding around each character still matters because the atlas contains neighboring cells.
Step 4 — Check the size and transparency
Before saving, make sure the imported image has the expected size and alpha channel.
- Check the texture information in the Texture Editor. For the supplied example, the imported size should be 1024 × 1024. For your own image, check that it matches the original size.
- Find the preview's color-channel controls. Depending on the Editor layout, these may appear as R, G, B, A controls or inside a channel menu.
- Switch to an alpha-only preview. If the channels are separate toggles, turn off R, G, and B, and leave A enabled.
- Check the result:
- The characters should appear white.
- The empty background should appear black.
- Slightly gray pixels along the character edges are normal; they provide smooth transparency.
In the alpha preview, white means opaque and black means transparent. A black background in this view is correct—it does not mean the PNG has a solid black background.
If the alpha preview is completely white, check that you imported the original PNG with transparency and that Compress Without Alpha is disabled.
- Return to the normal color preview.
- Click Save.
Texture checkpoint
You should now have:
- A saved T_FloatingAtlas Texture asset in your project's
FloatingTextfolder. - A 1024 × 1024 image with the expected 4 × 4 character arrangement.
- An alpha channel that preserves the transparent background.
- The texture settings listed above.
Once these checks pass, continue to the Material steps below.
Step 5 — Create the Material
A Material controls how the texture appears. Start by displaying the whole atlas with its transparent background.
- In your
FloatingTextfolder, right-click and create a Material. - Name it M_FloatingText and open it.
- Click an empty area of the graph to see the Material settings in Details.
- Set Material Domain to Surface, Blend Mode to Translucent, and Shading Model to Unlit.
- Enable Two Sided and Used with Niagara Sprites. The latter is under Usage; use the Details search if needed.
Your Editor may show Translucent Grey Transmittance and an Opacity Override input, as in the verified example for this guide. Use that opacity input for the connection below when those labels appear.
Step 6 — Display the full atlas
- Drag T_FloatingAtlas from the Content Browser into the Material graph. This creates a Texture Sample node.
- Connect the texture's RGB output to Emissive Color.
- Connect its A output to Opacity (or Opacity Override, if that is the label shown).
- Leave UVs unconnected for this first check.
- Choose the Plane preview mesh, then click Apply and Save.
Texture RGB → Emissive Color
Texture A → Opacity / Opacity Override
Checkpoint: all 16 characters should appear, with the preview background visible between them. If you see an opaque rectangle, check the blend mode and the alpha connection before continuing.
Step 7 — Show only the first character
Now we will display only the 0 in the top-left cell of the example atlas. Keep the color and opacity connections from Step 6.
- Right-click an empty area of the Material graph, search for TextureCoordinate, and add it. The node may appear as TexCoord[0].
- Leave its UTiling and VTiling at 1.
- Right-click again, search for Constant2Vector, and add it.
- Select the Constant2Vector and set R = 4 and G = 4 in Details. These represent the number of columns and rows in the example atlas, not the image resolution.
- Add a Divide node.
- Connect TextureCoordinate to Divide input A.
- Connect Constant2Vector (4, 4) to Divide input B.
- Connect the Divide output to the Texture Sample's UVs input.
TextureCoordinate ─── A
Divide → Texture Sample UVs
Constant2Vector ──── B
R = 4
G = 4
Dividing both texture coordinates by four selects the first quarter of the image horizontally and vertically: the top-left cell. The selected cell now fills the preview plane.
If using your own grid, enter its column count in R and its row count in G instead. For example, a 5-column, 2-row image uses (5, 2).
- Click Apply, then Save.
Checkpoint: the preview should now show one large 0, with a transparent background. Seeing the whole atlas still means the Divide output is not reaching the texture's UVs input. If several cells appear, check the grid values and the TextureCoordinate tiling settings.
Once the preview shows the first character correctly, continue below to choose another cell.
Step 8 — Choose a character with GlyphIndex
Add a number control that selects a cell in the atlas. GlyphIndex is the cell number, not necessarily the character printed in that cell. Counting starts at zero, moves left to right, and then continues on the next row.
For the example atlas:
| GlyphIndex | Character |
|---|---|
| 0–9 | The matching digit |
| 10 | + |
| 11 | - |
| 12 | . |
| 13 | , |
| 14 | K |
| 15 | M |
Add the selector and column count
- Add a ScalarParameter node. Set its Parameter Name to
GlyphIndexand Default Value to0. - Add a ComponentMask node. Connect the existing Constant2Vector (4, 4) to it.
- Select the mask and enable R only. Disable G, B and A. This extracts the column count from the grid value.
Find the column and row
- Add an Fmod node. Connect
GlyphIndexto A, and the R-only mask to B. Its output is the column number. - Add a new Divide node, separate from the Divide already connected to the texture. Connect
GlyphIndexto A, and the same R-only mask to B. - Add a Floor node after this new Divide. Its output is the row number.
- Add an AppendVector node. Connect Fmod to A and Floor to B. The result is the cell offset: column first, row second.
For example, index 5 in a four-column grid gives column 1, row 1: the second cell of the second row.
Connect the cell offset to the texture
- Add an Add node. Connect TextureCoordinate to A and the AppendVector output to B.
- Connect the Add output to input A of the original Divide from Step 7. This replaces the direct TextureCoordinate connection.
- Keep Constant2Vector (4, 4) connected to B of the original Divide, and keep that Divide's output connected to the Texture Sample's UVs.
The completed UV path is:
GlyphIndex → Fmod with Columns ─────────────── Column ── Append A
GlyphIndex → Divide by Columns → Floor ────── Row ───── Append B
TextureCoordinate ── Add A
Cell offset ──────── Add B
↓
Original Divide A
Grid (4, 4) ──── Original Divide B
↓
Texture Sample UVs
Keep the existing RGB and alpha connections unchanged.
Check the result
- Select the GlyphIndex parameter, change its Default Value, and click Apply to check each example:
| Default Value | Expected preview |
|---|---|
| 0 | 0 |
| 5 | 5 |
| 10 | + |
| 15 | M |
Use whole numbers from 0 to 15 with this example. Fractional values sample between cells, and out-of-range values do not select a valid cell. For a different grid, use only cell indices that contain your artwork.
- Return the value to
0, then click Apply and Save.
Checkpoint: changing GlyphIndex selects one character at a time, with the background still transparent. This parameter is a preview control for now; Niagara will supply each particle's cell selection in a later step.
Verify these four values before continuing to the color and transparency connections below.
Step 9 — Keep the atlas colors and add transparency control
Each style uses its own atlas texture. Paint the colors, outlines and shading directly into that texture. Keep the atlas-selection nodes from Step 8 unchanged.
- Connect Texture Sample RGB directly to Emissive Color. If you previously added a Multiply for particle tint, remove that Multiply.
- Add a Particle Color node. We will use only its A output for transparency.
- Add one Multiply node and connect:
Texture Sample RGB ────────────── Emissive Color
Texture Sample A ── Multiply A
Particle Color A ── Multiply B
↓
Opacity / Opacity Override
The texture's alpha preserves the character shape. The particle's alpha controls visibility: 1 keeps it visible, 0.5 makes it partly transparent, and 0 hides it. Changing Particle Color RGB will not change the artwork's colors.
- Click Apply, then Save.
For example, normal damage can use a white atlas and critical damage can use a separate gold atlas. Within one Floating Text Profile, keep the same grid and character order in both textures. We will combine the style textures into a Texture2DArray, then use one shared channel and one Sprite Renderer. Keep all source textures at the same resolution and format.
Checkpoint: RGB goes directly to Emissive Color. There is one Multiply in the opacity path. Keep the UV connection intact. This enables fading; we will initialize particle alpha to 1 and add the fade animation when setting up Niagara.
Stop here and check the connections before continuing.
Step 10 — Make the atlas texture selectable
Expose the sample texture as a parameter for the initial single-atlas preview. Step 36 replaces this Texture2D parameter with the final Texture2DArray parameter for multiple styles.
- In
M_FloatingText, right-click the existing Texture Sample node that holds the atlas. - Choose Convert to Parameter.
- Select the converted node and set Parameter Name to
AtlasTexturein Details. - Keep
T_FloatingAtlasas the default texture for now. - Check that the existing connections remain intact: the atlas UV calculation goes into UVs, RGB goes directly to Emissive Color, and A goes into the opacity Multiply from Step 9.
- Click Apply, then Save.
Checkpoint: the texture node is now a texture parameter named AtlasTexture, and the selected character should look the same as before. This step only makes the texture selectable; it does not change the artwork.
Stop here. In the next step, we will create a Material Instance for the first style.
Step 11 — Set up the first style's Material Instance
- In the Content Browser, right-click
M_FloatingText, choose Create Material Instance, and name itMI_Floating_Default. If you already created it, open it. - Check that its Parent is
M_FloatingText. - Find
AtlasTexturein the parameter list. Enable the checkbox beside it and selectT_FloatingAtlas. - Find
GlyphIndex, enable its override checkbox, and set it to0. - Use the plane preview mesh to inspect the character. Try
GlyphIndexvalues0,5, and10: these select0,5, and+in the example atlas. - Return
GlyphIndexto0and click Save.
Checkpoint: this instance uses the example atlas and selects one character at a time. If the parameters are missing, apply and save the parent Material and check the instance's Parent.
GlyphIndex is still a preview control shared by this material instance. We will connect per-particle character selection when preparing the Material for Niagara. This preview does not yet test Niagara alpha fading.
Stop here before continuing to the Niagara setup.
Step 12 — Let each particle select its own character
The scalar GlyphIndex used for preview gives every particle using the same Material Instance the same value. Replace it with a Dynamic Parameter so Niagara can supply a different cell index for each particle.
- Open the parent Material,
M_FloatingText. - Add a Dynamic Parameter node. Keep its Parameter Index set to
0. - In Details, set its four Param Names, in order:
| Entry | Name | Purpose |
|---|---|---|
| 0 / first | GlyphOffsetX | Horizontal character offset, connected later |
| 1 / second | GlyphOffsetY | Vertical character offset, connected later |
| 2 / third | GlyphIndex | Atlas cell selected by this particle |
| 3 / fourth | Unused | Reserved; leave unconnected |
- Connect the third output, now named GlyphIndex, to A of Fmod and A of the Divide that feeds Floor. These replace both connections from the old scalar
GlyphIndex. Do not change the final Divide connected to the texture's UVs. - Delete the old scalar
GlyphIndexnode once both wires have been replaced. Its Material Instance override is no longer used. - Set the Dynamic Parameter's Default Value to
(R=0, G=0, B=0, A=0). For a material preview check, change only B to10and apply: the example atlas should select+. Then restore B to0. - Click Apply, then Save.
Dynamic Parameter: GlyphIndex (third output)
├─ Fmod A
└─ Divide A → Floor
Keep the existing grid, atlas texture, RGB and opacity connections unchanged. The offset outputs remain unconnected for this step.
Checkpoint: both cell-selection calculations use the third Dynamic Parameter output. The names label the outputs; Niagara still needs explicit renderer bindings, which we will configure later. This step alone does not send particle data into the Material.
Step 13 — Receive the atlas grid from Niagara
Replace the example's fixed (4, 4) grid with a second Dynamic Parameter. Niagara will later supply the column and row counts from the Floating Text Profile.
- Add another Dynamic Parameter node in
M_FloatingText. Set its Parameter Index to1before editing its names. Keep the first Dynamic Parameter at index0. - For this new node, set Param Names in order to
AtlasColumns,AtlasRows,SizeMode, andUnused3. - Set its Default Value to
(R=4, G=4, B=0, A=0)for the example atlas. Enter the numeric R and G values directly; these are grid counts, not artwork colors. - Add a new AppendVector. Connect AtlasColumns to A and AtlasRows to B. It produces the two-component grid
(Columns, Rows). - Connect the new AppendVector output to B of the final Divide that feeds the texture UVs, replacing the old Constant2Vector connection.
- Connect AtlasColumns directly to B of Fmod and B of the Divide that feeds Floor. These replace the two R-only ComponentMask connections.
- Delete the old
(4, 4)Constant2Vector and the two now-unused R-only ComponentMask nodes. Keep the existing AppendVector that joins the calculated cell column and row. - Click Apply, then Save.
Dynamic Parameter (Index 1)
AtlasColumns ── Fmod B
├─ Divide B → Floor
└─ New Append A
AtlasRows ───── New Append B
↓
Final UV Divide B
Checkpoint: the default grid is still 4 columns by 4 rows, so the preview should stay the same. The first Dynamic Parameter has index 0 and provides glyph selection; the second has index 1 and provides the grid. Keep both counts positive. Renderer bindings will be configured later to supply the actual values for particles.
Step 14 — Position characters along the camera plane
Each character needs its own offset so a number such as 125 forms a row instead of overlapping at one point. Use GlyphOffsetX and GlyphOffsetY from Dynamic Parameter index 0 to move the sprite along the camera's horizontal and vertical axes.
- Add a new AppendVector. Connect GlyphOffsetX to A and GlyphOffsetY to B. This produces
(OffsetX, OffsetY). - Add another AppendVector. Connect the first new Append's output to A.
- Add a Constant set to
0and connect it to B of the second new Append. The result is(OffsetX, OffsetY, 0). - Add a TransformVector node (it may appear as Transform in the graph). Set Source = View Space and Destination = World Space. Use the vector transform, not TransformPosition.
- Connect the second new Append's output to TransformVector, then connect its output to the Material's World Position Offset.
- Keep the UV, texture color and opacity paths unchanged. Click Apply, then Save.
GlyphOffsetX ── Append A
GlyphOffsetY ── Append B
↓
Next Append A
Constant 0 ─── Next Append B
↓
TransformVector
View Space → World Space
↓
World Position Offset
For a preview check, temporarily set R = 20 in the Default Value of Dynamic Parameter index 0, keeping G, B and A at zero. With material preview WPO enabled, the plane should shift horizontally. Restore R = 0 and save. Do not change the grid node at index 1; its default remains (4, 4, 0, 0).
Checkpoint: World Position Offset now receives the transformed three-component offset. With zero offsets, the preview should look unchanged. Niagara will supply the actual character spacing later. Apply this offset only in the Material; adding the same offset to Niagara particle positions would apply it twice. Camera-facing sprite settings and particle bounds will be checked during Niagara setup.
Step 15 — Create the Default style's Data Channel
A Niagara Data Channel carries the character data that the Niagara effect will read. Create one channel shared by every style in the Floating Text Profile.
- In the Content Browser, open the folder where you keep the Floating Text assets.
- Right-click an empty area and choose FX → Niagara Data Channel.
- Name the asset
NDC_Floating_Defaultand open it. - In Details, find Data Channel Type and choose Niagara Data Channel Gameplay Burst (look for
Gameplay Burstin the class picker). - Save the asset.
Checkpoint: NDC_Floating_Default exists and its type is Gameplay Burst. This channel is still incomplete: we will add its variables and connect its Niagara handler in the following steps. Leave the other settings unchanged for now.
If the menu or type picker differs in your Editor, check that screen before continuing. Floating Text expects the Gameplay Burst channel type.
Step 16 — Add the channel variables
Open NDC_Floating_Default and find Channel Variables in Details. Use the add button to create the entries below, setting each entry's name and Niagara type. Copy the names exactly, without spaces or a Particles. prefix.
| Name | Niagara type | What it controls |
|---|---|---|
| Position | Position | Shared world origin of the text |
| GlyphIndex | Integer | Which atlas cell this character uses |
| GlyphOffset | Vector2D | Horizontal and vertical spacing from the text origin |
| GlyphSize | Vector2D | Character width and height |
| AtlasGrid | Vector2D | Number of atlas columns and rows |
| Lifetime | Float | How long the character lives, in seconds |
| Velocity | Vector | Movement direction and speed |
| MessageId | Integer | Groups characters belonging to the same message |
| GlyphOrder | Integer | Character order within the message, starting at zero |
| GlyphCount | Integer | Total characters in the message |
| StyleId | Integer | Style index and Texture2DArray slice within the Floating Text Profile |
| SizeMode | Integer | 0 for world size, 1 for fixed screen size |
Choose Position for the variable named Position, not Vector. This preserves correct world-position handling in large worlds. Velocity uses Vector, while the three two-component values use Vector2D. An Integer type may be shown as int32 in the picker.
There is no Color entry: the texture provides the artwork colors, and Niagara will control particle alpha for fading.
Save the asset after adding all 11 variables. Each channel row represents one character; the Niagara setup will spawn one particle per row, not GlyphCount particles per row.
Checkpoint: the list contains the 11 names above with matching types. The channel is not ready to display text yet; its settings and Niagara handler will be configured next.
Step 17 — Prepare the channel settings
In NDC_Floating_Default, confirm these settings:
| Setting | Value for this example |
|---|---|
| Keep Previous Frame Data | Enabled |
| Enforce Tick Group Read Write Order | Disabled |
| Cell Size | X = 2500, Y = 2500, Z = 2500 |
| System Bounds Padding | X = 512, Y = 512, Z = 512 |
Padding allows room for character spacing and movement outside the cell. This is a starting value for the example; larger text or longer travel may need more padding. Leave Default System to Spawn empty until its handler is configured. Save the channel.
Step 18 — Create the Default Niagara System
- In the Content Browser, right-click an empty area and choose FX → Niagara System.
- In the creation window, search for
Minimaland select the standard Minimal emitter as the starting point. Its description says it is a minimal, almost empty emitter for building from scratch. Use the standard emitter, not the Minimal Lightweight system. - Create the system and name it
NS_Floating_Default. - Open it. You should have one emitter in the System Overview. Rename that emitter to
FloatingText_Default. - Select Emitter Properties in its stack. Set Sim Target to CPUSim and disable Local Space.
- Save the system.
Checkpoint: the system has one standard CPU emitter with Local Space disabled. A blank preview is expected at this stage: spawning, channel reading and the renderer are not configured yet. Keep the channel's Default System to Spawn empty for now.
If the creation window does not show the Minimal emitter, check the template selection screen before choosing a different starting point.
Step 19 — Set up the Sprite Renderer
Continue with CPUSim and Local Space disabled in NS_Floating_Default.
- Open the parent Material
M_FloatingText. In its material Details, find Used with Niagara Sprites under Usage and enable it. Apply and save. - Return to the
FloatingText_Defaultemitter inNS_Floating_Default. - Under Render, select its Sprite Renderer. If none exists, use the add button for the Render section to add one. Keep only one Sprite Renderer for this emitter.
- Set these renderer properties:
| Property | Value |
|---|---|
| Material | MI_Floating_Default |
| Facing Mode | Face Camera Plane |
| Alignment | Unaligned |
| Default Pivot in UV Space | X = 0.5, Y = 0.5 |
| Sub Image Size | X = 1, Y = 1 |
| Sub Image Blend | Disabled |
| Sort Mode | None |
| Cast Shadows | Disabled |
Face Camera Plane keeps the character sprites parallel to the camera plane, matching the camera-relative spacing in our Material. Unaligned avoids aligning characters to their movement velocity. Particle rotation will be set to zero during initialization.
Keep Sub Image Size at (1, 1), even though the example texture contains a 4-by-4 grid: the Material already selects the atlas cell. The renderer should not divide the UVs again.
Save the system. Keep the existing renderer attribute bindings for now; we will connect particle size and the two Dynamic Material Parameter vectors later.
Checkpoint: the emitter has one Sprite Renderer using MI_Floating_Default with the settings above. A blank preview is still expected until particle spawning and initialization are configured.
Step 20 — Open the Data Channel spawn wizard
- Open
NS_Floating_Defaultand locate its emitter stack. - Click + beside Emitter Update, then search for Spawn From Data Channel.
- Choose Spawn From Data Channel... to open the wizard. This action is available in Emitter Update; do not add it under Particle Spawn.
- On the Select asset page, set Data Channel to
NDC_Floating_Defaultand Spawn Mode to Conditional Spawn. - Expand Advanced if needed and set:
| Setting | Value |
|---|---|
| Read Current Frame | Disabled |
| Update Source Data Every Tick | Leave enabled for now |
| Auto Transform Position Data | Enabled |
| Modify Spawn Count By Scalability | Disabled |
Conditional Spawn supports spawning a fixed number of particles per channel entry, with optional conditions. We will leave the conditions empty and set the count to one in the following steps. Do not use GlyphCount as a spawn count: every channel entry already represents one character.
Disabling Modify Spawn Count By Scalability prevents this spawn module from scaling individual character counts, which could leave incomplete text. Other Niagara scalability settings still apply.
Checkpoint: the wizard references the Default channel, uses Conditional Spawn, and reads previous-frame data. Stop on this page before proceeding to the spawn conditions and variable selection pages.
Step 21 — Choose the particle attributes and create the modules
- From the wizard's first page, click Next.
- On Spawn conditions, leave every checkbox unchecked. This allows all entries in this style's channel to be processed. Click Next.
- On the page asking which variables should be read into particle attributes, select all 12 variables: Position, GlyphIndex, GlyphOffset, GlyphSize, AtlasGrid, Lifetime, Velocity, MessageId, GlyphOrder, GlyphCount, StyleId and SizeMode.
- Keep Target Namespace = Particles and the proposed Module Name = Spawn From NDC_Floating_Default.
- Click Create Module and let Niagara finish compiling.
The wizard adds channel setup under Emitter Spawn, spawning logic under Emitter Update, and Init Particle From NDC under Particle Spawn. The selected values become particle attributes, such as Particles.GlyphIndex and Particles.GlyphOffset.
Checkpoint: inspect the generated modules in the emitter stack. Under Particle Spawn, Init Particle From NDC should follow Initialize Particle, so the channel's position, lifetime and velocity are not overwritten by initialization afterward. We will check the spawn count and channel reader settings next. A blank preview is still expected; no test data is being sent yet.
Step 22 — Verify the generated spawn and reader settings
Select the generated spawn module in Emitter Update and confirm:
| Setting | Value |
|---|---|
| Spawn Enabled | Enabled |
| Spawn Mode | Override |
| Min Count | 1 |
| Max Count | 1 |
This produces one particle per channel entry. Override here controls the generated module's spawn-count operation; it is a different setting from Conditional Spawn in the wizard. Leave Comparison Operator at Equals; no condition variables were selected.
Expand the advanced properties of Init Data Channel → Data Channel Reader and confirm:
| Setting | Value |
|---|---|
| Channel | The channel created for this style |
| Auto Link to Spawning NDC | Enabled |
| Read Current Frame | Disabled |
| Update Source Data Every Tick | Enabled |
| Only Spawn Once on Subticks | Enabled |
| Override Spawn Group to Data Channel Index | Disabled |
Access Context can remain disabled and None while Auto Link is enabled. The handler will read from the channel bucket that spawned it. If you renamed the example channel, use that same asset consistently in the reader and Floating Text Profile.
Step 23 — Initialize opacity and sprite rotation
Select Initialize Particle under Particle Spawn.
- Set Color Mode = Direct Set. Set Color to white with R = 1, G = 1, B = 1, A = 1. Our Material uses only this color's alpha, so the texture's painted colors remain unchanged.
- Set Sprite Rotation Mode = Direct Angle (Degrees) and set the angle to 0. This keeps the text upright with the renderer settings from Step 19.
- Keep Sprite Size Mode = Unset for now. We will connect the channel's GlyphSize to the renderer in a later step.
- Leave Position Mode and Lifetime as they are for this step. Init Particle From NDC must remain below Initialize Particle: it supplies Position and Lifetime from the channel after initialization. The displayed Lifetime of 5 in Initialize Particle is therefore not the final channel-driven lifetime.
- Save the system.
Checkpoint: particle alpha starts at 1 and sprite rotation at 0. The actual lifetime comes from the channel. No fade animation or size/material parameter bindings have been added yet.
Step 24 — Bind the character size
- Select Sprite Renderer and expand Bindings.
- Open Sprite Size Binding, currently set to
Particles.SpriteSize. - Search for
GlyphSizeand select Particles.GlyphSize. This Vector2D attribute was created by Init Particle From NDC. - Keep Position Binding = Particles.Position, Color Binding = Particles.Color, and Sprite Rotation Binding = Particles.SpriteRotation.
- Leave Dynamic Material Binding = Particles.DynamicMaterialParameter and Dynamic Material 1 Binding = Particles.DynamicMaterialParameter1 unchanged. We will populate these two attributes in the next step.
- Save the system.
Checkpoint: Sprite Size Binding now points to Particles.GlyphSize. The renderer will use the width and height received from the channel. The attribute already includes the style's Height, so no extra size multiplication is needed here.
If GlyphSize is missing from the list, compile the system and check that GlyphSize was selected in the wizard with Target Namespace set to Particles. Do not select GlyphOffset or AtlasGrid; those are also Vector2D but serve different purposes.
Step 25 — Add Dynamic Material Parameters
- In the emitter stack, click + beside Particle Spawn.
- Search for Dynamic Material Parameters and add the module.
- Place it immediately after Init Particle From NDC. The order should be:
Particle Spawn
Initialize Particle
Init Particle From NDC
Dynamic Material Parameters
- Select Dynamic Material Parameters to inspect its inputs. Keep the Sprite Renderer's existing Dynamic Material bindings unchanged.
This module will pack the channel attributes into the two vectors read by our Material. It must run after the channel reader so those attributes are available. We use Particle Spawn because the glyph index, grid and layout offset stay fixed for each particle's lifetime.
Checkpoint: the module is present in Particle Spawn after Init Particle From NDC. Its inputs still need configuring; do not expect correct atlas selection or spacing yet. Stop here to inspect the module's input controls before connecting the values.
Step 26 — Connect the glyph index to the Material
In Dynamic Material Parameters, keep Write Parameter Index 0 = Float. The first three inputs should display the names from the Material: GlyphOffsetX, GlyphOffsetY and GlyphIndex.
- Open the input dropdown at the right of GlyphIndex.
- Search for Make Float from Int and select that Dynamic Input. This converts the channel's integer cell index to the float expected by the Material.
- Expand the new Dynamic Input. On its integer input, choose Link Inputs and select Particles.GlyphIndex. Search for GlyphIndex if needed. Do not leave this input as a constant zero.
- Keep the checkbox beside GlyphIndex enabled. Leave GlyphOffsetX and GlyphOffsetY at zero for this step, and the unused fourth input at zero.
- Save the system.
Particles.GlyphIndex (Integer)
→ Make Float from Int
→ Dynamic Material Parameters: GlyphIndex (Float)
Checkpoint: GlyphIndex reads the particle attribute through the conversion instead of using a fixed value. The channel index still represents a whole-number atlas cell; conversion does not change its value. Index 1 remains Off until the atlas grid is connected in a following step, so the Material setup is not ready for an end-to-end rendering test yet.
Step 27 — Connect horizontal and vertical glyph offsets
Keep Write Parameter Index 0 = Float in Dynamic Material Parameters.
- Open the input dropdown for GlyphOffsetX and choose Make Float from Vector2D. Search for
Make FloatorVector2if needed. - Expand this Dynamic Input. Set its Vector 2D input to Link Inputs → Particles.GlyphOffset and set Channel to X.
- On GlyphOffsetY, add another Make Float from Vector2D.
- Link its Vector 2D input to the same Particles.GlyphOffset, but set Channel = Y.
- Keep both write checkboxes enabled. Keep the GlyphIndex conversion from Step 26 and leave the unused fourth value at zero.
- Save the system.
| Material input | Source | Selected component |
|---|---|---|
| GlyphOffsetX | Particles.GlyphOffset | X |
| GlyphOffsetY | Particles.GlyphOffset | Y |
Checkpoint: both offsets now read from the particle attribute rather than constant zero. These values already include the spacing and size chosen in the profile. Do not multiply them by Height again or add them to Particles.Position; the Material applies the offset through World Position Offset.
Index 1 is still Off. We will connect the atlas columns and rows next, before testing rendering.
Step 28 — Connect the atlas columns and rows
In Dynamic Material Parameters, expand Dynamic Parameter Index 1.
- Change Write Parameter Index 1 from Off to Float.
- For the first input, AtlasColumns, choose Make Float from Vector2D. Set Vector 2D → Link Inputs → Particles.AtlasGrid, then set Channel = X.
- For the second input, AtlasRows, choose Make Float from Vector2D. Link Vector 2D to Particles.AtlasGrid, then set Channel = Y.
- Keep the write checkboxes for both inputs enabled. Set the unused third and fourth values to zero.
- Leave Write Parameter Index 2 and Write Parameter Index 3 Off. Keep Index 0 as configured in the previous steps.
- Compile and save the system.
| Index 1 input | Source | Component |
|---|---|---|
| AtlasColumns | Particles.AtlasGrid | X |
| AtlasRows | Particles.AtlasGrid | Y |
| Third, unused | Constant 0 | — |
| Fourth, unused | Constant 0 | — |
If the first two labels appear as generic parameter names, verify that the renderer uses the intended Material Instance and that the parent Material's grid Dynamic Parameter has Parameter Index = 1. Apply and save the parent Material, then compile Niagara.
Checkpoint: the two Dynamic Material Parameter vectors now carry (GlyphOffset.X, GlyphOffset.Y, GlyphIndex, 0) and (AtlasGrid.X, AtlasGrid.Y, 0, 0). The Material receives the grid from each particle; do not hard-code 4 in these Niagara inputs. A blank preview is still expected until the handler setup is completed and channel data is sent.
Step 29 — Move the text using its velocity
The channel reader sets Particles.Velocity when each character spawns. Add a solver to move its position over time.
- In the emitter stack, click + beside Particle Update.
- Search for Solve Forces and Velocity and add it.
- Keep the order:
Particle Update
Particle State
Solve Forces and Velocity
- Leave the solver inputs at their defaults for this step. Do not add random velocity, gravity or extra force modules for the initial example.
- Compile and save the system.
All characters in one message receive the same velocity. For example, a style velocity of (0, 0, 60) moves their shared origin upward at 60 centimeters per second when no additional forces or drag are applied. The Material continues to place each character at its own camera-relative offset.
Checkpoint: Particle Update contains one Solve Forces and Velocity module after Particle State. Keep glyph offsets in the Material; do not add them to particle positions again. Movement will be checked when we send test data. The handler's lifetime and alpha fade still need configuring.
Step 30 — Add alpha fading
- Click + beside Particle Update and add Scale Color.
- Place it after Solve Forces and Velocity, keeping Particle State first.
- In Scale Color, choose the mode RGB and Alpha Separately if a mode selector is shown.
- Set Scale RGB to
(1, 1, 1). - Keep Color Value To Scale at its initial-color default,
Particles.Initial.Color. Do not link it to the current Particles.Color; repeatedly scaling the current color would compound the fade each frame. - Open the input dropdown for Scale Alpha and choose Float from Curve.
Particle Update
Particle State
Solve Forces and Velocity
Scale Color
Checkpoint: Scale Alpha uses a Float from Curve input. Stop here and inspect its curve controls before editing the keys. The next step will set a fade based on particle age, so characters with different lifetimes fade at the same relative point in their lives. The Material continues to use the atlas RGB directly; only particle alpha affects its opacity.
Step 31 — Keep the text visible, then fade out
Confirm that Scale Alpha uses Float from Curve, CurveIndex = Particles.NormalizedAge, Scale Curve = 1, and Color Value To Scale = Particles.Initial.Color.
Edit the float curve to contain these three keys:
| Time | Value (alpha multiplier) |
|---|---|
| 0 | 1 |
| 0.7 | 1 |
| 1 | 0 |
Select each existing key and use the Key Data fields beneath the graph to set its time and value. Add the middle key using the + button beneath the curve, then set its time to 0.7 and value to 1. Select the keys and choose Linear interpolation in the key context menu so the curve stays flat at 1, then slopes down to 0 without overshooting.
Time here is normalized age, not seconds: 0 is birth and 1 is the end of the particle's Lifetime. With a 1-second lifetime, fading begins at 0.7 seconds. With a 2-second lifetime, fading begins at 1.4 seconds.
Keep Use LUT and Optimize LUT enabled, and leave Expose Curve to Material disabled. Compile and save.
Checkpoint: the graph is horizontal at value 1 from time 0 to 0.7, then descends to 0 at time 1. This configures the fade; its visible result will be checked once the handler receives test data.
Step 32 — Remove particles when their lifetime ends
- Select Particle State at the top of Particle Update. Keep the module enabled and above Solve Forces and Velocity and Scale Color.
- Enable Kill Particles When Lifetime Has Elapsed.
- If Loop Particles Lifetime or Enable Particle Lifetime Looping is visible, leave it disabled. It may be hidden while lifetime killing is enabled.
- If a Lifetime input is exposed, keep it linked to Particles.Lifetime. Do not replace it with a fixed number: Init Particle From NDC supplies the lifetime for each character.
- Leave the age-update and delta-time settings at their defaults so particle age advances normally.
- Compile and save.
Particle State updates normalized age for the fade curve and removes the particle at the end of its lifetime. Alpha reaching zero only makes a particle invisible; it does not remove it by itself.
Checkpoint: lifetime killing is enabled and lifetime comes from Particles.Lifetime. This controls individual particles. We will configure the Niagara handler's own lifetime separately so it can receive new messages and finish when unused.
Step 33 — Finish the handler when it is unused
Keep Emitter State → Life Cycle Mode = System and Scalability Mode = System. In System State, keep Loop Behavior = Infinite, Inactive Response = Complete, and Loop Delay disabled. Loop Duration is not the lifetime of individual characters.
- On the System node, click + beside System Update. Use the System node, not the emitter's Emitter Update section.
- Add Complete If Unused after System State.
- Set Unused Time Till Inactive to 1.0 second as a starting value for this example. Do not set it to zero; leave time for channel data to reach newly spawned particles.
- Compile and save.
System Update
System State
Complete If Unused
Complete If Unused finishes the entire system after its total live particle count has remained zero for the configured period. It does not stop a system just because new messages have paused while existing characters are still alive. The one-second value is an idle grace period, not the text's Lifetime.
Checkpoint: System Update contains Complete If Unused after System State, and the emitter still inherits its lifecycle from the System. The handler can finish after its last particles die instead of remaining active indefinitely. We will verify new-message delivery and handler restart when testing the connected Data Channel.
Step 34 — Assign the Niagara handler to the channel
- Compile and save the Niagara System configured in the previous steps.
- Open the Data Channel asset used by its Init Data Channel reader.
- Set Default System to Spawn to that Niagara System.
- Save the Data Channel.
For the original example names, assign NS_Floating_Default to NDC_Floating_Default. If you renamed them to NS_FloatingText and NDC_FloatingText, use those assets instead. The important point is that the handler's reader references the same channel that spawns it.
NDC_FloatingText
Default System to Spawn → NS_FloatingText
NS_FloatingText
Init Data Channel → Channel → NDC_FloatingText
Keep Auto Link to Spawning NDC enabled on the reader. Do not place a separate copy of the System in the level or spawn it manually for each message; the Gameplay Burst channel manages its handler instances.
Checkpoint: both asset references point to the matching channel and handler, and both assets are saved. This connects the assets but does not send a message. The next step is to assign the channel to a style in a Floating Text Profile and prepare a test emission.
Step 35 — Create the Floating Text Profile
The profile defines which atlas cells represent each character. Its styles select painted textures and share a single Data Channel.
- In the Content Browser, right-click and choose Miscellaneous → Data Asset.
- Search for MassWardenFloatingTextProfile (the picker may display it with spaces), select that class, and create the asset.
- Name it
DA_FloatingTextand open it. If you already created a profile of this class, use that asset instead. - For the sample atlas, keep Atlas Grid at X = 4, Y = 4: four columns and four rows.
- Keep the default Glyphs entries if your texture uses the sample order below, read left to right and top to bottom.
0 1 2 3
4 5 6 7
8 9 + -
. , K M
Under Rendering, set Data Channel to NDC_FloatingText (or your matching channel). Expand Styles, then its first entry, and use these values:
| Setting | Value |
|---|---|
| Name | Default |
| Atlas Texture | T_FloatingAtlas (or your painted source texture) |
| Lifetime | 1.0 second |
| Height | 24.0 centimeters |
| Velocity | X = 0, Y = 0, Z = 60 |
| Priority | 0 |
This starting style displays characters for one second and moves them upward at 60 centimeters per second. Height is a world-space size, so apparent size changes with camera distance. The painted source texture is selected in the Style. The next step builds the array used by the handler material.
Leave the visibility settings at their defaults for now and save the profile. When adding more styles later, assign a painted Atlas Texture to each style and rebuild the array; the styles share this channel and handler. All styles in this profile share the same atlas grid and glyph order.
Checkpoint: the first style is named Default and has an Atlas Texture. The profile references your configured Data Channel. Complete the array and material setup below before sending a test message. If you edit the profile during testing, stop and restart Play In Editor before testing the changed settings.
Step 36 — Build the style texture array
The earlier steps used a Texture2D for the initial preview. The final setup uses a Texture2DArray: each layer holds one style's atlas. This lets different styles appear at the same time through one Sprite Renderer.
- Stop Play In Editor and save
DA_FloatingText. - Assign Atlas Texture for every entry in Styles. For example, Default uses a white atlas and Critical uses a gold atlas.
- Use matching source image dimensions, pixel formats, sRGB and compression settings. Use Default or UserInterface2D compression. Each texture must use the same grid and glyph order. Paint colors and adjustments into the source image.
- Click Build Atlas Array in the profile.
- The generated asset appears in Atlas Array and in the profile's Content Browser folder. Save both the profile and the generated asset.
The first Style uses slice 0, the second uses slice 1, and so on. Rebuild after replacing or reimporting a texture, adding or removing styles, or changing their order. No texture is built during gameplay.
Checkpoint: Atlas Array contains one layer per Style. Keep one Data Channel for this profile; a different profile with a different array needs its own channel and material binding.
Step 37 — Sample the array in the Material
- Open
M_FloatingText. - Add Texture Sample Parameter 2D Array and name it
AtlasTextureArray. Assign the generated array as its default texture. This replaces the earlierAtlasTextureTexture2D sample. - On Dynamic Parameter, Parameter Index 0, rename the fourth output to
StyleIdand leave its default value at0. - Keep the existing atlas-cell UV calculation. Connect its two-component result to Append Vector A, then connect
StyleIdto Append Vector B. - Connect the resulting three-component value to the array sample's UVs. The first two components select the character cell; the third selects the style layer.
- Connect the array sample's RGB to Emissive Color. Multiply its A by Particle Color A, then connect to Opacity (or Opacity Override).
- Keep the existing World Position Offset connections. Compile and save.
- Open
MI_Floating_Default, enableAtlasTextureArray, select the profile's generated array, and save.
Step 38 — Send StyleId to the Material
- Open
NS_FloatingTextand select Dynamic Material Parameters under Particle Spawn. - Under Dynamic Parameter Index 0, enable the fourth value, now named
StyleId. - Choose Make Float from Int, and bind its Int input to Particles.StyleId.
- Keep GlyphOffsetX, GlyphOffsetY, GlyphIndex, AtlasColumns and AtlasRows as previously configured.
- Keep one emitter and one Sprite Renderer using
MI_Floating_Default. Do not filter spawning to a single StyleId. - Compile and save, then restart Play In Editor.
Emit Default and Critical messages close together. Both should retain their own colors while they overlap in time. If both use the first texture, check the fourth dynamic parameter and the third component of the array sample UVs. If the wrong artwork appears, check the array assigned to the Material Instance and rebuild after any style-order changes.
Step 39 — Choose glyph types and style spacing
In the Profile, each Glyph has a Type:
- Character shows a single-character field, such as
0or+. - Symbol shows a name field, such as
ShieldorMiss.
Both use Atlas Index to select a cell. An ordinary space needs no glyph entry. In the Details panel, only the field for the selected Type is shown.
Spacing and sprite dimensions are configured once per Style:
| Setting | Default | Meaning |
|---|---|---|
| Advance | 0.65 | Horizontal advance for each visible character or symbol |
| Size | X = 0.65, Y = 1 | Sprite width and height |
| Space Width | 0.5 | Horizontal advance for each ordinary space |
These values are multiplied by Height. For Height 100, Space Width 0.5 gives a 50-centimeter gap. Spaces contribute to centering but create no particles. Every glyph in the message uses the same Size, including icons or whole-word symbols.
Step 40 — Emit mixed text and symbols
Use Emit Floating Text with an Identity, or Emit Floating Text At Location with a Profile and world Position. Both accept a Content array and a Style name. Replace earlier Emit Number, Emit Text, Emit Symbols and Emit Text At Location nodes with these functions.
Each Content item is a Mass Warden Floating Text Content struct. Select Text for a string, or Symbol for a named symbol glyph. For example:
| Array index | Type | Value |
|---|---|---|
| 0 | Text | 100 (including a trailing space) |
| 1 | Symbol | Shield |
| 2 | Text | +25 (including a leading space) |
Create a Symbol-type Shield mapping in the Profile first. All three items form one centered message with one Style. Convert a number to a String before putting it in a Text item.
In a Blueprint Make Struct node, both value pins may still be visible; only the field matching Type is used. The conditional field hiding applies to the Details panel.
A message supports up to 32 Content items and 32 total characters, spaces and symbols. Unknown mappings reject the entire message. Empty items are ignored; empty or space-only messages return false. Tabs and newlines are not supported as spacing commands.
Save the Profile and restart Play In Editor after changing its settings. No Material or Niagara changes are needed for this content and spacing update. Test 1 2 first: it should create two visible characters with a gap controlled by Space Width.
Step 41 — Choose world size or fixed screen size
Each Style now has Size Mode. World Space shows Height (cm), with the existing default of 24. Screen Space shows Height (px), default 32, to keep the sprite rectangle and character spacing the same size while zooming. Style Size, Advance and Space Width multiply the selected height. Transparent padding still reduces the visible artwork inside that rectangle.
Before using the updated profile, add SizeMode, type int32, to the Data Channel and read it into Particles.SizeMode in Init Particle from NDC. The channel now has 12 fields. In Dynamic Material Parameters Index 1, bind its third component to Make Float from Int → Particles.SizeMode. Name the matching Material Dynamic Parameter output SizeMode and default it to 0.
The Material must also resize each billboard around its particle origin every frame. Changing the Profile alone does not provide fixed screen size. Replace the existing World Position Offset branch using the following setup; keep atlas sampling and opacity unchanged.
Add a Custom node with output type CMOT Float3, with these inputs:
| Input | Connection |
|---|---|
| VertexOffsetVS | Absolute World Position (Excluding Material Shader Offsets) minus Particle Position WS, then Transform Vector World to View |
| GlyphOffset | Append GlyphOffsetX and GlyphOffsetY from Dynamic Parameter 0 |
| ViewDepth | Particle Position WS → Transform Position World to View → Component Mask B |
| SizeMode | Third output of Dynamic Parameter 1 |
Paste into the Custom node:
float2 scale = float2(1.0, 1.0);
if (SizeMode > 0.5)
{
float clipW = max(mul(float4(0.0, 0.0, ViewDepth, 1.0), ResolvedView.ViewToClip).w, 0.0001);
float2 projection = max(abs(float2(ResolvedView.ViewToClip[0][0], ResolvedView.ViewToClip[1][1])), float2(0.0001, 0.0001));
float2 viewport = max(ResolvedView.ViewSizeAndInvSize.xy / max(ResolvedView.ViewResolutionFraction, 0.0001), float2(1.0, 1.0));
scale = (2.0 * clipW) / (projection * viewport);
}
return float3(VertexOffsetVS.xy * (scale - 1.0) + GlyphOffset * scale, 0.0);
Connect the result through Transform Vector: View to World, then to World Position Offset, replacing the old branch. Keep the renderer camera-plane facing, centered, unaligned, with zero rotation. Compile and save both Material and Niagara, then restart Play In Editor.
Increase the channel's System Bounds Padding as needed for the largest message and viewing distance: Material expansion can extend beyond simulation bounds. Max Distance, outside-view culling and occlusion still apply. Velocity and the Trait Offset remain world-space values.
Test two styles at different distances and zoom while the particles are alive. World Space should shrink with distance; Screen Space should keep its pixel size and spacing. Also check orthographic zoom, viewport resizing and screen percentage changes. These are viewport pixels, not UMG DPI units. This setup still needs verification in your Editor and target platform.