0
\$\begingroup\$

Using the built-in text UI, is there a way to animate only a selected number of letters?

What I want to do is something like the word "you" in this video and "I'd" in this one

\$\endgroup\$
3
  • \$\begingroup\$ The text animations in those two games look suspiciously similar. Makes me wonder if they used the same 3rd party asset for that. \$\endgroup\$
    – Philipp
    Commented Mar 2, 2022 at 14:32
  • \$\begingroup\$ A sketch of how I'd approach it (will elaborate in an answer if I have time to test this): 1) Copy the source of the TextMeshPro standard text shader into a new shader. 2) Add into the vertex shader a sinusoidal offset over time, scaled by the alpha channel of the vertex colour. 3) Wrap the word I want to animate in rich text formatting tags that give it a different alpha value than the rest of the text. 4) Apply my customized shader to a new material, and use that material on a standard TextMeshPro/UGUI component. \$\endgroup\$
    – DMGregory
    Commented Mar 2, 2022 at 15:24
  • 2
    \$\begingroup\$ ...though you could also achieve this with the built-in shader by using voffset tags that you animate via script, similar to the fading effect I show here. \$\endgroup\$
    – DMGregory
    Commented Mar 2, 2022 at 15:29

1 Answer 1

1
\$\begingroup\$

As per DMGregory's suggestion in the comments, I propose a solution using the <voffset> tag for rich text. It's a little bit elaborate code-wise but doesn't require you to work with shaders at all.

Setup

We begin by creating a Text - TextMeshPro UI element in the Hierarchy. Change the text, font, colour and alignment as you wish. By now, we have a simple, static text:

enter image description here

Testing the <voffset> tag

The <voffset> tag changes the offset of the baseline. We can enclose the word 'world' between tags and add a value in pixels or ems. The enclosed text will be lifted

If we change the text content to Hello, <voffset=10px>world</voffset>! the result is the following:

enter image description here

Changing the offset via Script

First of all, we must reference the TextMeshPro Component, so that we can access its text property. We do that with a class property and a function call at script startup:

TextMeshProUGUI meshText;
meshText = GetComponent<TextMeshProUGUI>();

We can animate the vertical movement via a sine function over time. We want control over the function's frequency and amplitude, thus limiting "how fast" and "how high" the text will float.

We define the following class properties:

public float frequency = 1f;    // frequency factor
public float amplitude = 10f;   // pixel factor

Now we can compute an offset value over time with parameters:

float offset = Mathf.Sin(Time.time * frequency) * amplitude;

We will feed this value to the text in each frame to update the animation.

To update the text and only change the <voffset> value, we can search for a substring of type <voffset=*px> where * is a number, but this would require using regular expressions, which is likely overkill.

A simpler approach exists. Substring replacement is easy if we know the substring to replace in advance. Then, we can do the following:

  1. Save the original string populating the text property.
string sourceString;
sourceString = meshText.text;
  1. Copy the above string with an updated offset value.
string newString = sourceString.Replace(
    "<voffset=0px>", "<voffset=" + offset.ToString() + "px>");
  1. Update the text field with such string.
meshText.text = newString;

Assembling the Script

Let's put everything together and in place. Our class should look like this:

using UnityEngine;
using TMPro;

public class Baseline : MonoBehaviour {
    public float frequency = 5f;
    public float amplitude = 10f;
    string sourceString;
    TextMeshProUGUI meshText;

    void Start() {
        meshText = GetComponent<TextMeshProUGUI>();
        sourceString = meshText.text;
    }

    void Update() {
        float offset = Mathf.Sin(Time.time * frequency) * amplitude;
        string newString = sourceString.Replace("<voffset=0px>", "<voffset=" + offset.ToString() + "px>");
        meshText.text = newString;
    }
}

Testing

Now we can assign this script to the GameObject with a Text Mesh Pro - Text (UI) Component, tweak the parameters and test it:

enter image description here

This problem occurs because <voffset> adjusts the line height to accommodate the displaced text. We can fix that by selecting the Capline Alignment option from the Inspector:

enter image description here

And the result is what we expect:

enter image description here

Unfortunately, this simple solution isn't enough for multi-line text with animation:

enter image description here

In such a case, we simply need to force the overall text line-height to be constant for the whole text by adding the <line-height> tag at the beginning of our string:

<line-height=100%>Hello, <voffset=0px>world</voffset>!
How are you?

Now everything does work!

enter image description here

Extending the script

This is a very simple approach to animating a single word in a text string. You can extend these functionalities to animate individual letters in a word, by enclosing each character between <voffset> tags and adding a phase offset to each letter so that they float at the same speed and height but different timing.

\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .