Search Unity

  1. Unity 6 Preview is now available. To find out what's new, have a look at our Unity 6 Preview blog post.
    Dismiss Notice
  2. Unity is excited to announce that we will be collaborating with TheXPlace for a summer game jam from June 13 - June 19. Learn more.
    Dismiss Notice

TextMeshPro characterInfo.vertexIndex problem/bug?

Discussion in 'Scripting' started by asnowcappedromance, Jul 4, 2021.

  1. asnowcappedromance


    May 1, 2021
    Hi everybody,

    I've been running into a problem with TextMeshPro that I cannot solve and would love some insight on why this is happening / how to fix it.
    I'm aiming to start a Coroutine in order to reveal letters of a given word, while having each letter's vertex position animated eventually. In order to reveal each letter, I'm incrementing the "maxVisibleCharacters" property of the TMP component, which works fine. However, when I'm then trying to access the vertexIndex of the current letter, a lot of times characterInfo.vertexInfo returns 0, instead of the expected value).
    (In my opinion a word with 4 letters i.e. 'word' should have the vertex indeces 0, 4, 8, 12)

    The IEnumerator RevealNewCharacter() method is being called from the Update() method in one of my game objects, please have a look at my code:
    Code (CSharp):
    1. IEnumerator RevealNewCharacter()
    2.     {
    3.         // This adds a new letter to an existing string, iterates over the vertices and aninmates their positions
    4.         textMeshTranslation = textTranslationTmp3D.GetComponent<TMP_Text>();
    5.         charactersVisibleTranslation++;
    6.         textMeshTranslation.maxVisibleCharacters = charactersVisibleTranslation;
    7.         Debug.Log("AddCharacterFlyIn() - maxVisibleCharacters: " + textMeshTranslation.maxVisibleCharacters.ToString());
    9.         textMeshTranslation.ForceMeshUpdate();
    10.         meshTranslation = textMeshTranslation.mesh;
    11.         verticesTranslation = meshTranslation.vertices;
    12.         tinfoTranslation = textMeshTranslation.textInfo;
    14.         // now store vertex positions for current character to reveal
    15.         Debug.Log("AddCharacterFlyIn() - character count: " + tinfoTranslation.characterCount);
    16.         TMP_CharacterInfo cInfoNext = tinfoTranslation.characterInfo[charactersVisibleTranslation - 1];
    18.         // skipping over " " space characters
    19.         string nextChar = cInfoNext.character.ToString();
    20.         if (nextChar == " ")
    21.         {
    22.             animOnTranslation = false;
    23.             yield break;
    24.         }
    26.         // each vertex square has four vertices in TMP, the vertexIndex is the first vertex of current letter
    27.         int charIdxNext = cInfoNext.vertexIndex;
    29.         // this is a test to debug vertex info problem
    30.         Debug.Log("AddCharacterFlyIn() - Next char: " + cInfoNext.character.ToString() + " vertex Index: " + charIdxNext.ToString());
    31.         for (int i = 0; i < tinfoTranslation.characterCount; i++)
    32.         {
    33.             TMP_CharacterInfo cInfo = tinfoTranslation.characterInfo[i];
    34.             Debug.Log(">>> cur char: " + cInfo.character.ToString() + " vertex index: " + cInfo.vertexIndex);
    35.         }
    37.         // now add to lettersValidated list
    38.         listLettersValidated.Add(nextChar);
    39.         //PrintLetterList("listLettersValidated", listLettersValidated);
    41.         // listLettersRemaining is being set initially by SetToTranslate() method
    42.         // since we're validating the letter and assume it's sitting at position 0 of lettersRemaining list, we can remove it from the latter
    43.         if (listLettersRemaining.Count > 0)
    44.         {
    45.             int idxRemaining = listLettersRemaining.IndexOf(nextChar);
    46.             listLettersRemaining.RemoveAt(idxRemaining);
    47.         }
    48.         // we validated the letter, so we can remove it out of the scene
    49.         if (listLettersInScene.Count > 0)
    50.         {
    51.             int idxInScene = listLettersInScene.IndexOf(nextChar);
    52.             listLettersInScene.RemoveAt(idxInScene);
    53.         }
    55.         //PrintLetterList("listLettersRemaining", listLettersRemaining);
    56.         //PrintLetterList("listLettersInScene", listLettersInScene);
    58.         // Display last character of word a little longer before switching to new word
    59.         if (textMeshTranslation.text.Length == textMeshTranslation.maxVisibleCharacters)
    60.         {
    61.             //Debug.Log("Word is complete");
    62.             yield return new WaitForSeconds(0.5f);
    63.             MarkTranslationComplete();
    64.         }
    66.         // skipping the animation for debugging
    67.         animOnTranslation = false;
    68.         yield return null;
    69.     }
    While I cannot post my entire project, because it's too complex in size and confidential, I would appreciate some pointers as to why this could be happening.
    Here's a screenshot of the console, that logs my printing statements:


    Thanks a lot for your help!

  2. Kurt-Dekker


    Mar 16, 2013
    Is this consistent with how TMPro makes characters? You can look for yourself because all the TMPro source is right there in the package.
  3. asnowcappedromance


    May 1, 2021
    Hi Kurt,

    Thanks for your reply. I haven't looked into any source code, but all basic tests that I run confirm my theory.
    If you put this simple code on a TextMeshPro 3D object, you'll get the following prints in the console:

    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4. using TMPro;
    6. public class testScriptTmp : MonoBehaviour
    7. {
    8.     TMP_Text tmp;
    9.     TMP_TextInfo tmpInfo;
    11.     // Start is called before the first frame update
    12.     void Start()
    13.     {
    14.         tmp = gameObject.GetComponent<TMP_Text>();
    15.         tmp.text = "Example";
    16.         tmpInfo = tmp.textInfo;
    17.         tmp.ForceMeshUpdate();
    19.         Debug.Log("Current Word: " + tmp.text);
    20.         for (int i = 0; i < tmpInfo.characterCount; i++)
    21.         {
    22.             TMP_CharacterInfo cInfo = tmpInfo.characterInfo[i];
    23.             Debug.Log(">>> cur char: " + cInfo.character.ToString() + " vertex index: " + cInfo.vertexIndex);
    24.         }
    25.     }
    26. }

    Here the vertex indeces are behaving as expected.
    In my much more complex code, I'm switching out the words on certain conditions, and resetting the "maxVisibleCharacters" property back to 0, then using the "ForceMeshUpdate()" to update the TMP component, so potentially that would mess things up? Would really love to understand how to fix this!
  4. Kurt-Dekker


    Mar 16, 2013
    You're seeing something get messed up, so I am gonna answer "Sure!"

    TMPro is a black box that you luckily have the source code to. Go inspect it, see if it does dynamically rearrange stuff.

    At a bare minimum, make something visual onscreen that shows you onscreen where each vertex is as you change letters in the string, like visualize the vertices in their place in space, and what index each one is. I think you can use Handles.Label() to cheese those numbers onscreen and test your theories.
  5. asnowcappedromance


    May 1, 2021
    I have to say that I feel a little overwhelmed when looking into the source code.
    While I managed to find the TMP_CharacterInfo script, it doesn't show me how those properties such as "vertexIndex" are being set.

    In this thread here
    Stephan_B states that a character's vertexIndex is 0 if it is not visible (isVisible = false), however that theory doesn't hold up in my tests:

    I also experimented with add those 2 lines before I iterate over the vertices, that didn't help much either:

    Code (CSharp):
    1.         textMeshTranslation.ForceMeshUpdate();
    2.         textMeshTranslation.UpdateVertexData();
  6. asnowcappedromance


    May 1, 2021
    Quick update, I'm not one to give up easily, but soon I'll be at my wits end :)
    If I completely disable playing with the "maxVisibleCharacters" property, the problem remains. In which case simply updating the TMP_Text.text property would mean that the vertex information becomes incorrect.
    I assume I must be missing an important step to let TMP know that something has changed.
  7. asnowcappedromance


    May 1, 2021
    Ok, so I do believe that I've found a bug.
    If you look closely at the previous logs that I posted, you can see that the *vertexIndex* is always 0 for special characters, such as ö, ü, ß, ä, etc.
    If I iterate over a word with standard letters, everything works as expected:

    Can anybody confirm that for me?
    Since I've never submitted a bug report to Unity, what's the standard procedure for this?
  8. flewd


    Jan 10, 2019
    I have also noticed this issue on the newest version of TextMeshPro 3.0.6 and Unity 2020.3.7f1

    Certain characters will cause the VertexIndex within TMP_CharacterInfo to return 0 and sometimes even reset the vertex count from that point on. For my use case I'm using this VertexIndex for text animations.

    This is happening when all the characters are visible.
  9. Bunny83


    Oct 18, 2010
    I haven't really used TextMeshPro that much so I never ran into that issue. However it may be related to any unicode characters that require more than a single byte in UTF8 encoding. So everything besides ASCII characters may be affected. This is in general an issue because if you have multiple umlaut or in general non-ASCII characters in a string, there is no one-to-one relationship between "char" index in a string and the actual "character".

    So it's possible that TMP may not decode the string properly if it contains unicode characters (anything outside the ASCII range). So yes, I would call it a bug. Maybe someone can create an example project that verifies this and file a bug report. Currently I don't really use Unity :)