Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

TextMesh Pro [Solved] TMP_TextInfo does not work on a List that is populated at runtime.

Discussion in 'UGUI & TextMesh Pro' started by Vee_Oh, Apr 4, 2019.

  1. Vee_Oh

    Vee_Oh

    Joined:
    Mar 26, 2015
    Posts:
    10
    TLDR: Using TMP_TextInfo in a Loop(For or Foreach) on a List or Array that is populated at runtime does not work. The TMP_TextInfo seems to refer to the same TMP_Text, no matter how the reference is provided.

    Hello, I really hope someone can help me with this, it has been driving me crazy trying to figure it out, and I am completely stuck, and have tried everything I can think of over the last week and a half.

    • I am using the TMP_TextInfo class to do various tasks. I use characterInfo, characterCount, line count etc.
    I use it in a pretty standard way, for example to get the character at a certain index, or to get the line count of a certain TMP_Text instance. So nothing fancy or strange.

    All of this works perfectly if I have a List or Array that is Pre-Populated, and also if I use a single instance that has been pre-assigned (Example, the TMP_Text component of a TMP_InputField)
    So basically if I call my method that uses TMP_TextInfo it works perfectly, as expected, and gives me the correct results.

    However...

    As soon as I populate a List/Array at runtime, and then try to access the TMP_TextInfo of the items that have been added to the collection at runtime, it just returns the same result for all of the TMP_Text objects. So it does not matter if 1 TMP_Text has 9 lines of text and the other only has 2, it will return 1 as a result for both of them(For example)

    • If I debug the value of TMP_Text.text I get the correct result., so for example, the one Tmp_Text.text would return "Grapefruit" and the other would return "Kiwi" if I query the actual text value, so I know I am dealing with different TMP instances for a fact.
    • However if I debug TMP_TextInfo.characterCount for the same TMP_Text, both would return 6 for example.
    The same goes for any other TMP_Textinfo values that I try to access.

    I have made a small scene that illustrates my problem.
    You can download it, or quickly recreate it if you wish.

    1. On the left is a Canvas with 5 TMP_Text instances
    2. The text values are "A", "BB", "CCC", "DDDD" and "EEEEE" (So 1 - 5 Characters) added to the List TMP_PredefinedSource
    3. On the right is an empty Canvas with 1 Template TMP_Text with the value "WORD"
    4. The template object has a Button on it that will call GetTextInfo to show that the result is correct if you query a single instance, even if it was instantiated at runtime.
    5. When you hit play, it will spawn 5 template objects and set the text to "F", "FF" ... "FFFFF" (So 1 - 5 Characters) and add them to the List TMP_SpawnedSource
    6. It will then print the Text, and the character count for each instance in each of the Two lists.
    7. So the A - E range debugs correctly.
    8. However the F-FFFFF range prints a character count of 4 for all the instances.
    A screenshot of the result:

    TMP_TextInfoTest.png

    And the code that generates this:

    Code (CSharp):
    1.  
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4. using System.Linq;
    5. using TMPro;
    6.  
    7. public class TInfo_test : MonoBehaviour
    8. {
    9.     public List<GameObject> TMP_PredefinedSource;
    10.     public List<GameObject> TMP_SpawnedSource;
    11.  
    12.     public GameObject templateObject;
    13.     public Transform parent;
    14.  
    15.     private void Start()
    16.     {
    17.         InstantiateTemplate();
    18.     }
    19.  
    20.     public void InstantiateTemplate()
    21.     {
    22. //Spawn TMP_Text, add to list, set the text value to F, FF, FFF, FFFF, FFFFF
    23.         for(int i = 1; i<=5; i++)
    24.         {
    25.             var GO = Instantiate(templateObject, parent);
    26.             TMP_SpawnedSource.Add(GO);
    27.             GO.SetActive(true);
    28.             var t = GO.GetComponent<TMP_Text>();
    29.             t.text = string.Concat(Enumerable.Repeat("F",i));
    30.         }
    31.  
    32.         GetTextInfoFromCollection();
    33.     }
    34.  
    35.     public void GetTextInfoFromCollection()
    36.     {
    37. //Get TMP_Text component and print Character Count for each instance.
    38. //WORKS
    39.         foreach (GameObject t in TMP_PredefinedSource)
    40.         {
    41.             var tMesh = t.GetComponent<TMP_Text>();
    42.             TMP_TextInfo t_Info = tMesh.textInfo;
    43.             print("Character Count: " + tMesh.text+ " = " + t_Info.characterCount);
    44.         }
    45. //DOES NOT WORK
    46.         foreach (GameObject t in TMP_SpawnedSource)
    47.         {
    48.             var tMesh = t.GetComponent<TMP_Text>();
    49.             TMP_TextInfo t_Info = tMesh.textInfo;
    50.             print("Character Count: " + tMesh.text + " = " + t_Info.characterCount);
    51.         }
    52.     }
    53. //Do exactly the same as above, but only for instance that is passed specifically
    54. //WORKS
    55.     public void GetTextInfo(GameObject GO)
    56.     {
    57.         var tMesh = GO.GetComponent<TMP_Text>();
    58.         TMP_TextInfo t_Info = tMesh.textInfo;
    59.         print("Character Count: " + tMesh.text + " = " + t_Info.characterCount);
    60.     }
    61. }
    62.  
    Here is a link to the UnityProject with the test (2018.3.10f1):
    https://drive.google.com/file/d/1vmFuK7jjNNLSda_FVb6mZnNpXGNFhS9J/view?usp=sharing

    If you have any suggestions I would truly appreciate the help.
    Thank you in advance for your time!

    V
     
    Last edited: Apr 6, 2019
  2. Vee_Oh

    Vee_Oh

    Joined:
    Mar 26, 2015
    Posts:
    10
    [UPDATED]

    After upgrading to TMP 1.4 and testing this issue again, it does not work for the pre-populated List either, everything just returns a value of 0.
    It still works when I click on the 'Button', as before (See image)

    If I downgrade to TMP 1.3 again, the pre-populated list works again as explained above, but the List populated at runtime still returns wonky values.

    TMP_TextInfoTest_Update.png
     
    Last edited: Apr 6, 2019
  3. Stephan_B

    Stephan_B

    Joined:
    Feb 26, 2017
    Posts:
    6,595
    First, thank you for providing the repro project to make it simpler / quicker for me to reproduce the reported behavior.

    In terms of the reported behavior and why the TextInfo appears incorrect, it is pretty simple but certainly not obvious until this is explained.

    The content of the TMP_TextInfo gets updated when the text object has been processed / rendered. This occurs late in the update cycle and just before the Camera is rendered in OnPreRender. So for instance, if you were to create a text object in Awake() and set the text to some value and then checked the textInfo, the data contained in the textInfo would be incorrect / nor reflective of the new text since the text object hasn't been processed yet.

    Most of the time this is fine as we usually want to wait until the text object has been processed / rendered before trying to manipulate some of its geometry or act on some of the data contained in the textInfo.

    When a text object has been processed / rendered, you can use the TMPro_EventManager.TEXT_CHANGED_EVENT to get a callback as used in the example VertexJitter.cs script contained in the TMP Examples & Extras.

    However, when there is a need to have the text object updated right away after changing some of its properties, you can use the TMP_Text.ForceMeshUpdate(); to force this update which in turn will update / populate the textInfo with the relevant data.

    Looking at your InstantiateTemplate() function, you are creating new text objects whose textInfo is representative of their initial content / properties which in this case contains the text "WORD" which has 4 characters. Since your GetTextInfoFromCollection() function is called right after these objects are created and before they have had a chance to get processed / rendered, their textInfo still reflects that of their initial content. This can easily be addressed by calling ForceMeshUpdate() on these text components in order to get them processed and their textInfo updated. Below is a modified version of your InstantiateTemplate() function.

    Code (csharp):
    1.  
    2. public void InstantiateTemplate()
    3.     {
    4.         for (int i = 1; i <= 5; i++)
    5.         {
    6.             var GO = Instantiate(templateObject, parent);
    7.             TMP_SpawnedSource.Add(GO);
    8.             GO.SetActive(true);
    9.             var t = GO.GetComponent<TMP_Text>();
    10.             t.text = string.Concat(Enumerable.Repeat("F", i));
    11.             // Force update of the text object
    12.             t.ForceMeshUpdate();
    13.         }
    14.         GetTextInfoFromCollection();
    15.     }
    16.  
    In regards to the change in behavior between 1.3.0 and 1.4.0, it is related to the above and due to a change I made where in Awake(), their the textInfo is cleared. As such in the case of the text objects in the list, as you enter play mode, their textInfo is cleared and then queried before any of these objects have had a chance to get processed / rendered.

    Similar to above, you can revise your GetTextInfoFromCollection() to force an update of these text objects before querying their textInfo.

    Code (csharp):
    1.  
    2. public void GetTextInfoFromCollection()
    3.     {
    4.         foreach (GameObject t in TMP_PredefinedSource)
    5.         {
    6.             var tMesh = t.GetComponent<TMP_Text>();
    7.             // Force update of the text object
    8.             tMesh.ForceMeshUpdate();
    9.             TMP_TextInfo t_Info = tMesh.textInfo;
    10.             print("Character Count: " + tMesh.text+ " = " + t_Info.characterCount);
    11.         }
    12.         foreach (GameObject t in TMP_SpawnedSource)
    13.         {
    14.             var tMesh = t.GetComponent<TMP_Text>();
    15.             // Force update of the text object.
    16.             tMesh.ForceMeshUpdate();
    17.             TMP_TextInfo t_Info = tMesh.textInfo;
    18.             print("Character Count: " + tMesh.text + " = " + t_Info.characterCount);
    19.         }
    20.     }
    21.  
    Making the above changes in your GetTextInfoFromCollection function makes the ForceMeshUpdate in the InstantiateTemplate function unnecessary.
     
    TheRealGrendel and Vee_Oh like this.
  4. Vee_Oh

    Vee_Oh

    Joined:
    Mar 26, 2015
    Posts:
    10
    Thanks so much for explaining it Stephan. I really appreciate it.
    It was driving me nuts! Now I can move forward with my project, since this little thing was stopping me everywhere!
    :):):)

    Enjoy the day, and thank you for replying over the weekend.
     
    Stephan_B likes this.