Search Unity

Any better way to handle strings for a "typewriter" style effect?

Discussion in 'Scripting' started by kathode, Jan 18, 2018.

  1. kathode

    kathode

    Joined:
    Jan 15, 2013
    Posts:
    63
    Hi, I'm hoping someone can recommend me some ways to increase efficiency of some of my string handling. I have a short tutorial section in my game where a character speaks to you. I use a "typewriter" style text box that fills in one letter at a time. The code is pretty straightforward:

    Code (CSharp):
    1.     IEnumerator Typewriter(string text, dfLabel label)
    2.     {
    3.         foreach (char c in text)
    4.         {
    5.             label.Text = label.Text + c;
    6.             yield return new WaitForSeconds(.03f);
    7.         }
    8.         yield return null;
    9.     }
    The Text property is just a string property on the label object. I'm seeing a lot of small hitches, and a couple big ones, all that seem linked to garbage collection when strings are loaded. I've seen references to StringBuilder as a better way of handling strings, but I haven't yet figured it out. Is there a better way to handle this kind of string manipulation?
     
  2. whileBreak

    whileBreak

    Joined:
    Aug 28, 2014
    Posts:
    289
    I have used something like this before, the hiccups might be because of the fps, don't think so though cause it would have to be running at less than 30 fps. In my case I never had an issue, still i would also like to know if there is a better way.
     
  3. Brathnann

    Brathnann

    Joined:
    Aug 12, 2014
    Posts:
    7,188
    StringBuilder probably isn't going to be much better here. As you would essentially be converting it each time to a string per letter.

    I'd suggest looking into textmeshpro, which is free now that Unity owns it. And here is a typerwriter example using it


    I haven't tested it, but I would think it's faster and less garbage as it's not creating a string each time from a glance at the video (I just skimmed it)
     
  4. whileBreak

    whileBreak

    Joined:
    Aug 28, 2014
    Posts:
    289
    Can it work with Unity's UI?
     
  5. Brathnann

    Brathnann

    Joined:
    Aug 12, 2014
    Posts:
    7,188
    TMP has a UGUI part that works with Unity's canvas. Should work the same. TMP itself is a replacement for Unity's Text UI and doesn't inherit from Text. TMP also includes a few other things like it's own inputfield.

    We use it in one of our apps that is very heavy on text since it provides a ton of formatting options and has much sharper text. You should be able to use it anywhere you would use Unity's Text if you wanted. (we are in the one app)

    As for the example above, should be fine, though they may be using a different TMP component, it should work with the TMP UGUI component also if you target that instead. Again I haven't tested this script from the video.
     
    whileBreak likes this.
  6. DonLoquacious

    DonLoquacious

    Joined:
    Feb 24, 2013
    Posts:
    1,667
    The garbage you're generating from the strings is tiny compared to the garbage you're generating from not caching the yield instruction. Try this:
    Code (CSharp):
    1. IEnumerator Typewriter(string text, dfLabel label)
    2. {
    3.     var waitTimer = new WaitForSeconds(.3f);
    4.     foreach (char c in text)
    5.     {
    6.         label.Text = label.Text + c;
    7.         yield return waitTimer;
    8.     }
    9. }
    I can't imagine any solution that's more efficient than this, unless you want to put the "waitTimer" definition in the class scope instead, and just recreate it when anyone changes a public "typerate" property or something.
     
    Last edited: Jan 18, 2018
  7. TonyLi

    TonyLi

    Joined:
    Apr 10, 2012
    Posts:
    12,697
    Two comments:

    1. If your Text component is center-aligned, then as you add characters it will grow from the center. Instead of this:
    Code (csharp):
    1. label.Text = label.Text + c;
    you may want to set label.Text to the full string, but make part of it invisible using the <color> rich text code. It's been a while since I used DFGUI, but I think it supports rich text. So your strings would progressively look like this:

    H<color=#00000000>ello world</color>
    He<color=#00000000>llo world</color>
    Hel<color=#00000000>lo world</color>
    Hell<color=#00000000>o world</color>
    Hello<color=#00000000> world</color>

    etc.​

    At least that's how the Dialogue System's typewriter effect for Unity UI does it. For TextMesh Pro, it just uses TMP's built-in reveal functionality.


    2. If you want to avoid some of the garbage generated by creating new strings, use a StringBuilder. Create a new StringBuilder containing the maximum characters you'll need (e.g., 16 in the example above). Call StringBuilder.Clear() to start a new string and StringBuilder.Append/AppendFormat() to add fragments to the string.
     
    kathode and whileBreak like this.
  8. kathode

    kathode

    Joined:
    Jan 15, 2013
    Posts:
    63
    Thanks, great information. Between this and caching WaitForSeconds, I think I can get garbage down quite a bit. And yeah, good old DFGUI. Should have dumped it long ago...
     
  9. theLameBrain

    theLameBrain

    Joined:
    May 27, 2020
    Posts:
    5
    Sorry to necro-thread this, but I have one other tip. It is possible for the code to be running perfectly but still feel like it is lagging because it still pauses when placing spaces.
    On my phone, so no practical examples, but don't delay if the last character was a space, and it will "feel" smoother.