How to add sprites and prefabs in text in Unity (Text Mesh Pro)

While working on the hints feature in the Minesweeper game in Puzzle Hub, I felt that it would be really useful if the highlighted squares (in yellow and blue color) on the grid also appear in the hint message. This makes it easier to explain the hint with fewer words. You have probably seen something similar in many other games, for example when there is a coin next to a price in some text.

For a lot of use cases, you only need to add a sprite to the text, similar to how emojis work. However, in this specific use case, the sprite had to be a prefab. The first reason is that the highlighted squares can be in 5 different states. The second reason is that we support multiple color themes for the whole app. So I didn’t want to generate 5 static sprites for each supported color theme. Another reason why you might want to have a prefab instead of a sprite is if you want to have some kind of animation.

So, if you want to add a prefab that can be animated or scripted to a text, continue reading this post. If you want just a static sprite, try out this.

Tested on:
Unity 2020.3
– Text Mesh Pro 3.0.6

By the end of this tutorial, you will create a text with an animated coins icon like in this gif:

The final result

Step 1: Create a text and add a wildcard where you want the icon to appear

Start by creating a text element using Text Mesh Pro. Place a custom wildcard where you want the icon to appear. A wildcard is the part of the text where the prefab will be placed. In this example, I chose {c} as a wildcard for the coins icon. In the next steps, we will make this wildcard invisible and put the prefab in it’s place.

Step 2: Create a method

Let’s create a method in our code that will be responsible for replacing a wildcard with a specified prefab. I called the method ReplaceWildcardWithPrefab and it takes 3 arguments: a reference to the TextMeshProUGUI element, a string of the chosen wildcard, and a reference to the prefab we want to show.

public class PrefabInText : MonoBehaviour
{
    public TextMeshProUGUI Text;
    public GameObject CoinsPrefab;

    void Start()
    {
        ReplaceWildcardWithPrefab(Text, "{c}", CoinsPrefab);
    }

    public void ReplaceWildcardWithPrefab(TextMeshProUGUI text, string wildcard, GameObject prefab)
    {
        // The rest of the code will go here
    }
}

Step 3: Make the wildcard invisible using code

The first thing we do is to make the wildcard invisible by wrapping it in <color> tags. TMPro allows us to use tags to set the color of some substring of characters. To make a text invisible, we can use the color #fff0 (white with 0 alpha/opacity).

We can do that by adding the following code in our method:

var initialString = text.text;

// hide the wildcard by making it transparent
var hiddenWildcardString = initialString.Replace(wildcard, $"<color=#fff0>{wildcard}</color>");
Text.text = hiddenWildcardString;

Step 4: Find the center position of the wildcard

Before we instantiate the prefab, we need to calculate the position where it will go, which is the center of the wildcard.

The TextMeshProUGUI class has a textInfo property which contains the bottomLeft, bottomRight, topLeft and topRight positions of each character, among other useful things. To find the center of the wildcard, we get the bottomLeft position of the first character of the wildcard, and the topRight position of the last character of the wildcard and calculate the midpoint between them.

In code:

// get the index of the wildcard in the string
var wildcardStartIndex = initialString.IndexOf(wildcard);
var wildcardEndIndex = wildcardStartIndex + wildcard.Length - 1;

// get the positions of the first and last characters of the wildcard
var bottomLeftPos = text.textInfo.characterInfo[wildcardStartIndex].bottomLeft;
var topRightPos = text.textInfo.characterInfo[wildcardEndIndex].topRight;
var center = (bottomLeftPos + topRightPos) / 2f;

Step 5: Instantiate the prefab

Now that we have everything we need, we just instantiate the prefab:

// instantiate prefab
var prefabInstance = Instantiate(CoinsPrefab, text.transform);
// set the local position
prefabInstance.transform.localPosition = center;

Note that the calculated position is in the local space of the Text, that’s why the prefab is instantiated as a parent of the text and the local position is set.

The result

Step 6: Update the method to support multiple wildcards

At the moment, we instantiate only one wildcard. Without getting into details, here is how the method is modified to support prefabs of a same wildcard:

public void ReplaceWildcardWithPrefab(TextMeshProUGUI text, string wildcard, GameObject prefab)
{
    var initialString = text.text;

    // hide the wildcard by making it transparent
    var hiddenWildcardString = initialString.Replace(wildcard, $"<color=#fff0>{wildcard}</color>");
    Text.text = hiddenWildcardString;

    var lastIndex = 0;
    do
    {
        // get the index of the wildcard in the string
        var wildcardStartIndex = initialString.IndexOf(wildcard, lastIndex);
        if (wildcardStartIndex <= 0)
        {
            var wildcardEndIndex = wildcardStartIndex + wildcard.Length - 1;

            lastIndex = wildcardEndIndex;

            // get the positions of the first and last characters of the wildcard
            var bottomLeftPos = text.textInfo.characterInfo[wildcardStartIndex].bottomLeft;
            var topRightPos = text.textInfo.characterInfo[wildcardEndIndex].topRight;
            var center = (bottomLeftPos + topRightPos) / 2f;

            // instantiate prefab
            var prefabInstance = Instantiate(CoinsPrefab, text.transform);
            prefabInstance.transform.localPosition = center;
       }
       else
       {
           lastIndex = -1;
        }
   } while (lastIndex != -1);
}
Final result

I hope you will find this tutorial useful. If you have any questions or tutorial ideas related to Puzzle Hub, feel free to send a mail at contact@puzzlehub.app.