Search Unity

Resolved Text generated by while loop makes little freeze

Discussion in 'UGUI & TextMesh Pro' started by Virial, Jul 13, 2021.

  1. Virial

    Virial

    Joined:
    Mar 20, 2018
    Posts:
    13
    Hello,

    I am a total novice in any kind of programming, though I'm trying to learn and make a visual novel game. I was struggling to somehow make line consisting of dots to separate text in a box that keeps history so far. When i was finally able to do it using "do while" loop, for some reason game freezes a bit when generating it. Not really sure why it is happening as i doubt that making approximetly 50 dots as a line is so resource consuming, though i could be wrong as i am totally green in this stuff. Using text mesh pro of course.

    Could anyone give me some suggestions as to how to check if it is a direct reason or indirect or how could I find a way to solve it? Worst case i will give up on this feature though I would regret spending couple days trying to figure it out and most of all I really do like this effect.
     
  2. JuliaP_Unity

    JuliaP_Unity

    Unity Technologies

    Joined:
    Mar 26, 2020
    Posts:
    700
    Hello! If you want to keep adding dots while the UI updates at the same time you'll probably want to use a Coroutine to update the text value. If you're doing all the work in one shot without a Coroutine, that means your game's UI won't update while the "do while" loop runs and this may look choppy if your loop is long enough.

    This tutorial is a bit old but it should all still work the same: https://learn.unity.com/tutorial/coroutines
     
  3. Stephan_B

    Stephan_B

    Joined:
    Feb 26, 2017
    Posts:
    6,595
    I would also suggest that you post the code that your using to update the text as this would enable us to understand how you are trying to implement this thus making it easier to provide feedback and suggestions to you.
     
  4. Virial

    Virial

    Joined:
    Mar 20, 2018
    Posts:
    13
    Thank you for your feedback. Sorry for the late reply, as I was busy with work I couldn't touch Unity in last couple days. Function TextGetter receives the part of the text and formats it the way i want it to look, then calls "tester" function to make a separation with that line of dots. I hope this isn't too big of a mess, I tried to clean it a bit removing useless comments. TextGetter function is called with every mouse click on the dialog box (or rather it is called in other function that works like that - showing another part of the text in dialog box, while this function adds text to different box, where whole story so far is contained). Not sure if it is significant information though. I'll try to check using coroutine for this. Just first I have to finish another part of the game that i'm currently struggling with.

    Edit: I tried coroutine, it didn't help. Maybe I'll be more specific as I could have been misunderstood earlier. I don't want dots to show up one at a time, just make instant separation line out of them. I could also make a graphic with line as a separator, but I just like the idea with the dots.

    Code (CSharp):
    1. public string tester () {
    2.  
    3.         _fieldText.ForceMeshUpdate ();
    4.         int a = _fieldText.textInfo.lineCount;
    5.         int b;
    6.         do {
    7.             _fieldText.text += ".";
    8.             _fieldText.ForceMeshUpdate ();
    9.             b = _fieldText.textInfo.lineCount;
    10.         } while(a == b - 1);
    11.         int lastDot = _fieldText.textInfo.characterCount;
    12.         _fieldText.text = _fieldText.text.Remove (lastDot, 1);
    13.    
    14.         return _fieldText.text += "\n";
    15.  
    16.     public void TextGetter (string storyText)
    17.     {
    18.         if (storyText.Contains (":")) {
    19.             var splitted = storyText.Split (new[] { ':' }, 2);
    20.             splitted [1] = splitted [1].Trim ();
    21.             _fieldText.text += "<b>" + splitted [0] + ":</b>\n" + splitted [1] + "\n";
    22.         } else {
    23.             _fieldText.text += storyText;
    24.         }
    25.         tester ();
    26.             if (SoFarBox.activeSelf == true) {
    27.                 StartCoroutine (ScrollDown ());
    28.             }
    29.         _fieldText.ForceMeshUpdate ();
    30.  
    31.  
    32.     }
     
    Last edited: Jul 18, 2021
  5. JuliaP_Unity

    JuliaP_Unity

    Unity Technologies

    Joined:
    Mar 26, 2020
    Posts:
    700
    Yeah my answer was assuming you wanted the dots to come over time, not in one shot, so sorry about that!
    Your code seems to be quite heavy, with the multiple calls to
    ForceMeshUpdate
    . If you could add some dots but not necessarily have them take the whole line, that would avoid checking all the time (e.g. you could add some amount like 5 or 10 dots, maybe even center them, and then break line and start your next line.
     
  6. Stephan_B

    Stephan_B

    Joined:
    Feb 26, 2017
    Posts:
    6,595
    Here is a different way to approach this that will be more efficient.

    Since you are trying to insert a line of dots in your text, we can calculate how many dots are needed to fill a single line given a certain text container width.

    The sample code below is just to illustrate how to create a string of dots of the correct length. You will need to adapt this for your own needs.

    Code (csharp):
    1.  
    2. // Check if dot character is present in the primary font asset.
    3. if (!TextComponent.font.characterLookupTable.ContainsKey('.'))
    4.     return;
    5.  
    6. // Compute the scale of the text relative to the font asset sampling point size and displayed size.
    7. float textScale = TextComponent.fontSize / TextComponent.font.faceInfo.pointSize * TextComponent.font.faceInfo.scale * 0.1f;
    8.  
    9. // Get the scaled horizontal advance of the glyph used by the dot character
    10. float hAdvance = TextComponent.font.characterLookupTable['.'].glyph.metrics.horizontalAdvance * textScale;
    11.  
    12. // Determine how many dot glyphs we can fit on a single line given the width of the RectTransform (rounding down).
    13. int dotCount = (int)(TextComponent.rectTransform.sizeDelta.x / hAdvance);
    14.  
    15. // Compose a new string made up of dots to fill a line of text.
    16. string dotLineSeparator = new string('.', dotCount);
    17.  
    18. // Insert this line of dots
    19. TextComponent.text += "\n" + dotLineSeparator;
    20.  
    In this implementation, we simply use the metrics of the dot glyph to determine its width which is the horizontal advance value.

    Since the glyph metrics are relative to the sampling point size of the font asset, we need to scale this to the font size used by the text object.

    We then simply figure out how many of those we can fit on a single line given the RectTransform width and compose a string to append to the text.

    The check for the presence of the dot character, textScale and hAdvance can be cached as this will not change unless, you change the font asset or point size so no need to compute that every time.

    Note that <b> bold does increase the spacing between characters so you would need to account for that. You would need to lookup the Bold Spacing which is found in the Fount Weights table of the font asset inspector and make sure this value is taken into account in the hAdvance calculations.

    The above approach will be more efficient since we are only updating the text once regardless of the number of dots being inserted for a given line width.
     
  7. Virial

    Virial

    Joined:
    Mar 20, 2018
    Posts:
    13
    @JuliaP_Unity
    No, no, I was just not precise enough with words. Might be partly because I barely use english in everyday life.
    Unfortunately I couldn't make it without ForceMeshUpdate. I already struggled how to limit those when I was looking up the way to make the text scroll down automatically. But without them I couldn't get up to date data of the text in the component.

    @Stephan_B
    Yes, that kind of approach was my first try, but after few days not being able to figure out how to calculate the actual width and number of dots that would fit in line I have changed the approach. Was thinking about trying something with glyphs too, but I'm still too inexperienced to find solution even looking through documentation. Thank You very much. I will try this approach, but I already see that this is very valuable for learning how to work with TextMeshPro.
     
  8. Virial

    Virial

    Joined:
    Mar 20, 2018
    Posts:
    13
    After another couple of days I'm close to giving up. Unfortunately code above didn't work. The problem is that, for some reason, it counts 0 as for "rectTransform.sizeDelta.x" or when I join anchors it counts the same as for "rect.width" which returns over 6 lines of dots. I don't know if it is the result of TextComponent being Component of the Child Object in whole scrollbar configuration or there is another reason. Anyway it seems it returns way too big number (over 600) comparing to 0.8 for hAdvance. Tried to find another solution, but I have no idea where to find code for the actual text container width.
     
  9. CedricLehr

    CedricLehr

    Joined:
    Apr 5, 2017
    Posts:
    13
    Does the size of your Textfield ever change? If not you can make a static string of dots without the need to calculate it each time.

    Alternatively you could save the dotted line string you computed already in like a dictionary or something, so it will only use the loop if you don't have it saved already.
     
  10. Stephan_B

    Stephan_B

    Joined:
    Feb 26, 2017
    Posts:
    6,595
    The size of the RectTransform and how it is reported is affected by the Anchors and Anchor mode (stretch or not). It might be better for you to look at the RectTransform.rect width and height instead.

    Make sure that your computed textScale is correct. This scale is the relationship between your sampling point size and display size. For instance, if the sampling point size of the font asset was 100 and the point size of the text object 100, then this scale is 1. This means the values for the glyph metrics including horizontal advance would be the same.
     
  11. april_4_short

    april_4_short

    Joined:
    Jul 19, 2021
    Posts:
    489
    I'm a visual thinker, or so they say.

    Can you provide a picture of what you're trying to achieve, and highlight the part you're having a problem with?

    Due to my limitations in programming, I might be able to suggest a simple way of doing it with a minimal code (and therefore), minimal code problems approach.
     
  12. Virial

    Virial

    Joined:
    Mar 20, 2018
    Posts:
    13
    upload_2021-8-15_18-6-51.png
    Above is the effect I wanted to achieve.

    Took a while, especially since it was a hot weeks at work, but i guess i finally was able to achieve what i wanted to. I was on the verge of creating alternative way, namely pregenerating seperator line using the previous method on start of the program so it won't have to generate it everytime, however I was finally able to figure out how to modify the code provided by @Stephan_B . Once again thank you very much. I had to modify the multiplier for textScale (not sure why there was 0.1f), I guess this was the matter you wrote in last post, but honestly I don't even have the slightest idea how to check that stuff you wrote. I just make it multiply as 1 (just random guess) and it worked. I had to stick with rectTransform.width and i got close to one line finally. Then I realized i have to consider margins and voila. It works. Not sure if I should leave it to compute everytime as when I was changing resolutions the number of dots didn't change anyway... but I guess I'll get back to i if I'll have any problems with performance.

    If anyone is interested, this is how code looks like:
    Code (CSharp):
    1.     public void DotLineSeparator() {
    2.  
    3.         float textScale = _fieldText.fontSize / _fieldText.font.faceInfo.pointSize * _fieldText.font.faceInfo.scale * 1f;
    4.  
    5.         float hAdvance = _fieldText.font.characterLookupTable ['.'].glyph.metrics.horizontalAdvance * textScale;
    6.  
    7.         float marginSize = _fieldText.margin.x + _fieldText.margin.z;
    8.  
    9.         int dotCount = (int)((_fieldText.rectTransform.rect.width - marginSize) / hAdvance);
    10.  
    11.         string dotLineSeparator = new string ('.', dotCount);
    12.  
    13.         _fieldText.text += dotLineSeparator + "\n";
    14.     }
     
    Stephan_B likes this.