An Unreal Engine 5 showcase project implementing a Dialog Box System with a typewriter-style character reveal. Each character gets its own widget, words are grouped into their own containers, line breaks are handled, and the whole thing supports per-character shake animations, custom fonts, configurable reveal speed, auto-kill, and sound effects.
Grab the packaged demo build from the Releases tab! The demo shows the dialog box in action with a
ControlWidgetoverlay so you can tweak every parameter live and spawn new boxes on the fly.
If this saved you some time or you just think it's neat, you can throw a coffee my way over on Ko-fi. It genuinely helps me keep making stuff like this!
Rather than animating text directly in a single Text Block, this system builds dialog one character at a time by spawning individual DialogCharacterWidget instances. Characters are grouped into DialogWordWidget containers (one per word), and those word containers live inside a WrapBox — so line wrapping happens naturally without any manual calculation. Newline characters get their own DialogLineBreakWidget that forces a new row.
The result is that every character is an independent widget that can be individually animated, shaken, skewed, or styled however you want.
| Widget | Role |
|---|---|
DialogBoxWidget |
The main widget. Orchestrates the reveal timer, manages state, plays open/close animations, handles sound |
DialogWordWidget |
Groups characters belonging to the same word into a HorizontalBox |
DialogCharacterWidget |
A single character. Accepts a glyph string, font, font size, skew amount, and a shake toggle |
DialogLineBreakWidget |
An empty spacer widget inserted when a newline character is encountered, forcing a WrapBox row break |
When the widget is constructed, it immediately:
- Calculates the timer interval from
Sym Per Sec— specifically1 / (Sym Per Sec * 25), giving a finer-grained tick rate than a straight characters-per-second setup - Starts a looping timer calling
Populate Textbox, with an optionalInitial Start Delayoffset - Plays the
Openwidget animation forward - Sets input mode to UI Only, shows the mouse cursor, and sets keyboard + user focus to itself
- Plays the open sound effect (
SX_Open)
This is where the typewriter effect lives. It fires on every timer tick and processes one character at a time using a Processing Index integer that increments each call.
Per-tick logic:
Characters = GetCharacterArrayFromString(Text)
CurrentChar = Characters[Processing Index]
Word grouping:
The widget maintains a Word Box Ref — a reference to the current DialogWordWidget being filled. Each character gets appended to the horizontal box inside whatever word container is currently active.
Space detection:
When CurrentChar equals " " (space), Word Box Ref is set to null. On the next non-space character, the IsValid check on Word Box Ref fails, which triggers creation of a new DialogWordWidget. This is how word boundaries are tracked — no explicit parsing, just a null reference acting as a "start new word" signal.
Newline detection:
When the ASCII value of CurrentChar equals 10 (line feed), a DialogLineBreakWidget is added to WrapBox_Textbox instead of a character. This forces the WrapBox to begin a new row, giving you hard line breaks in your dialog.
Regular characters:
For everything else, the system checks if Word Box Ref is valid. If it isn't (start of text, or just after a space), it creates a new DialogWordWidget, stores it as Word Box Ref, and adds it to WrapBox_Textbox. Then a DialogCharacterWidget is created with the current glyph, font, font size, skew amount, and shake flag, and added to the word widget's HorizontalBox.
Typing sound:
When adding a regular character, if Skipping is false, it plays SX_Type with a randomized pitch between 1.2 and 1.3, giving each character a slightly different sound.
Completion:
Once Processing Index exceeds Length - 1 (all characters revealed), the timer is cleared, Finished is set to true, and the Auto Kill branch is evaluated.
| Variable | Type | What it does |
|---|---|---|
Text |
String | The dialog content to reveal |
Sym Per Sec |
Float | Characters revealed per second (approx) |
Initial Start Delay |
Float | Delay before the reveal starts |
Font |
Font | The font to use for all characters |
Font Size |
Float | Font size passed to each character widget |
Skew Amount |
Float | Italic-style skew applied to each character |
Shake |
Bool | Whether characters play a shake animation |
Auto Kill |
Bool | Whether the widget removes itself after finishing |
Auto Kill Delay |
Float | How long to wait after finishing before the close sequence begins |
Calling Kill on the widget triggers the close sequence:
- If
Auto Killis true (called internally when the reveal finishes), it waitsAuto Kill Delayseconds, then clears all children fromWrapBox_Textbox, plays theOpenwidget animation in reverse, playsSX_Close, waits a short fixed delay (0.2s), switches input back to Game Only, hides the mouse cursor, broadcasts theClosedelegate, then callsRemoveFromParent - If
Auto Killis false,Killworks the same way when called from outside — it just won't trigger automatically on its own
Calling Skip Populate fast-forwards the reveal. If Finished is already true it does nothing. Otherwise it sets Skipping = true and resets the timer to a much faster interval, effectively dumping all remaining characters as fast as the engine allows. It also plays SX_Skip.
DialogBoxWidget exposes a Close delegate. This fires right before the widget removes itself, so you can bind to it from whatever spawned the box and react when the dialog finishes — advance to the next line, re-enable game input, spawn the next box, etc.
In the demo, ControlWidget binds a Close_Event custom event to this delegate when it creates a new box, and uses it to flip input mode back to UI Only so the control panel stays usable.
The demo control widget lets you configure and spawn dialog boxes live. Its variables map directly onto DialogBoxWidget's exposed parameters:
- Text input — an editable text field. Pressing Enter calls
Commit Textwhich spawns a new box. The field also has anOnTextChangedbinding so the current text is tracked even before committing. If the field is empty when you press the button, it falls back to a default placeholder string rather than spawning an empty box - Toggle Font button — flips between two fonts using a FlipFlop macro (in the demo, a fancy glyph font and a pixel font). Also updates a label to show which is active
- Toggle Shake button — inverts the current
Shakebool - Font Size slider — maps directly to the
Font Sizevariable - Skew Amount slider — maps directly to
Skew Amount - Sym Per Sec slider — maps directly to
Sym Per Sec - Toggle Auto Kill button — inverts
Auto Kill - Auto Kill Delay slider — maps to
Auto Kill Delay
When a box is spawned, ControlWidget binds to its Close delegate and uses the callback to restore input mode to UI Only so the control panel keeps focus.
-
Migrate
DialogBoxWidget,DialogWordWidget,DialogCharacterWidget, andDialogLineBreakWidgetinto your project -
Create
DialogBoxWidgetand add it to your viewport or HUD at whatever Z-order you need -
Set the exposed variables —
Text,Sym Per Sec,Font,Font Size,Shake,Auto Kill,Auto Kill Delay,Initial Start Delay -
Bind to the
Closedelegate if you need to react when the box finishes and removes itself -
The sounds (
SX_Open,SX_Close,SX_Type,SX_Skip) are assets in the project — swap them out with your own or wire them up to your audio system
The fonts used in this project have been made by my "Image to Font Converter" tool, which you can check out here.
This project is licensed under the MIT License - see the LICENSE file for details.
MIT License
Copyright (c) 2026 SirDiabo
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
Or in short, do whatever. Go crazy. Credit would be cool, but don't expect to be sued if you don't.
Made with <3 by SirDiabo

