JuiceBox / Text Animation Pro guide → / Asset Store

Text Animation

Drop a tag into your string and the characters come alive. Every effect is a JuiceBox sequence under the hood, so you can retime and restyle it like anything else you animate.
Add-on · Requires JuiceBox - PRO

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

  1. Create a TextMeshPro text: GameObject > UI > Text - TextMeshPro for UI, or GameObject > 3D Object > Text - TextMeshPro for a world-space label. Both work.
  2. Select it and choose Add Component > JuiceBox > JuiceBox Text. The component takes over whatever text the TextMeshPro already shows.
  3. Drag TextAnimation/Profiles/TextEffects into the component's Profile field. That profile holds every shipped effect.
  4. In the component's Text box, type Welcome to the <wave>JuiceBox</wave> arcade!
  5. 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.

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.
ModifierWhat 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=lineWhat 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.

TagWhat it doesModifiers
<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:

DrivesStacks by
Position (any offset)Adding the offsets: a wave on top of a shake bobs while it jitters.
Rotation, hanging rotationAdding the angles. A rotation and a hanging rotation both apply.
Scale, opacity, colourAdding 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:

FieldExampleWhat it is
NamespecialThe 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.

MemberPurpose
TextThe tagged text. Assigning a different string applies it.
SetText(string)The same as assigning Text.
ProfileThe Animation Profile whose sequences the tags name.
HandleThe 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:

RevealWhat happens
ShownThe default. A text shows in full as soon as it is set.
On TextEach new text starts hidden and types itself out.
On EnableAs On Text, and the text types out again every time the component is enabled.
ScriptEach 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:

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:

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

  1. On the JuiceBox Text, set Reveal to On Text and Default Appearance to fade d=0.25.
  2. 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.

  1. 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.
  2. Select the copy and click Open in Sequence Editor. The editor enters the profile's preview stage, holding a sample text.
  3. 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.
  4. Change it and play it again.

What to Change

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

  1. Open your copy of the profile in the Sequence Editor and add a Float sequence. Name it pulse.
  2. Set its trigger to Manual. JuiceBox Text starts it on each character; it should not start by itself.
  3. 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.
  4. 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.
  5. Add a Tween to 1.3 over 0.15 seconds, easing Pow2Out, then a Tween back to 1 over 0.25 seconds.
  6. Set the sequence's loop to Loop, and its Stagger to 0.04.
  7. 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

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.

KindTypeMembersUnits
OffsetVector3Offset, Shake, Wiggleems
Horizontal offsetFloatOffsetX, Slidehems
Vertical offsetFloatOffsetY, Wave, Bounceems
RotationFloatRotation, Rot, Swingdegrees, about the character's centre
Hanging rotationFloatHanging, Pend, Dangledegrees, about the character's top edge
ScaleFloatScale, Incr1 is the character's own size
ColourVector3Color, RainbRGB, 0 to 1 each
OpacityFloatAlpha, Fade0 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:

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

12 Troubleshooting