UGTK / Toolkit / Ui Module / Text Typer System Completed
Text Typer System
Summary
The Text Typer System reveals a TextMeshPro text one character at a time — the classic typewriter.
A single component, Mn_TextTyperComponent, does everything: forward typing, two kinds of reverse
typing (from the end and from the start), an optional per-character fade, a large UnityEvent-friendly
API, and a custom inspector with buttons to drive it all without a line of glue code.
Note: the system is kept for backward compatibility and keeps working exactly as always. For new work the richer typewriter lives in TextAnimatorUGTK; this component can delegate to it by turning on Use Text Animator (see TextAnimatorUGTK integration), keeping every existing reference and inspector event untouched.
Content
- Modules Dependencies
- Setup
- How To Use
- Inspector Controls
- TextAnimatorUGTK integration
- Testing Scene
- Technical Info
Modules Dependencies
- TextMeshPro (Unity package): the component writes into a
TextMeshProUGUI. - TextAnimatorUGTK: referenced by the assembly definition and required to compile. It is only used at runtime when Use Text Animator is on, but the reference must be there — import both modules together.
Only in the demo scene. The Debug scene of this module also uses GameplaySystems/AudioSystem.
Nothing in the runtime code needs it: import this module on its own and it works, but
the demo scene opens with a missing script where that component was.
Setup
Drop the TextTyper prefab under a Canvas — a TextMeshProUGUI with Mn_TextTyperComponent
already wired to itself (0.2 s per character, 0.2 s fade, Play On Start on, a Lorem ipsum message):
There is a second prefab, TextTyperBox: the same typer inside a ScrollRect with a background
and a vertical scrollbar — the ready-made dialogue box.
Or set it up by hand: add Mn_TextTyperComponent to any GameObject and assign Target Text Mesh.
How To Use
Tune the timing under Writing Settings — speed up the typing and shorten the fade with it:
Further down, the inspector's Timing section recomputes live how long a character step really lasts and how much of the fade actually fits inside it — see Inspector Controls.
Core
- Target Text Mesh: the TextMeshProUGUI to write into. Without it the component logs an error
and fires the onFinish… event straight away.
- Play On Start: calls StartWrite() in Start().
- Prepare Invisible On Enable: on Awake/OnEnable the TMP is put in the "text assigned, zero
visible characters" state. Leave it on — it is what prevents the classic one-frame flash of the
whole message before typing begins.
- Writing Settings: timePerCharacter, startDelay, fadeDuration (see below).
- Message: the text to type (multiline).
The timing rule — this is the part that surprises people:
- every character step lasts exactly timePerCharacter seconds (scaled time);
- fadeDuration runs inside that step and is clamped to timePerCharacter, so it never makes
the typing longer. Want a visible fade? Raise timePerCharacter, don't raise the fade;
- with timePerCharacter = 0 the whole message appears in the same frame — and every
onCharacterTyped still fires, once per non-whitespace character;
- negative values are clamped to 0 on Awake, on OnValidate and on every setter.
textTyper.StartWrite(); // types `message` forward
textTyper.SetMessage_AndStartWrite("Ciao"); // change + type, in one call
textTyper.StartReverseWrite(); // erases from the END, last char first
textTyper.StartReverseWriteFromStart(); // erases from the START, first char first
textTyper.StopTyping(); // freeze where it is
textTyper.ResetVisibleCharactersToZero(); // hide everything again, keeping the layout
textTyper.SetInstantTypingOn(); // "skip" button: finishes the current write now
textTyper.SetTimePerCharacter_ApplyNow(0.05f);
bool typing = textTyper.IsTyping();
var state = textTyper.GetTypingState(); // Idle / WritingForward / WritingReverseFromEnd / WritingReverseFromStart
Settings can be changed while typing: every routine re-reads them at each step. The setters come
in three flavours so they can be wired straight to a Button's onClick:
SetX(value) (applies to the next write), SetX_ApplyNow(value) (applies to the write in progress)
and SetX_Restart(value) (restarts the current routine with the new value).
Events — three families, one per direction:
onStartWriting / onCharacterTyped / onFinishWriting,
onStartReverseWriting / onCharacterReverseTyped / onFinishReverseWriting,
onStartReverseFromStartWriting / onCharacterReverseFromStartTyped / onFinishReverseFromStartWriting.
The per-character events fire only for non-whitespace characters, so a typing sound does not
click on spaces.
Good to know:
- the routines use WaitForSeconds, so they freeze with Time.timeScale = 0;
- StartWrite() calls StopAllCoroutines() on this GameObject: do not park unrelated coroutines on
the same object;
- reverse-from-end works on maxVisibleCharacters; reverse-from-start works on per-character
vertex alpha, so it keeps the tail of the sentence in place instead of reflowing it.
Inspector Controls
The custom inspector adds a TextTyper Controls block: a Quick Message field (with Copy Current Message and Apply Quick Message, which also previews the text in Edit Mode) and the buttons Start Write, Reverse (From End), Reverse (From Start), Stop Typing — enabled in Play Mode — plus Reset Visible Characters, Reset All, Enable/Disable Component and the two Play On Start: ON/OFF shortcuts, which work in Edit Mode too.
Above them sits the Timing (NEW rule) section with a live Computed readout — Step per char, FadeDuration, Effective Fade (the fade actually applied, clamped to the step), Start Delay and the estimated total duration of the message. It is the fastest way to see that a fade longer than the step is silently cut.
Everything the buttons change in Edit Mode goes through Undo.RecordObject, so Ctrl+Z puts it back.
TextAnimatorUGTK integration
Mn_TextTyperComponent can delegate its typing to
TextAnimatorUGTK,
gaining tags, effects and inline actions while keeping the classic API and inspector events untouched.
- Off by default: with
useTextAnimatordisabled the component behaves exactly as it always has — old scenes and projects updating UGTK are not affected. - Turn on Use Text Animator in the inspector (or call
ConfigureTextAnimator(true, tags)) and the component adds/uses aMn_TextAnimatorUGTKTexton the target TMP:timePerCharacteris mapped to characters-per-second (1 / timePerCharacter, and a 0 step becomes 1000 cps), the optional Text Animator Tags are prepended to the message (e.g.<wave>), and the classic events keep firing (onCharacterTyped,onFinishWriting,onFinishReverseWriting). StartWrite,StartReverseWriteandStopTypingdelegate transparently; every other method keeps its meaning.- Two caveats worth knowing:
IsTyping()reads the classic coroutine, so it returnsfalsewhile the TextAnimator is doing the work (useGetTypingState()instead), andStartReverseWriteFromStart()has no TextAnimator counterpart — it always runs the classic routine.
Testing Scene
Open Debug/TextTyperSystemTest.unity and press Play: the TextTyper prefab types the Lorem ipsum
message character by character with its 0.2 s step and 0.2 s fade:
The scene shows the reference setup for the typewriter click: the TextTyperBox instance has its
onCharacterTyped wired to Mn_AudioUserRandomClips.Play from the
Audio System.
That is exactly why the per-character events skip whitespace — the click would sound wrong on spaces.
Technical Info
| Path | Content |
|---|---|
Scripts/Runtime/S_WritingSettings.cs |
Serializable struct: timePerCharacter, startDelay, fadeDuration |
Scripts/Runtime/Mn_TextTyperComponent.cs |
The component: forward/reverse routines, instant path, UnityEvent-friendly API, TextAnimatorUGTK adapter |
Scripts/Editor/Mn_TextTyperComponent_Editor.cs |
Custom inspector: Timing section with computed values, Quick Message, playback buttons |
Prefabs/TextTyper.prefab |
TMP text + Mn_TextTyperComponent pre-configured |
Prefabs/TextTyperBox.prefab |
The same typer inside a ScrollRect with background and scrollbar |
Debug/TextTyperSystemTest.unity |
Test scene: Canvas + TextTyper |
Mn_TextTyperComponent — main API: StartWrite(), StartReverseWrite(),
StartReverseWriteFromStart(), StopTyping(), ResetVisibleCharactersToZero(),
PrepareIdleInvisibleImmediate(), ChangeMessage(), SetMessage*(), SetTimePerCharacter*(),
SetStartDelay*(), SetFadeDuration*(), SetWritingSettings(), SetInstantTypingOn()/Off(),
ConfigureTextAnimator(), IsUsingTextAnimator(), IsTyping(), GetTypingState() and the Get*
family. E_TypingState reports the routine in progress.
© 2026 Marcello De Bonis. All rights reserved
Depends on 1
Used by 1
Measured from the repository: code references (asmdef) plus prefab and asset GUIDs. Importing this module alone brings in 1 module in total.
UGTKengine within an engine


