Skip to content

SirDiabo/DialogBoxSystem

Repository files navigation

DialogBoxSystem for Unreal Engine

Showcase GIF

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 ControlWidget overlay so you can tweak every parameter live and spawn new boxes on the fly.


Support my work!

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!


How it works

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 overview

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

DialogBoxWidget — detailed breakdown

Startup (Event Construct)

When the widget is constructed, it immediately:

  1. Calculates the timer interval from Sym Per Sec — specifically 1 / (Sym Per Sec * 25), giving a finer-grained tick rate than a straight characters-per-second setup
  2. Starts a looping timer calling Populate Textbox, with an optional Initial Start Delay offset
  3. Plays the Open widget animation forward
  4. Sets input mode to UI Only, shows the mouse cursor, and sets keyboard + user focus to itself
  5. Plays the open sound effect (SX_Open)

Populate Textbox (the reveal loop)

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.


Variables exposed on the widget

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

Kill (custom event)

Calling Kill on the widget triggers the close sequence:

  • If Auto Kill is true (called internally when the reveal finishes), it waits Auto Kill Delay seconds, then clears all children from WrapBox_Textbox, plays the Open widget animation in reverse, plays SX_Close, waits a short fixed delay (0.2s), switches input back to Game Only, hides the mouse cursor, broadcasts the Close delegate, then calls RemoveFromParent
  • If Auto Kill is false, Kill works the same way when called from outside — it just won't trigger automatically on its own

Skip Populate (custom event)

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.


The Close delegate

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.


ControlWidget — the demo driver

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 Text which spawns a new box. The field also has an OnTextChanged binding 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 Shake bool
  • Font Size slider — maps directly to the Font Size variable
  • 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.


Using it in your own project

  1. Migrate DialogBoxWidget, DialogWordWidget, DialogCharacterWidget, and DialogLineBreakWidget into your project

  2. Create DialogBoxWidget and add it to your viewport or HUD at whatever Z-order you need

  3. Set the exposed variables — Text, Sym Per Sec, Font, Font Size, Shake, Auto Kill, Auto Kill Delay, Initial Start Delay

  4. Bind to the Close delegate if you need to react when the box finishes and removes itself

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

    Creating Dialog Image


Fonts featured in this project

The fonts used in this project have been made by my "Image to Font Converter" tool, which you can check out here.


License

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

About

A Unreal Engine 5 showcase project implementing a Dialog Box System with a typewriter-style character reveal.

Topics

Resources

License

Stars

Watchers

Forks

Contributors