Search Unity

TextMesh Pro Dynamically Resizing TextMesh Pro objects

Discussion in 'UGUI & TextMesh Pro' started by esteban16108, Mar 7, 2018.

  1. esteban16108

    esteban16108

    Joined:
    Jan 23, 2014
    Posts:
    158
    Hi, Don't know how to really explain this but I will try.

    I'm working on an Android 2D game where random words fall from the top and you have to collect them in order, to complete the desire phrase.

    I made it work in the Canvas Space (Unity UI not TMPro) but found out that creating and destroying many Buttons at the time makes the game sluggish so I figured out to try TextMesh Pro not in the canvas.

    This objects should have a Collider2d to interact with other objects in the scene and to be able to be collected via Taps/Clicks.

    So my idea is to have a TMPro Object prefab where it can be Set the Word (string text) and a Size. The problem I'm facing right now is that when changing the Text and Size the Text Container doesn't resize itself.

    I was trying to find something like this but the only example I found was the Benchmark and that doesn't change the text, only creates TMPro objects dynamically and always using 1 digit numbers.

    Any clue on how can I tackle this, maybe I'm missing something completely.

    Thanks in advance.
     
  2. Beks_Omega

    Beks_Omega

    Joined:
    Jun 7, 2017
    Posts:
    72
    I don't think your problem is necessarily Buttons vs TMPro objects (but it might be), I think it's more likely to do with destroying and creating so many objects, which can be very hard on mobile. So if you implement some kind of object pooling system I think that could help a lot with your sluggishness problem, but I'm no expert hehe.

    As for changing the size of text containers it doesn't seem like they work quite like that. I don't know a ton about TMPro but after watching this video it looks like the text container defines the region the text can live in, not that the text size defines the size of the text container.

    I would play around with setting the text to auto set its size to fit in the container, and then resizing the container directly.
     
  3. Stephan_B

    Stephan_B

    Joined:
    Feb 26, 2017
    Posts:
    6,595
    Whenever dealing with a fair amount of objects, as @Beks_Omega suggested, using some type of pooling to recycle objects is always best as constantly instantiating new objects is very costly.

    The normal <TextMeshPro> component does provide for better performance but that mostly because of the Canvas system having a much greater overhead. So I would continue to use the normal TextMeshPro component.

    With regards to the Text Container (RectTransform) again as @Beks_Omega this defines the area where the text lives and not the other way around. However, that doesn't mean we can't have the button resize as you desire based on the text.

    Assuming we are only dealing with a word at a time or a few words but on a single line (ie. no word wrapping), I would create a prefab which contains a button and a text object. A script would be used to control the resizing of the button when you change the text as well as when this object is enabled / disabled as part of the pooling implementation. This part is not specific to TextMesh Pro.

    With regards to the resizing of the button, here is an example of how this can be done by using the TMP_Text.PreferredValues() function.

    Code (csharp):
    1.  
    2. using UnityEngine;
    3. using TMPro;
    4. public class ButtonResizeExample : MonoBehaviour {
    5.     public TMP_Text TextComponent;
    6.     public Transform ButtonTransform;
    7.     private void Awake()
    8.     {
    9.         string theWord = "Example";
    10.        
    11.         // Get the size of the text for the given string.
    12.         Vector2 textSize = TextComponent.GetPreferredValues(theWord);
    13.         // Set the text
    14.         TextComponent.text = theWord;
    15.         // Adjust the button size / scale.
    16.         ButtonTransform.localScale = textSize;
    17.     }
    18. }
    Here is an example of the setup of those objects. The TextMeshPro object size is the default 20, 5 and the Quad is the same for its scale values.

    upload_2018-3-7_14-23-57.png

    In the example above, whatever word you set will result in the size of the quad to be adjusted to match. This is a crude example but demonstrates on way of doing this. So in your implementation, whenever you need to introduce a new word, you would get it from the object pool. Set the new text which would adjust the size to match the new word and voila.

    Please Note: Whenever properties of the text object, this will result in the text object having to be regenerated. This regeneration happens once per frame in OnPreCull which is late in the update cycle. As such if you make changes to the text object before it is re-generated, accessing the text objects data like bounds or textInfo (which contains information about the text object including characters, words, lines, etc.) will not be reflective of the new changes until this regeneration takes place. When there are times when you want to get this information right away after changing a property of the text object, you can use the ForceMeshUpdate() function which forces an immediate regeneration.
     
    esteban16108 and Beks_Omega like this.
  4. esteban16108

    esteban16108

    Joined:
    Jan 23, 2014
    Posts:
    158
    @Stephan_B Thanks a lot for taking your time on explaining this which such detail.

    Didn't know about TMP_Text and about GetPreferredValues.

    Will try it and let you know.

    Regards.
     
  5. esteban16108

    esteban16108

    Joined:
    Jan 23, 2014
    Posts:
    158
    @Stephan_B It worked, but had to add


    TextComponent.autoSizeTextContainer = true;


    So the word doesn't get mangled. My question now is how do I get the size of the Text Container? So I can resize the quad to the Container size in case I want to add margins etc...?

    Now will try to reuse the objects for using them on a Pooling system.

    Thanks.
     
  6. Stephan_B

    Stephan_B

    Joined:
    Feb 26, 2017
    Posts:
    6,595
    The size value returned from the PreferredValues() would be used to change the RectTransform.sizeDelta.

    Now unless these words need to be on multiple lines of text, I would simply disable word wrapping, not use autoSizeTextContainer and set alignment to center so that its transform / pivot is the same as the button / quad and only change the size of the this element behind the text.
     
  7. esteban16108

    esteban16108

    Joined:
    Jan 23, 2014
    Posts:
    158
    @Stephan_B

    No need to use multiline, they are just words, meaning 1 word per object.

    One feature of the game is that the words falling can be of variable size and the problem I'm facing now is that when reusing the previous object and changing the fontSize the Text Container doesn't change size hence sometimes the word gets wrapped.

    Or should I Scale the object instead?

    Here is the test code I put together:

    Code (Csharp):
    1.  
    2. using System.Collections;
    3. using System.Collections.Generic;
    4. using TMPro;
    5. using UnityEngine;
    6.  
    7. public class ButtonResize : MonoBehaviour
    8. {
    9.     public TMP_Text TextComponent;
    10.     public Transform ButtonTransform;
    11.  
    12.     private void Awake()
    13.     {
    14.         Construct("Example", 1);
    15.     }
    16.  
    17.     // This would be used when getting a Word object from the Pool and resetting for other word/size
    18.     public void Construct(string text, float fontSize)
    19.     {
    20.         TextComponent.fontSize = fontSize;
    21.         TextComponent.autoSizeTextContainer = true;
    22.         TextComponent.text = text;
    23.      
    24.         Vector2 textSize = TextComponent.GetPreferredValues(text);
    25.      
    26.         // Adjust the button size / scale.
    27.         ButtonTransform.localScale = textSize + Vector2.one;
    28.     }
    29.  
    30. }
    31.  
     
    tokar_dev likes this.
  8. Stephan_B

    Stephan_B

    Joined:
    Feb 26, 2017
    Posts:
    6,595
    I would scale the parent which is more efficient than forcing a whole regeneration of the text and having to keep resizing that button / quad.

    With Signed Distance Field which is what TMP uses, TMP still needs to pass updated scale to the shader but that is more efficient than a whole new generation. You will need to make sure the scale of the parent remains Uniform for (x, y and z) See the following post.
     
  9. esteban16108

    esteban16108

    Joined:
    Jan 23, 2014
    Posts:
    158
    @Stephan_B One last thing, If I re-scale the parent, how to handle when using longer words? The problem is that the Text Container is not resized and the word gets wrapped around.

    https://prnt.sc/ioc7q3
     
  10. esteban16108

    esteban16108

    Joined:
    Jan 23, 2014
    Posts:
    158
    Just for clarification I'm NOT using TMP on UI Canvas.
     
  11. Stephan_B

    Stephan_B

    Joined:
    Feb 26, 2017
    Posts:
    6,595
    That only happens if word wrapping is enabled. Disable it by using TMP_Text.enableWordWrapping = false;

    Then provided the pivot / transform of the button and text object overlap and you are using center alignment, the length of the words should not matter.

    I guess to center the text better vertically, you could also use the midline or vertical geometry alignment option.

    upload_2018-3-7_18-13-38.png
     
  12. esteban16108

    esteban16108

    Joined:
    Jan 23, 2014
    Posts:
    158
    @Stephan_B The word wrapping thing did it. The Alignment was set already on the TMP component by hand. But Didn't know that the Word Wrapping was true by def.

    So, the size of the text container would not make any problem?

    Thanks a lot. Will try to update with a demo.
     
    Stephan_B likes this.
  13. Stephan_B

    Stephan_B

    Joined:
    Feb 26, 2017
    Posts:
    6,595
    When Word Wrapping is disabled, there is no width to check against so no automatic word wrapping. Linefeed would still create new lines but again this would not impact / prevent this from working again provided the text is centered (ie. since we are scaling the button based on the center point).
     
  14. Davon_Allen_AofL

    Davon_Allen_AofL

    Joined:
    Jul 17, 2019
    Posts:
    9
    i hate to necro this but it is very close to what i am doing.

    I have a book with a tmpro ugui text box for where the text goes on a page.

    i need to put multiple gameobject boxes which I use as highlights behind the text at the position of the text.

    however i am highlighting by phrase not by word . so that means it can contain a combination of words and characters.
    this also means that it could go between multiple lines.

    i have search all over the web for examples but seem to have been the first to want this information.

    how would i go about just looking for phrases. and potentially using the word or character and line info to fetch or create position data so that i can know where to instantiate the highlight boxes? this is all ugui based.