Text Animation
01 Your First Animated Text
JuiceBox Text animates the individual characters of a TextMeshPro text, or of a UI Toolkit label on Unity 6000.3 and later (section 11). You mark the characters you want to move with a tag, <wave>like this</wave>, and each marked character plays a JuiceBox sequence of its own, a little later than the one before it, so the effect ripples across the line.
This guide assumes you have read the Pro Quick Start Guide, in particular Animation Definitions (section 13, which introduces Animation Profiles) and Per-Run Options (section 17, whose last part covers injecting one profile onto many objects). Every text effect is a sequence in an Animation Profile, and that is all it is: nothing about text needs a new kind of asset.
Example: A Waving Title
- Create a TextMeshPro text: GameObject > UI > Text - TextMeshPro for UI, or GameObject > 3D Object > Text - TextMeshPro for a world-space label. Both work.
- Select it and choose Add Component > JuiceBox > JuiceBox Text. The component takes over whatever text the TextMeshPro already shows.
- Drag TextAnimation/Profiles/TextEffects into the component's Profile field. That profile holds every shipped effect.
- In the component's Text box, type
Welcome to the <wave>JuiceBox</wave> arcade! - Press Play. "JuiceBox" waves; the rest of the line stays still.
Type your text into the JuiceBox Text component, not into the TextMeshPro component. JuiceBox Text holds the tagged text and hands TextMeshPro the same text with its own tags removed, so TextMeshPro never sees a tag it does not understand.
The effects play in Play mode. To see an effect while you edit it, open the profile in the Sequence Editor (section 08), which plays it across a sample text in the profile's preview stage.
02 Writing Tags
A tag is the name of a sequence in the profile. <wave> plays the sequence called wave, <shake> the one called shake, and a sequence you add yourself called jitter is played by <jitter> with no further setup.
- Open and close.
<wave>starts the effect and</wave>ends it. A tag left open runs to the end of the text. - Case does not matter.
<Wave>and<WAVE>both play wave. When two sequences differ only by case, the exact match wins. - Different tags can overlap freely:
<wave>big <rainb>bright</wave> colours</rainb>is fine, and the characters inside both play both (section 04). - The same tag nested inside itself counts as one: the outer pair decides where it starts and ends.
- TextMeshPro's own tags still work.
<b>,<color=red>,<size=120%>and the rest are passed straight through to TextMeshPro. Any tag that is not the name of a sequence is left alone. - Spaces are not animated. Whitespace takes no part in an effect, so a ripple steps from letter to letter without pausing on the gaps.
- To show a tag literally, wrap it in TextMeshPro's
<noparse>:<noparse><wave></noparse>shows the word <wave> on screen. </>closes every open angle-bracket tag, so<wave><rainb>both</> neitherneeds no second closing tag.- Braces play an effect once, as the character appears or disappears:
{fade}and{#fadeout}(section 07). Braces that do not name a sequence, such as{0}, are ordinary text. Any brace tag can also be written inside angle brackets,<{fade}>, which is the form to use when another tool rewrites your text (section 11).
Modifiers
A tag can tune the effect for just the characters it covers. Write modifiers after the tag's name, separated by spaces or line breaks, with nothing around the =:
A <wave a=0.3 s=1>tall, slow</wave> wave and a <shake d=0.5>brief</shake> shake.
| Modifier | What it does |
|---|---|
a= s= d= | Write the effect's amp, speed and duration (section 08): how far, in the effect's own units (ems, degrees or scale); how fast, in cycles per second; how long, in seconds. d=inf runs a looping effect or a shake forever, which is their default. |
name= | Writes any number parameter the effect declares, by its full name: amp=0.3 is a=0.3, and an effect of your own with a height parameter takes height=2. |
name* | Multiplies the profile's value instead of replacing it: <wave a*2> waves twice as high as the profile says, whatever that is. |
stagger= | The seconds between one character's start and the next, for this span only, in place of the sequence's Stagger. stagger=0 moves the span as one, a negative value ripples from the last character to the first, and stagger*2 doubles the sequence's own. |
by=word by=line | What one step of the ripple covers. By default each character starts one stagger after the one before; by=word starts each word as one and steps per word, by=line per line as the text is laid out. It works with stagger= too: by=word stagger=0.3 starts a word every 0.3 seconds. |
A modifier the effect does not have, a value that is not a number, a multiplication that does not give one (twice forever is forever, but nothing times forever is not a number), or an inf on an effect that cannot run forever is ignored and reported in the Console, once per effect; the tag still plays. When the same modifier is written twice, the last one wins. A tag nested inside the same tag keeps the outer tag's modifiers. by and stagger always mean the two modifiers above, so a parameter of your own needs another name.
Everything after the name has to be a modifier. A tag with anything else in it, <wave now> or <wave a = 2>, is not treated as an effect tag at all and shows up on screen as you wrote it.
Each tagged character runs its own copy of the sequence. The sequence's Stagger (section 08) sets how far behind the previous character each one starts: this is what turns one bob into a wave. Two separate spans of the same tag share one ripple across the whole text, so <wave>left</wave> and <wave>right</wave> waves as a single line would, not as two waves that each start over.
03 The Shipped Effects
Every tag below is a sequence of TextEffects. Assign that profile and every one of them is available.
| Tag | What it does | Modifiers |
|---|---|---|
| <wave> | Characters bob up and down in a smooth sine, one after another. | a s d |
| <bounce> | Characters hop upward and land, as if on springs. | a s d |
| <shake> | Characters jitter in random directions, each on its own. | a s d |
| <wiggle> | Characters trace small looping figure-of-eight paths. | a s d |
| <slideh> | Characters slide left and right (the h is horizontal). | a d |
| <swing> | Characters rock back and forth around their centres. | a s d |
| <rot> | Characters spin continuously. | d |
| <incr> | Characters grow and shrink from their own size. | a d |
| <fade> | The whole span disappears at once, then fades back in one character after another. | d |
| <rainb> | Characters cycle round the colour wheel. | d |
| <pend> | Characters swing out and settle like a pendulum, hanging from their top edge. | a |
| <dangle> | Like pend, with a looser, longer settle. | a |
| {size} | An appearance: the character grows from nothing, with a small overshoot. | d |
| {offset} | An appearance: the character rises into place from a ems away (negative is below). | a d |
| {turn} | An appearance: the character turns in from a degrees. | a d |
| {#fadeout} | A disappearance: the character fades away. | d |
| {#shrink} | A disappearance: the character shrinks to nothing. | d |
| {#fall} | A disappearance: the character drops a ems (negative is down) from wherever it is. | a d |
fade, size, offset and turn are made to be written in braces as appearances, and fadeout, shrink and fall with # as disappearances (section 07).
On shake and wiggle, a= is a multiplier of the size the profile authored rather than a distance in ems: <shake a=2> shakes twice as far, <wiggle a=0.5> traces a figure-eight half the size, and a=0 holds the characters still.
The looping effects run until their characters change or you stop them; give one a d= to end it on its own. fade plays once and holds at fully visible.
Sizes are in ems, multiples of the character's own font size. A wave that rises 0.1 em rises a tenth of a letter on a small UI label and on a large billboard alike, so one profile looks right on text of any size.
04 Stacking Effects
A character inside several tags plays all of them at once, and their effects add up:
| Drives | Stacks by |
|---|---|
| Position (any offset) | Adding the offsets: a wave on top of a shake bobs while it jitters. |
| Rotation, hanging rotation | Adding the angles. A rotation and a hanging rotation both apply. |
| Scale, opacity, colour | Adding each effect's change: two effects that each grow a letter from its own size to double make it three times as big, and two fades that each take away half its opacity leave it invisible. |
Two effects that drive exactly the same thing, wave and bounce for example (both move characters vertically), are added together through a combiner made for them, exactly as two injected lanes on one member are in Pro, so they add rather than fighting over the value. In such a stack each effect starts from the stacked value rather than from its own Initial Value: a fade stacked with another opacity effect starts from the letter's current opacity, not from invisible.
An appearance or a disappearance (section 07) is not stacked that way. It plays on top of whatever the character's ordinary tags are doing: its offsets and angles add to theirs, its size and opacity multiply theirs, and its colour replaces theirs while it differs from the character's own. So {size}<incr> grows the letter in from nothing while incr pulses, and {fade}<rainb> fades in a letter that is already cycling colour. Two appearances that drive the same thing on one character are still added together, as above.
Example: A Glowing, Waving Word
You found the <wave><rainb>Golden Key</rainb></wave>!
Each letter of "Golden Key" moves with the wave and cycles colour at the same time. The two effects drive different things, so each keeps its own shape.
Styles: One Tag for Several
A combination you use often can have a name of its own. Select the profile, click Add Extension under its Preview Prefab and choose Text Styles, then add a style:
| Field | Example | What it is |
|---|---|---|
| Name | special | The tag that uses the style: <special>...</special>. Letters, digits, - and _. |
| Opening | <bounce a=1.5><rainb><?sparkle> | What <special> stands for. Anything a text can hold: effects with their modifiers, appearances such as {fade}, events, <speed> and <waitfor>, rich-text tags, even another style. |
| Closing | </color> | What </special> stands for. The effects Opening started close by themselves, so this is only for rich-text tags such as <color> or <b>. Leave it empty otherwise. |
You found the <special>Golden Key</special>! now bounces, cycles colour and raises On Event with sparkle as the typing reaches it. Styles live on the profile beside the effects they use, so every text that uses the profile has them, and a translator sees one tag to keep instead of a string of effect names and numbers.
A style is written as <name> only: it takes no modifiers, and its Opening decides whether each effect is ordinary, an appearance or a disappearance. An effect of the same name wins over a style, and the profile's inspector warns about that, about a name that cannot be a tag, about two styles with one name, about a name that is also a rich-text tag such as b (the style would replace bold in every text), and about a tag in Opening or Closing that names nothing it knows, which would show as text. A style that uses itself, directly or through another, is skipped with a warning in the Console.
05 Changing the Text from Code
JuiceBox Text lives in the namespace JuiceBox.TextAnimation. Set its Text property, or call SetText, with a tagged string:
using JuiceBox.TextAnimation;
public class Dialogue : MonoBehaviour
{
[SerializeField] JuiceBoxText line;
public void Say(string who, string what)
{
line.Text = who + ": " + what; // e.g. "Guard: <shake>Halt!</shake>"
}
}
A new text keeps the effects running on the characters it keeps: the unchanged start and end of the text carry on, the characters you edited start their effects afresh, and a character that loses its tag goes straight back to its place. A character whose turn in a staggered effect moved, one after an insertion or a deletion, starts again at its new turn; typing at the end moves nobody's turn, so the rest of the text carries on. Setting the text it already shows changes nothing, so it is safe to assign every frame from a binding or an update loop: the effects keep playing rather than starting over.
You can also keep writing to the TextMeshPro component the way you always have (tmp.text = ..., or a localisation system that does it for you). JuiceBox Text notices the change and treats the new string as its tagged text.
The Profile property swaps the whole set of effects: assigning a different profile re-reads the tags against it.
| Member | Purpose |
|---|---|
| Text | The tagged text. Assigning a different string applies it. |
| SetText(string) | The same as assigning Text. |
| Profile | The Animation Profile whose sequences the tags name. |
| Handle | The running effects of the current text, as one InjectionHandle (section 06). |
| SetParameter(tag, name, value) | Changes one parameter of an effect on every character playing it, without restarting them (section 06). |
06 Controlling a Running Text
Every character of the current text is one lane of a single injection, and Handle is that injection's handle. It is the same InjectionHandle that JuiceBoxCentralController.Inject returns in Pro, so everything you can do to an injection you can do to a text:
InjectionHandle fx = line.Handle;
fx.Pause(); // freeze every effect mid-motion
fx.Resume();
fx.SetTimescale(0.5f); // half speed, e.g. during a slow-motion moment
fx.Restart(); // replay the running effects from the start, stagger included
fx.Stop(); // end them; the text stays as it is
The handle is empty while the text has no tagged character. It carries on across text changes, so a Pause or SetTimescale on it also reaches the characters a later text adds, but a text with no tags, a new profile, or a text whose effects had all finished replaces it, so read line.Handle when you need it rather than keeping an old one.
Restart replays effects that are still running. A staggered effect ripples in again: every character waits out its stagger and then starts over. A Prewarm effect (wave and bounce ship with Prewarm, section 08) restarts every character at once, already as far along as its stagger would have put it. While it waits, a character holds the pose it had when you called Restart, unless the effect is set to Initialize (section 08), in which case it goes straight back to where the effect starts. Once every effect on the text has finished, as a one-shot fade does, the injection has ended and Restart does nothing: it returns false. Restart is for persistent tags: appearances and disappearances (section 07) are timed by the reveal, not by the stagger, so to play them again call StartReveal.
Driving an Effect from the Game
To make an effect follow something in the game, a shiver that grows as the timer runs down, do not rewrite the tag: a new a= in the string starts those characters over. Give the parameter a new value on the running effect instead:
line.SetParameter("shake", "amp", 2f); // every character playing <shake>, now and later
SetParameter takes the tag, the name of one of its number parameters (the same names a modifier uses, in full: amp, speed, duration, or your own) and the value. Section 03 lists which parameters each shipped effect has. Nothing restarts. The characters already playing follow at once (a shake picks a new size for each jitter, so it follows from its next one, within an eighth of a second), and characters a later text adds start with the value. A parameter an effect only reads when it starts, such as a duration, reaches the characters that start after the call. The value lasts until you assign a different Profile.
The value takes the place of the one in the profile, so modifiers work on it as they always do: <shake a*2> shakes twice as hard as the value you set, and keeps doing so as you change it, while <shake a=0.5> still sets its own size. A value that is not a number is refused, with a warning in the Console.
The demo scene, Text Demo in TextAnimation/Demo/, does exactly this: its Dread slider calls SetParameter for shake and wiggle.
You can also do it with no code at all. Put your component on the profile's Preview Prefab as well as beside the text, open the profile in the Sequence Editor, and bind the effect's input (a Shake's Amplitude, say) to one of the component's members, exactly as you would bind any input in Pro. Every character then reads that member from the component beside its own text each frame, so changing it there changes the whole effect.
Reduced Motion
For a player who asks for less movement, set one value from your options menu when the game loads and whenever the player changes it:
JuiceBoxText.MotionScale = reduceMotion ? 0f : 1f; // or anything between, to tone it down
It reaches every text at once, from its next frame, with nothing restarting: offsets, rotations and size changes are scaled by it, so at zero a wave or a shake stands still and an appearance that only moves, such as offset or turn, simply shows. Colour and alpha are left alone, so fade, rainb and the typewriter play as before. The value lasts until play stops, so set it again from your saved options each time the game starts. Tick Ignore Reduced Motion on a text whose movement the player needs to see.
Example: Replaying a Fade-In
fade plays once. To play it again on the same text, such as when a dialogue box reopens, apply the text afresh. Setting the text it already shows changes nothing, so clear it first; both assignments happen before the text is drawn, so it never shows empty:
void OnEnable()
{
string tagged = line.Text;
line.Text = "";
line.Text = tagged;
}
07 Typing a Text Out
A JuiceBox Text can hide its text and reveal it a character at a time, the way a dialogue box does. Set the component's Reveal field:
| Reveal | What happens |
|---|---|
| Shown | The default. A text shows in full as soon as it is set. |
| On Text | Each new text starts hidden and types itself out. |
| On Enable | As On Text, and the text types out again every time the component is enabled. |
| Script | Each new text starts hidden and waits for your code. |
Characters Per Second sets the typing rate and Reveal Speed multiplies it; give RevealSpeed to your player as a text-speed setting. Neither touches the effects: a wave keeps its own speed however fast the line is typed, and Handle.SetTimescale slows the effects without slowing the typing. A hidden character's effects are already running, so a wave is in step the moment its letters appear.
The typing counts game time, and so do the effects of a profile on the usual segments, so they stop while the game is paused with Time.timeScale at zero. Tick Unscaled Time on a text that must move while the game stands still, such as one on a pause menu: its typing and every effect on it then run at full speed whatever Time.timeScale is.
line.StartReveal(); // hide the text and type it from the start
line.RevealTo(12); // show the first 12 characters, e.g. to follow a voice-over
line.RevealNext(); // one more
line.RevealAll(); // the player's skip: show everything now
line.Hide(); // hide it again
bool busy = line.IsTyping; // true until the last character is revealed
Characters are counted as TextMeshPro counts them: spaces count, tags do not. CharacterCount and RevealedCount report where a text stands.
You can call any of these straight after setting the text, even on an object that is still inactive or a UI text that has no Canvas yet. Calls made before the text can be laid out are kept, in order, and run as soon as it is, so line.Text = next; line.RevealTo(5); works wherever the text is.
Pauses, Speed and Waiting for the Player
Three tags steer the typing from inside the text. They are removed from what is shown, like every other tag:
<waitfor=0.5>pauses for that many seconds.<speed=2>multiplies the typing speed from there on, until the next speed tag.<waitinput>holds the typing until your code callsline.Continue().line.WaitingForInputis true while it holds.
line.Text = "Wait.<waitfor=0.6> Did you hear that?<waitinput> <speed=0.4>It is inside the walls.";
These tags act on typing only. RevealTo and RevealAll pass straight over a wait.
Automatic Pauses
Typing can also pause by itself at punctuation, so a line reads like speech without a <waitfor> after every sentence. The seconds live on a Text Settings asset, the component's Settings field: Punctuation Pause after a sentence's end (. ! ? : ; the ellipsis, and the ideographic, Arabic and Devanagari forms of them) and Comma Pause after a comma (, ، 、 ,). The shipped TextSettings asset, assigned when you add the component in the editor (one a script adds starts with none, so set Settings yourself), has both at zero, so nothing pauses until you set them; every text that references the asset follows it, and a change shows at once, in Play Mode too. Keep a second asset for texts that should pause differently, a cutscene with longer pauses, say, and give it to those components. A pause holds the typing for its seconds from the moment the character shows: a <waitfor> written right after the character adds to it, a skip drops it, a reveal your script makes takes none, and Reveal Speed at zero holds it while a higher speed leaves it as it is. A run of punctuation such as ... or ?! pauses once, after its last character, and a closing quote or bracket right after it, as in "Go.", pauses after the quote. A period or comma between two digits, 3.5 or 1,000, does not pause; Mr. does. The last character of the text, or of a page, never waits, so On Text Shown is raised the moment it shows, and nothing pauses before a <waitinput>, since the hold is the pause.
Events
The component has five events, each of which you can wire in the Inspector or subscribe to with AddListener:
- On Character Visible is raised with a character's index as typing or
RevealToshows it. Play your letter blip here. It is raised once for each frame of typing and once for eachRevealTo, with the last character shown, however many that was, and never for a space, so a fast line does not stack sounds. - On Text Shown is raised when the last character has been revealed. A text that was never hidden, such as one set with Reveal on Shown, does not raise it.
- On Text Hidden is raised when
Hidehas finished, if any character was showing. - On Page Shown is raised with a page's number, counted from zero, when the last character of a page that is not the text's last has been revealed. Only a TextMeshPro text with Page overflow has pages (section 11).
- On Event is raised by an event tag.
<?door>raises it with the name door as typing passes that point in the text;<?sound=creak,2>also hands you creak,2.
RevealAll fires the event tags it jumps past, in text order, and then On Text Shown, so a skipped line leaves your game where a typed one would. It never raises On Character Visible, so a skip plays no letter blip, but an event tag that plays a sound does fire, all at once if the skip passes several. RevealTo fires every event tag it passes too. Hide takes a text back to its start, so a text you hide and then show again, with RevealAll or any other reveal, fires its event tags again.
Appearances and Disappearances
Write a tag in braces and its effect plays once, when the character is revealed, instead of from the moment the text is set: {fade}like this{/fade}. With typing, each letter fades in as it is typed. Without it, the appearance ripples across the text as it is shown.
To give every character an appearance without tagging anything, type the effect into the component's Default Appearance field, with modifiers if you like: fade, or offset a=-0.3 d=0.2. A character under its own {...} tag keeps that one instead. The field can also name a style (section 04), which then applies to the whole text as if the text were wrapped in it: a style whose Opening is {fade}<?blip> fades every line in and raises an event as each one starts, with no tags written in the lines at all.
A brace tag that starts with # is a disappearance: {#fadeout}these letters{/#fadeout} fade out when you call Hide(), and the text is hidden when the last character has finished. A disappearance plays its effect forward, as authored, so an appearance does not double as one: use an effect made to take a character away, the shipped fadeout, shrink and fall, or your own. It must end, so a looping effect never finishes hiding, and it waits while the text's Handle is paused. Calling Hide() again while it plays hides the text at once, so a second press of a button skips the fade, and any reveal call brings the text back. A hide that starts while an appearance is still playing blends into it: a disappearance of the same kind carries on from where the appearance was, and one of another kind lets the appearance finish while it plays, so nothing snaps. Disabling the text while it disappears finishes the hide there and then, raising On Text Hidden. {/} closes every open appearance and {/#} every open disappearance.
RevealAll shows the line at once and cuts the appearances short, which is what a skip should do. Untick Skip Appearances to have it play them instead.
An effect used as an appearance must have its dropdown under Stagger set to Initialize (section 08), so a character sits at the start of the effect from the moment it is revealed. Without it the character shows normally for a frame first, and the Console warns you. fade, size, offset and turn are set up this way.
Example: A Dialogue Box
- On the JuiceBox Text, set Reveal to On Text and Default Appearance to
fade d=0.25. - Give your Continue button this method. The first press finishes the line; the next one moves on.
public void Continue()
{
if (line.WaitingForInput) line.Continue();
else if (line.IsTyping) line.RevealAll();
else line.Text = NextLine();
}
The skip shows the rest of the line at once, its appearances cut short and its event tags fired. The demo scene, Text Demo in TextAnimation/Demo/, is this dialogue box, with one change: instead of setting the next line straight away it calls Hide(), so the line fades out through a {#fadeout} it puts in front of every line, and sets the next one from On Text Hidden.
Three things to know. To pause the typing yourself, set RevealSpeed = 0 rather than disabling the component, which stops it hiding anything. An underline, strikethrough or highlight is revealed with the characters it runs under: it stops at the last revealed character of its line. And an animated sprite stays hidden until its turn, even while TextMeshPro's sprite animator steps through its frames.
08 Tuning an Effect
Every effect is an ordinary sequence, so you tune it in the Sequence Editor like any other.
- Duplicate TextEffects (Ctrl+D / Cmd+D) and assign the copy to your JuiceBox Text. Tuning a copy keeps your changes safe from updates of the add-on.
- Select the copy and click Open in Sequence Editor. The editor enters the profile's preview stage, holding a sample text.
- Find the strip for the effect you want, wave say, and press its play button. The effect plays across the sample text exactly as it will on yours, ripple included.
- Change it and play it again.
What to Change
- The named ValueNodes. Most effects expose their main settings as ValueNodes named
amp(how far: ems, degrees or scale; on shake and wiggle, a multiplier of the authored size),speed(how fast, in cycles per second) andduration(how long, in seconds). The looping effects and shakes default todurationinfinite, which means forever: give it a number to make the effect end on its own. It finishes the cycle it is in, or settles, rather than stopping dead. These are what a tag'sa=,s=andd=write (section 02). - Stagger, in the small box under the strip's left panel: the seconds between one character starting and the next. Larger makes a slower ripple;
0moves every character together; a negative value runs the ripple from the last character to the first. - The dropdown beside Stagger decides what a character does while it waits for its turn:
- Options (the default): nothing. It shows as it normally would until its turn comes.
- Prewarm: there is no wait. Every character starts at once, already as far along as the stagger would have put it, so a wave is fully formed from the first frame instead of rolling in across the text. wave and bounce ship with Prewarm.
- Initialize: every character jumps to where the effect starts the moment the text appears, and holds there until its turn. fade ships with Initialize, which is why the whole span vanishes first and then fades in letter by letter; without it, each letter would show normally, then blink out as its turn arrived.
- The effect itself. Easing, wave shape, the Shake popup's settings, and everything else in the Pro guide apply as usual.
The sample text is the profile's Preview Prefab, TextAnimation/Preview/JuiceBoxTextPreview. Point your copy's Preview Prefab at your own prefab to preview on your own font and size: give it a TextMeshPro text and a JuiceBox Text Preview component.
09 Authoring Your Own Effect
A new effect is a new sequence in the profile. Its name is its tag, and its parameters are its modifiers: a tag writes any number parameter the sequence declares, by name.
Example: A Heartbeat Pulse
- Open your copy of the profile in the Sequence Editor and add a Float sequence. Name it pulse.
- Set its trigger to Manual. JuiceBox Text starts it on each character; it should not start by itself.
- Click the sequence's OnUpdate slot. In the DelegatePicker, switch the toggle from Direct Output to Channel Output, then pick JuiceBoxTextPreview > Scale. The member you pick decides what the effect drives on each character (section 10); Channel means that when the sequence is played on a text, each character supplies its own output.
- Click the Initial Value slot, switch it to Channel Input, and pick JuiceBoxTextPreview > Scale again. Each character now starts from its own scale, 1, instead of 0.
- Add a Tween to
1.3over0.15seconds, easingPow2Out, then a Tween back to1over0.25seconds. - Set the sequence's loop to Loop, and its Stagger to
0.04. - Press the strip's play button to watch it in the preview stage, then write
<pulse>LOW HEALTH</pulse>in your text.
Where an Effect Starts
An effect starts from its Initial Value, like any sequence, and an empty Initial Value means zero. For an offset or a rotation, zero is exactly right: the character's own place and angle. For a scale, a colour or an opacity, zero is a real value (invisible, black, see-through), which is useful when you mean it: an Initial Value of 0 on an opacity effect is how fade starts every character hidden.
When an effect should start from each character's own value (its natural size, or the colour you gave it in TextMeshPro), set the Initial Value to Channel Input with the same member as the output, as in the example. That is how incr and rainb are built.
Making It End, or Not
- For a one-shot effect such as fade, leave the loop off. Each character plays once and holds its last value.
- For a repeating effect that should run forever unless told otherwise, use Loop For Time, Yoyo For Time or Repeat For Time, wire a ValueNode named
durationto the loop node's limit, and leave it infinite. Setting it later then ends the effect cleanly at the end of a cycle. - Use Repeat rather than Loop for an effect whose tween does not come back to where it began. rot turns 0 to 360 degrees: on Loop the second pass would start at 360 and go nowhere, while Repeat starts every pass from the beginning.
A sequence whose OnUpdate is not a Channel output bound to one of the JuiceBox Text Preview members cannot drive a character. Its tag is still removed from the text, and the Console says why, once per sequence.
10 What an Effect Can Drive
The JuiceBox Text Preview component has a member named after most shipped effects, plus a plain member for each kind; size, offset and turn use the plain Scale, OffsetY and Rotation. They exist only to be picked in the graph: what matters is the kind. Bind a new effect to the plain member of the kind you want; its sequence type must match the member's type.
| Kind | Type | Members | Units |
|---|---|---|---|
| Offset | Vector3 | Offset, Shake, Wiggle | ems |
| Horizontal offset | Float | OffsetX, Slideh | ems |
| Vertical offset | Float | OffsetY, Wave, Bounce | ems |
| Rotation | Float | Rotation, Rot, Swing | degrees, about the character's centre |
| Hanging rotation | Float | Hanging, Pend, Dangle | degrees, about the character's top edge |
| Scale | Float | Scale, Incr | 1 is the character's own size |
| Colour | Vector3 | Color, Rainb | RGB, 0 to 1 each |
| Opacity | Float | Alpha, Fade | 0 invisible to 1 opaque |
JuiceBox Text needs none of this at runtime: it reads the kind from the member's name and writes straight into the text's mesh. The preview component is what lets the graph show your effect while you author it.
11 UI Toolkit, Other Scripts and Long Text
UI Toolkit
On Unity 6000.3 and later, JuiceBox Text also drives a UI Toolkit label. Add JuiceBox Text (UI Toolkit) to the GameObject that holds the UIDocument, set its Profile, and type the label's name into Element: Title is the first element named Title in the document and 1:Title the second, as a Pro binding names an element. Then write the tagged text into Text, or set the label's text from code. Everything in this guide applies: tags, braces, typing, events, RevealTo and the rest are one component underneath, JuiceBoxTextDriver, which a script can hold without caring which renderer is behind it. A document with several animated labels takes one component per label, all on the document's GameObject.
What differs from TextMeshPro:
- An underline or strikethrough is not revealed with its characters: UI Toolkit draws it whole from the first frame, under letters still waiting to be typed, and it stays when the text is hidden. A
<mark>highlight shows and hides with the first letter it covers. - A hidden character is collapsed to a point, as on TextMeshPro, so its text shadow and outline go with it. A fade is different: it fades the letter's face, while the shadow and outline, which come from the style, stay as they are.
- Offsets in ems use the label's font size, so a
<size>tag changes how big a letter is drawn but not how far it moves. - With the advanced text generator (UI Toolkit's default from Unity 6000.5; on 6000.3 and 6000.4 it needs Project Settings > UI Toolkit > Enable Advanced Text), UI Toolkit shapes and reorders the glyphs, and the effects still follow the characters of the string, with one exception: each
<mark>highlight costs its line one letter, the rightmost on that line, which is then neither animated nor typed out. Where the font draws several characters as one glyph (the Arabic lam-alef, an Indic conjunct, an emoji with a skin tone), that glyph wears the effects of the first of them and is typed out with it; a vowel sign drawn in two parts (Thai sara am, the two-part vowels of Bengali, Tamil and other Indic scripts) moves and appears as one. A label laid out right to left (itslanguageDirection, or an ancestor's) is read the same way. On 6000.3 and 6000.4, a label set to the advanced generator while that project setting is off hands the driver no glyphs, so nothing on it animates or types, and the driver says so once in the Console. - A disabled JuiceBox Text (UI Toolkit) hides nothing: the label shows in full until it is enabled again.
- The
<cr>tag, which returns to the start of the line and writes over it, is not supported.
JuiceBox Text finds a tagged character by where it sits in the string, and steps a ripple or a typewriter through the characters in string order. Most scripts need nothing more than that; the rest of this section covers the ones that do.
Right-to-Left Text
Tick TextMeshPro's Right To Left and type the text in reading order. TextMeshPro keeps the characters in string order and lays them out leftward, so a ripple and the typing both start at the right edge, as a reader does. TextMeshPro has no bidirectional layout of its own: a number or a Latin word inside a Hebrew line has to be typed reversed for it to read correctly, and a ripple then crosses it right to left with the rest of the line.
Arabic letters change shape to join their neighbours, which TextMeshPro does not do. Use strings that are already shaped, or a shaping plugin such as RTL TextMeshPro. With that plugin, add JuiceBox Text to the RTL TextMeshPro object, clear JuiceBox Text's Text box, and set the text on the RTL component from then on, in the Inspector or from code: the plugin shapes the tagged text and JuiceBox Text takes it from there. A text left in JuiceBox Text's own box would be shown unshaped instead. The plugin protects only angle-bracket tags and reverses everything else, so write brace tags in their angle-bracket form:
rtlText.text = "<{fade}>مرحبا<{/fade}> <wave>بك</wave>";
A TextMeshPro text preprocessor that changes the length of the text moves effects onto the wrong characters, because the positions it hands back are no longer those of the string JuiceBox Text tagged.
Chinese, Japanese and Korean
Chinese and Japanese put no spaces between words, so by=word steps through each Chinese character and kana as a word of its own; a Latin word inside the line is still one word. Korean, which does use spaces, splits on them like any other script.
Accents, Marks and Emoji
A combining mark, such as an accent typed as a separate character, an Arabic vowel mark, a Hebrew point or a Thai tone mark, moves, changes colour and is revealed with the letter it sits on. It takes no step of a ripple of its own, and a reveal that would show the letter without its mark shows both. It still counts as a character in CharacterCount.
An emoji, or a TextMeshPro <sprite>, is one character: one step of a ripple and one tick of the typing.
Long Text
- Ellipsis. When the text's overflow is set to Ellipsis, the "…" stands for the characters it cut: an effect over the cut plays on it, and the typing ends with it.
- Cut characters. Characters that Ellipsis or Truncate removes are not animated or typed, but pause and event tags among them still run when the typing reaches the end.
- Page. With Page overflow the typing follows the page on screen. It stops at the end of the page, raises On Page Shown, and holds there as a
<waitinput>does:WaitingForInputis true, andContinue()turns the page and types the next one; a<waitinput>right at the page break is answered by the same press. The dialogue box in section 07 works as it is: its skip,RevealAll, finishes the page on screen, firing the event tags up to the page's end, and holds there, so the next press turns the page.RevealTo(int.MaxValue)would count across pages and end the line on a page that shows only part of it. On the last pageRevealAllis the usual skip, and On Text Shown is raised once the last page is shown. Turning the page yourself throughpageToDisplayworks too: while the text types, a page not yet typed starts typing from its first character, skipping the event tags and pauses of any page passed over (their speed tags still count, and their appearances do not play), and a page already typed shows in full. A reveal your own script drives keeps the count it sets. A new text, andStartReveal, go back to the first page, andHideon a later page disappears the page on screen. - Linked. A linked chain is one text. Put the JuiceBox Text on the first TextMeshPro only: the characters that overflow into the linked texts animate, type and hide with the rest, the typing runs from one text into the next without stopping, and On Text Shown is raised when the last linked text has shown its last character. A linked text needs no JuiceBox Text of its own (the Console warns if it has one, since two would fight over its text). Give every text in the chain the same Rich Text setting, as TextMeshPro numbers their characters together; their fonts and sizes may differ. The chain ends at the first linked text that is inactive, and the last text's own overflow cuts what is left, as Truncate or Ellipsis would; Page overflow on a linked text is not supported.
12 Troubleshooting
- The tag shows up on screen, modifiers and all. Something after the name is not a modifier: check for spaces around an
=, or a word with no=(section 02). - A modifier does nothing. Look in the Console for a JuiceBox Text warning naming it. Each effect reports its first problem only, so fix that one and look again.
- The tag shows up on screen. No sequence in the assigned profile has that name. Check the spelling against the strip names, and check that the Profile field is set.
- The tag disappears but nothing moves. Look in the Console for a JuiceBox Text warning: the sequence has no Channel output, is bound to a member that is not a text kind, or its type does not match that kind (section 10).
- Nothing moves in the editor. Effects on a JuiceBox Text play in Play mode. Use the profile's preview stage to watch them while authoring.
- Every character moves at once. The sequence's Stagger is
0. Give it a small value such as0.05. - An effect starts from nothing: invisible, black, or zero size. Its Initial Value is empty, which means zero. Set it to Channel Input (section 09) or to the value you want.
- Letters show, then blink out just before they fade in. The effect's dropdown under Stagger is on Options, so a waiting letter shows normally until its turn. Set it to Initialize (section 08).
- A character shows for a frame before its appearance starts. The effect is not set to Initialize (section 08).
- The text never types. Reveal is on Shown or Script, or Characters Per Second or
RevealSpeedis zero, or the game is paused withTime.timeScaleat zero and Unscaled Time is not ticked. - On Text Hidden never comes. A disappearance tag names an effect that never ends, a loop or an infinite
duration, or the text'sHandleis paused. A disappearance has to finish before the text is hidden (section 07). - A brace tag shows up reversed,
}edaf{, on a right-to-left text. A shaping plugin reversed it. Write it as<{fade}>(section 11). - A UI Toolkit label does nothing. Look in the Console for a JuiceBox Text warning: the Element name matches no element in the
UIDocumenton the same GameObject, or names one that is not a text element. The component has to sit on the document's GameObject (section 11). - Typing stops partway. The text has a
<waitinput>and nothing callsContinue(). - A finished fade does not replay. Setting the same text again does nothing on purpose, and
Handle.Restart()only replays effects that are still running. Clear the text and set it again (section 06).
