Search Unity

TextMesh Pro TMP Mesh Vertex Colors not applying proerly. [SOLVED]

Discussion in 'UGUI & TextMesh Pro' started by looytroop, Jan 11, 2021.

  1. looytroop

    looytroop

    Joined:
    Jun 22, 2017
    Posts:
    70
    Hello, I am currently working on a project where we want text to teletype in. To do this we are making use of the mesh generated by TMPro and updating the alpha on the vertex colors.

    This is the text that I am trying to manipulate: <b>Chracter Name: </b> Here is some sample text you're going to read in the game. To hide and teletype this text we are going through two steps, first, set all the alphas to 0, then make a coroutine for each character where each one waits a set amount of time it's supposed to until it should reveal.

    My problems seem to mainly stem from part 1. If all I do is set the alpha to 0 this is the string I can see in game : Chracter Name: [SPACING] ' [SPACING]
    So as you can see, the character name is not hidden, but still comes through bold, and the apostrophe is still visible as well. It is worth noting that no other punctuation is visible.

    Here is the code I am using to hide the characters.

    Code (CSharp):
    1. m_TextComponent.ForceMeshUpdate();
    2.  
    3. TMP_TextInfo textInfo = m_TextComponent.textInfo;
    4.  
    5. int charCount = textInfo.characterCount;
    6. for (int i = 0; i < charCount; ++i)
    7. {
    8.     TMP_CharacterInfo charInfo = textInfo.characterInfo[i];
    9.  
    10.     if (!charInfo.isVisible)
    11.     {
    12.         print(charInfo.character);
    13.         continue;
    14.     }
    15.  
    16.     int index = charInfo.vertexIndex;
    17.  
    18.  
    19.     for (int j = 0; j < 4; ++j)
    20.     {
    21.         m_TextComponent.textInfo.meshInfo[0].colors32[index + j].a = startAlpha;
    22.     }
    23. }
    24.  
    25. textInfo.meshInfo[0].mesh.vertices = textInfo.meshInfo[0].vertices;
    26. m_TextComponent.UpdateVertexData();
    27.  
    As you can see I am also printing all of the characters that are not visible and I receive nothing but ' ' (space characters) from this output.

    It is in step 2, where I reveal the characters one at a time, that I see another oddity that seems related to part 1. I start a coroutine, where I pass the vertex index, and the coroutine will update the alpha value at the given index.This works very well for all but the first few letters, as it seems to do the first 4 letters twice. I believe this is the case because the $CharacterName that I am using is CEO: so the first message looks something like this:
    <b>CEO:</b> Welcome!

    When this runs, I see CEO: in bold from the start, they are never invisible, and then right away W comes in, then e, l, c, It pauses, as it redoes W, e, l, c then proceeds on to the rest of the message.

    Because this is the case, it seems to me that the CEO portion seems to be getting excluded from the mesh vertices or something of the like and I am unable to figure out what it is I am doing wrong or how I would properly handle a case such as this, I also still do not understand why the ' is also visible from the start.
     
  2. looytroop

    looytroop

    Joined:
    Jun 22, 2017
    Posts:
    70
    I solved my issue. As I was debugging my code, I remembered the concept of the material reference index! I assume that in my case, the bold letters were using a different material and we most likely do not have an apostrophe defined in our character set, so I imagine that it was using one from a fall-back font which required a new material. For those wondering, you get the material reference index off of the characterInformation. You can see my revised code below to hide all of the characters proper.

    Code (CSharp):
    1. m_TextComponent.ForceMeshUpdate();
    2.  
    3. TMP_TextInfo textInfo = m_TextComponent.textInfo;
    4.  
    5. int charCount = textInfo.characterCount;
    6. for (int i = 0; i < charCount; ++i)
    7. {
    8.     TMP_CharacterInfo charInfo = textInfo.characterInfo[i];
    9.  
    10.     if (!charInfo.isVisible)
    11.     {
    12.         print(charInfo.character);
    13.         continue;
    14.     }
    15.  
    16.     int index = charInfo.vertexIndex;
    17.  
    18.  
    19.     for (int j = 0; j < 4; ++j)
    20.     {
    21.         m_TextComponent.textInfo.meshInfo[charInfo.materialReferenceIndex].colors32[index + j].a = startAlpha;
    22.     }
    23. }
    24.  
    25. textInfo.meshInfo[0].mesh.vertices = textInfo.meshInfo[0].vertices;
    26. m_TextComponent.UpdateVertexData();
     
    OscarLeif likes this.
  3. brandonholt95

    brandonholt95

    Joined:
    Feb 12, 2020
    Posts:
    14
    I adapted this for use in my TMP_InputField, and it works when I change the color, but as soon as any new characters are added to the input field, the colors revert back to default white. Any ideas why that might be? @Stephan_B maybe you have an idea?

    Code (CSharp):
    1.     private void SetWordColor(TMP_Text tmpText, TMP_WordInfo wordInfo, Color color)
    2.     {
    3.         TMP_TextInfo textInfo = tmpText.textInfo;
    4.  
    5.         for (int i = wordInfo.firstCharacterIndex; i <= wordInfo.lastCharacterIndex; i++)
    6.         {
    7.             TMP_CharacterInfo charInfo = textInfo.characterInfo[i];
    8.  
    9.             for (int j = 0; j < 4; j++)
    10.             {
    11.                 textInfo.meshInfo[charInfo.materialReferenceIndex].colors32[charInfo.vertexIndex + j] = color;
    12.             }
    13.         }
    14.  
    15.         textInfo.meshInfo[0].mesh.vertices = textInfo.meshInfo[0].vertices;
    16.  
    17.         tmpText.UpdateVertexData();
    18.     }
     
  4. Stephan_B

    Stephan_B

    Joined:
    Feb 26, 2017
    Posts:
    6,595
    This would happen anytime the text object is updated as a result of changing any of its properties.

    As such, one of the ways you can handle this is using the following event similar to how it is used in the VertexJitter.cs script included in the TMP Examples & Extras to update the color whenever the text changes.

    Code (csharp):
    1.  
    2. // Subscribe to event fired when text object has been regenerated.
    3. TMPro_EventManager.TEXT_CHANGED_EVENT.Add(ON_TEXT_CHANGED);
    4.  
    Alternatively, you could use the following:

    Code (csharp):
    1.  
    2. /// <summary>
    3. /// Event to allow users to modify the content of the text info before the text is rendered.
    4. /// </summary>
    5. public override event Action<TMP_TextInfo> OnPreRenderText;
    To modify the geometry before it is rendered.

    Both implementations are fine and sort of come down to what works better for your needs.
     
    brandonholt95 likes this.
  5. brandonholt95

    brandonholt95

    Joined:
    Feb 12, 2020
    Posts:
    14
    @Stephan_B First of all, you are so helpful with TMPro related stuff on these forums, thank you lots!

    So In my case, I’m changing the color of keywords typed by the user. Each time the user finishes typing a new word, I check if that word they just typed is a key word and change the color.

    With the methods you provided, I would need to check over every word in the input field and change the color of the word accordingly each time the field is updated.

    Is there a better way I can implement those methods from a performance perspective, for example, caching references to the words I’ve already established are key words and then just checking the references to see if they still exist or have been modified? If so, what reference could I store? The TMP_WordInfo var, word index, etc? I’m not sure what sort of reference would be robust to deleting large chunks of text, or deleting a word in the middle of the paragraph.

    As a reminder, this is for a notes style input, so the input size would be large, and constantly edited. Perhaps >1000 words or if use your suggestion from the other thread I would break into different paragraphs for each input field, which would still be 100s of words in each field.

    thanks in advance!

    Edit: I just thought, you might suggest making those key words “links” with the <link=“ID”> notation. This is originally what I wanted to do separately, but ran into an issue using this. My issue here was that I rely on the character counts for inserting suggested keywords while typing (auto correct style) and this I rely on the first and last character index properties of the wordinfo component. With the link rich text tag, these character indices don’t account for that and thus when I insert words they get inserted into the wrong location. Also, if the user deleted a linked word, the rich text tag doesn’t go away, so my program still thinks there is a key word. These issues made me think it’s best to avoid using links in my case. But if you know of some way I can overcome this too that would be beneficial for the the larger set of issues, I’m all ears!

    Indeed, searching every word every time the field is changed is too slow, even after about 10 words. This is the method that is called on TEXT_CHANGED_EVENT:

    Code (CSharp):
    1.     private void UpdateKeywords(UnityEngine.Object obj)
    2.     {
    3.         TMP_WordInfo[] words = input.textComponent.textInfo.wordInfo;
    4.  
    5.         for (int i = 0; i < input.textComponent.textInfo.wordCount; i++)
    6.         {
    7.             List<Word> keyWord = wordData.GetSuggestions(words[i].GetWord(), 1); // search my keyword database to find a match
    8.  
    9.             if (keyWord.Count == 0) continue;
    10.  
    11.             SetWordColor(input.textComponent, words[i], keyWord[0]);
    12.         }
    13.     }
     
    Last edited: Sep 17, 2021
  6. larryPlayablStudios

    larryPlayablStudios

    Joined:
    May 15, 2018
    Posts:
    31
    @brandonholt95 I'm trying to do something similar involving dynamic color changing in an input field, and I'm wondering if you ever found a more performant solution. Or maybe @Stephan_B has a suggestion?